diff --git a/pkg/shell/active-pages-modal.jsx b/pkg/shell/active-pages-modal.tsx similarity index 96% rename from pkg/shell/active-pages-modal.jsx rename to pkg/shell/active-pages-modal.tsx index 665497801614..396bbbece989 100644 --- a/pkg/shell/active-pages-modal.jsx +++ b/pkg/shell/active-pages-modal.tsx @@ -17,6 +17,8 @@ * along with Cockpit; If not, see . */ +// @cockpit-ts-relaxed + import cockpit from "cockpit"; import React, { useState } from "react"; @@ -27,9 +29,11 @@ import { Split, SplitItem } from "@patternfly/react-core/dist/esm/layouts/Split/ import { Modal } from "@patternfly/react-core/dist/esm/components/Modal/index.js"; import { useInit } from "hooks"; +import { ShellState } from "./state"; + const _ = cockpit.gettext; -export const ActivePagesDialog = ({ dialogResult, state }) => { +export const ActivePagesDialog = ({ dialogResult, state } : { dialogResult, state: ShellState }) => { function get_pages() { const result = []; for (const frame of Object.values(state.frames)) { diff --git a/pkg/shell/failures.jsx b/pkg/shell/failures.tsx similarity index 91% rename from pkg/shell/failures.jsx rename to pkg/shell/failures.tsx index d45f93c72dfd..8c9992b15931 100644 --- a/pkg/shell/failures.jsx +++ b/pkg/shell/failures.tsx @@ -17,6 +17,8 @@ * along with Cockpit; If not, see . */ +// @cockpit-ts-relaxed + import cockpit from "cockpit"; import React from "react"; @@ -26,7 +28,7 @@ import { Page, PageSection, PageSectionVariants } from "@patternfly/react-core/d import { Stack } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js"; import { ExclamationCircleIcon } from "@patternfly/react-icons"; -import { EmptyStatePanel } from "cockpit-components-empty-state.jsx"; +import { EmptyStatePanel } from "cockpit-components-empty-state"; import { codes } from "./hosts_dialog.jsx"; @@ -61,20 +63,29 @@ export const EarlyFailure = () => { }; const EarlyFailureReady = ({ - loading, + loading = false, title, paragraph, - reconnect, - troubleshoot, - onTroubleshoot, + reconnect = false, + troubleshoot = false, + onTroubleshoot = () => {}, watchdog_problem, - onReconnect + onReconnect = () => {}, +} : { + loading?: boolean, + title: string, + paragraph: string, + reconnect?: boolean, + troubleshoot?: boolean, + onTroubleshoot?: () => void, + watchdog_problem?: string, + onReconnect?: () => void, }) => { return (
- diff --git a/pkg/shell/hosts.jsx b/pkg/shell/hosts.tsx similarity index 83% rename from pkg/shell/hosts.jsx rename to pkg/shell/hosts.tsx index 5a501a7aa9c5..5c835c3f5f0a 100644 --- a/pkg/shell/hosts.jsx +++ b/pkg/shell/hosts.tsx @@ -1,8 +1,28 @@ +/* + * This file is part of Cockpit. + * + * Copyright (C) 2024 Red Hat, Inc. + * + * Cockpit is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * Cockpit is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Cockpit; If not, see . + */ + +// @cockpit-ts-relaxed + import cockpit from "cockpit"; import React from 'react'; import ReactDOM from "react-dom"; -import PropTypes from 'prop-types'; import { Button } from "@patternfly/react-core/dist/esm/components/Button"; import { CaretDownIcon, @@ -19,23 +39,30 @@ import { encode_location } from "./util.jsx"; import { split_connection_string } from "./machines/machines"; import { add_host, edit_host, connect_host } from "./hosts_dialog.jsx"; +import { ShellState } from "./state"; + const _ = cockpit.gettext; class HostsSelector extends React.Component { - constructor() { - super(); + el: HTMLDivElement; + props: { children }; + + constructor(props) { + super(props); + this.props = props; + this.el = document.createElement("div"); this.el.className = "view-hosts"; } componentDidMount() { const hosts_sel = document.getElementById("nav-hosts"); - hosts_sel.appendChild(this.el); + hosts_sel?.appendChild(this.el); } componentWillUnmount() { const hosts_sel = document.getElementById("nav-hosts"); - hosts_sel.removeChild(this.el); + hosts_sel?.removeChild(this.el); } render() { @@ -63,10 +90,16 @@ export const CockpitCurrentHost = ({ current_user, machine }) => { ); }; +interface CockpitHostsState { opened, editing, current_user, current_key } + // full host switcher export class CockpitHosts extends React.Component { + props: { selector, host_modal_state, state: ShellState }; + state: CockpitHostsState; + constructor(props) { super(props); + this.props = props; this.state = { opened: false, @@ -91,7 +124,7 @@ export class CockpitHosts extends React.Component { static getDerivedStateFromProps(nextProps, prevState) { if (nextProps.state.current_machine.key !== prevState.current_key) { - document.getElementById(nextProps.selector).classList.toggle("interact", false); + document.getElementById(nextProps.selector)?.classList.toggle("interact", false); return { current_key: nextProps.state.current_machine.key, opened: false, @@ -102,9 +135,9 @@ export class CockpitHosts extends React.Component { } toggleMenu() { - document.getElementById(this.props.selector).classList.toggle("interact", !this.state.opened); + document.getElementById(this.props.selector)?.classList.toggle("interact", !this.state.opened); - this.setState(s => { + this.setState((s: CockpitHostsState) => { return ( { opened: !s.opened, @@ -118,7 +151,7 @@ export class CockpitHosts extends React.Component { await add_host(this.props.host_modal_state, this.props.state); } - async onHostEdit(event, machine) { + async onHostEdit(_event, machine) { await edit_host(this.props.host_modal_state, this.props.state, machine); } @@ -133,7 +166,7 @@ export class CockpitHosts extends React.Component { } onEditHosts() { - this.setState(s => { return { editing: !s.editing } }); + this.setState((s: CockpitHostsState) => { return { editing: !s.editing } }); } onRemove(event, machine) { @@ -202,8 +235,8 @@ export class CockpitHosts extends React.Component { } />; - const label = current_machine.label || ""; - const user = current_machine.user || this.state.current_user; + const label = current_machine?.label || ""; + const user = current_machine?.user || this.state.current_user; const add_host_action = ; @@ -238,7 +271,7 @@ export class CockpitHosts extends React.Component { selector={this.props.selector} groups={groups} item_render={render} - sorting={(a, b) => true} + sorting={(_a, _b) => true} filtering={this.filterHosts} current={label} jump={() => console.error("internal error: jump not supported in hosts selector")} @@ -254,9 +287,3 @@ export class CockpitHosts extends React.Component { ); } } - -CockpitHosts.propTypes = { - state: PropTypes.object.isRequired, - host_modal_state: PropTypes.object.isRequired, - selector: PropTypes.string.isRequired, -}; diff --git a/pkg/shell/hosts_dialog.jsx b/pkg/shell/hosts_dialog.jsx index aa2f7f22eb4c..e9d34d1aaf55 100644 --- a/pkg/shell/hosts_dialog.jsx +++ b/pkg/shell/hosts_dialog.jsx @@ -74,8 +74,7 @@ export const HostModalState = () => { close_modal, }; - cockpit.event_target(self); - return self; + return cockpit.event_target(self); }; /* Activate and jump to a newly added machine, identified by its diff --git a/pkg/shell/shell.jsx b/pkg/shell/shell.tsx similarity index 91% rename from pkg/shell/shell.jsx rename to pkg/shell/shell.tsx index 1e1e74fd7b4e..c41f8ef8adea 100644 --- a/pkg/shell/shell.jsx +++ b/pkg/shell/shell.tsx @@ -17,6 +17,8 @@ * along with Cockpit; If not, see . */ +// @cockpit-ts-relaxed + import cockpit from "cockpit"; import React, { useEffect } from 'react'; @@ -47,7 +49,7 @@ const SkipLink = ({ focus_id, children }) => { { - document.getElementById(focus_id).focus(); + document.getElementById(focus_id)?.focus(); ev.preventDefault(); }}> {children} @@ -86,6 +88,8 @@ const Shell = () => { if (!ready) return null; + cockpit.assert(current_machine && current_manifest_item); + const title_parts = []; if (current_manifest_item.label) title_parts.push(current_manifest_item.label); @@ -112,7 +116,7 @@ const Shell = () => { style={ { '--ct-color-host-accent': (current_machine.address == "localhost" ? undefined : current_machine.color) - } + } as React.CSSProperties }> {_("Skip to content")} @@ -155,6 +159,10 @@ const Shell = () => {
); }; +interface ShellWindow extends Window { + features?: object; +} + function init() { cockpit.translate(); @@ -165,17 +173,17 @@ function init() { window.name = "cockpit1"; /* Tell the pages about our features. */ - window.features = { + (window as ShellWindow).features = { navbar_is_for_current_machine: true }; function follow(arg) { /* A promise of some sort */ if (arguments.length == 1 && typeof arg.then == "function") { - arg.then(function() { console.log.apply(console, arguments) }, - function() { console.error.apply(console, arguments) }); + arg.then(function(...args) { console.log(...args) }, + function(...args) { console.error(...args) }); if (typeof arg.stream == "function") - arg.stream(function() { console.log.apply(console, arguments) }); + arg.stream(function(...args) { console.log(...args) }); } } @@ -190,7 +198,7 @@ function init() { } }); - const root = createRoot(document.getElementById("shell")); + const root = createRoot(document.getElementById("shell")!); root.render(); } diff --git a/pkg/shell/superuser.jsx b/pkg/shell/superuser.tsx similarity index 93% rename from pkg/shell/superuser.jsx rename to pkg/shell/superuser.tsx index 58dd7428e857..aa9911248ec3 100644 --- a/pkg/shell/superuser.jsx +++ b/pkg/shell/superuser.tsx @@ -17,6 +17,8 @@ * along with Cockpit; If not, see . */ +// @cockpit-ts-relaxed + import cockpit from "cockpit"; import React, { useState } from "react"; import { useObject, useInit, useEvent } from "hooks"; @@ -48,14 +50,14 @@ const UnlockDialog = ({ proxy, host }) => { const D = useDialogs(); useInit(init, [proxy, host]); - const [methods, setMethods] = useState(null); - const [method, setMethod] = useState(false); + const [methods, setMethods] = useState(null); + const [method, setMethod] = useState(false); const [busy, setBusy] = useState(false); const [cancel, setCancel] = useState(() => D.close); - const [prompt, setPrompt] = useState(null); - const [message, setMessage] = useState(null); + const [prompt, setPrompt] = useState<{ message: string, prompt: string, echo: boolean } | null>(null); + const [message, setMessage] = useState(null); const [error, setError] = useState(null); - const [errorVariant, setErrorVariant] = useState(null); + const [errorVariant, setErrorVariant] = useState<"danger" | "warning" | null>(null); const [value, setValue] = useState(""); function start(method) { @@ -67,7 +69,7 @@ const UnlockDialog = ({ proxy, host }) => { let did_prompt = false; - const onprompt = (event, message, prompt, def, echo, error) => { + const onprompt = (_event, message, prompt, def, echo, error) => { setBusy(false); setPrompt({ message: sudo_polish(message), @@ -134,7 +136,7 @@ const UnlockDialog = ({ proxy, host }) => { const validated = errorVariant == "danger" ? "error" : errorVariant; let title = null; - let title_icon = null; + let title_icon: null | "danger" = null; let body = null; let footer = null; @@ -230,7 +232,7 @@ const UnlockDialog = ({ proxy, host }) => { variant="medium" onClose={cancel} title={title} - titleIconVariant={title_icon} + titleIconVariant={title_icon || undefined} footer={footer}> {body} @@ -283,7 +285,7 @@ const LockDialog = ({ proxy, host }) => { ); }; -const SuperuserDialogs = ({ superuser_proxy, host, create_trigger }) => { +const SuperuserDialogs = ({ superuser_proxy, host = undefined, create_trigger }) => { const D = useDialogs(); useEvent(superuser_proxy, "changed", () => { @@ -338,7 +340,10 @@ export const SuperuserIndicator = ({ proxy, host }) => { }; export const SuperuserButton = () => { - const proxy = useObject(() => cockpit.dbus(null, { bus: "internal" }).proxy("cockpit.Superuser", "/superuser")); + const proxy = useObject( + () => cockpit.dbus(null, { bus: "internal" }).proxy("cockpit.Superuser", "/superuser"), + null, + []); const create_trigger = (unlocked, onclick) =>