diff --git a/pkg/lib/cockpit.d.ts b/pkg/lib/cockpit.d.ts index 01e539092138..0c8b771a6fda 100644 --- a/pkg/lib/cockpit.d.ts +++ b/pkg/lib/cockpit.d.ts @@ -365,4 +365,7 @@ declare module 'cockpit' { /* === Session ====================== */ function logout(reload: boolean, reason?: string): void; + + export let localStorage: Storage; + export let sessionStorage: Storage; } diff --git a/pkg/lib/credentials.ts b/pkg/lib/credentials.ts index 70be1df125b6..026672a730e2 100644 --- a/pkg/lib/credentials.ts +++ b/pkg/lib/credentials.ts @@ -46,7 +46,7 @@ export class KeyLoadError extends Error { } } -class Keys extends EventTarget { +export class Keys extends EventTarget { path: string | null = null; items: Record = { }; 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/credentials.jsx b/pkg/shell/credentials.tsx similarity index 90% rename from pkg/shell/credentials.jsx rename to pkg/shell/credentials.tsx index d17f2ab45540..8cc05699392b 100644 --- a/pkg/shell/credentials.jsx +++ b/pkg/shell/credentials.tsx @@ -39,120 +39,28 @@ import { ListingPanel } from 'cockpit-components-listing-panel.jsx'; import { ListingTable } from 'cockpit-components-table.jsx'; import { ModalError } from 'cockpit-components-inline-notification.jsx'; import { useEvent, useObject } from 'hooks'; +import { DialogResult } from "dialogs"; import "./credentials.scss"; const _ = cockpit.gettext; -export const CredentialsModal = ({ dialogResult }) => { - const keys = useObject(() => credentials.keys_instance(), null, []); - const [addNewKey, setAddNewKey] = useState(false); - const [dialogError, setDialogError] = useState(); - const [unlockKey, setUnlockKey] = useState(); - - useEvent(keys, "changed"); - - if (!keys) - return null; - - function onToggleKey(id, enable) { - const key = keys.items[id]; - - if (!key || !key.name) - return; - - /* Key needs to be loaded, show load UI */ - if (enable && !key.loaded) { - setUnlockKey(key.name); - /* Key needs to be unloaded, do that directly */ - } else if (!enable && key.loaded) { - keys.unload(key).catch(ex => setDialogError(ex.message)); - } - } - - return ( - <> - dialogResult.resolve()} - title={_("SSH keys")} - id="credentials-modal" - footer={} - > - - {dialogError && } - - {_("Use the following keys to authenticate against other systems")} - - - {addNewKey && setAddNewKey(false)} />} - { - const currentKey = keys.items[currentKeyId] || { name: 'test' }; - const tabRenderers = [ - { - data: { currentKey }, - name: _("Details"), - renderer: KeyDetails, - }, - { - data: { currentKey }, - name: _("Public key"), - renderer: PublicKey, - }, - { - data: { currentKey, keys, setDialogError }, - name: _("Password"), - renderer: KeyPassword, - }, - ]; - const expandedContent = ( - - ); - - return ({ - columns: [ - { - title: currentKey.name || currentKey.comment, - }, - { - title: onToggleKey(currentKeyId, value)} />, - } - ], - expandedContent, - props: { key: currentKey.fingerprint, 'data-name': currentKey.name || currentKey.comment, 'data-loaded': !!currentKey.loaded }, - }); - })} /> - - - {unlockKey && { setUnlockKey(undefined); setAddNewKey(false) }} />} - - ); -}; - -const AddNewKey = ({ keys, unlockKey, onClose }) => { +const AddNewKey = ({ + keys, + unlockKey, + onClose +} : { + keys: credentials.Keys, + unlockKey: (name: string) => void, + onClose: () => void +}) => { const [addNewKeyLoading, setAddNewKeyLoading] = useState(false); const [newKeyPath, setNewKeyPath] = useState(""); - const [newKeyPathError, setNewKeyPathError] = useState(); + const [newKeyPathError, setNewKeyPathError] = useState(""); const addCustomKey = () => { setAddNewKeyLoading(true); - keys.load(newKeyPath) + keys.load(newKeyPath, "") .then(onClose) .catch(ex => { if (!(ex instanceof credentials.KeyLoadError) || !ex.sent_password) @@ -186,7 +94,7 @@ const AddNewKey = ({ keys, unlockKey, onClose }) => { ); }; -const KeyDetails = ({ currentKey }) => { +const KeyDetails = ({ currentKey } : { currentKey: credentials.Key }) => { return ( @@ -205,7 +113,7 @@ const KeyDetails = ({ currentKey }) => { ); }; -const PublicKey = ({ currentKey }) => { +const PublicKey = ({ currentKey } : { currentKey: credentials.Key }) => { return ( {currentKey.data.trim()} @@ -213,9 +121,17 @@ const PublicKey = ({ currentKey }) => { ); }; -const KeyPassword = ({ currentKey, keys, setDialogError }) => { +const KeyPassword = ({ + currentKey, + keys, + setDialogError +} : { + currentKey: credentials.Key, + keys: credentials.Keys, + setDialogError: (msg: string | null) => void +}) => { const [confirmPassword, setConfirmPassword] = useState(''); - const [inProgress, setInProgress] = useState(undefined); + const [inProgress, setInProgress] = useState(undefined); const [newPassword, setNewPassword] = useState(''); const [oldPassword, setOldPassword] = useState(''); @@ -224,7 +140,7 @@ const KeyPassword = ({ currentKey, keys, setDialogError }) => { return; setInProgress(true); - setDialogError(); + setDialogError(null); if (oldPassword === undefined || newPassword === undefined || confirmPassword === undefined) setDialogError("Invalid password fields"); @@ -245,8 +161,7 @@ const KeyPassword = ({ currentKey, keys, setDialogError }) => { const changePasswordBtn = ( ); @@ -285,9 +200,17 @@ const KeyPassword = ({ currentKey, keys, setDialogError }) => { ); }; -const UnlockKey = ({ keyName, keys, onClose }) => { - const [password, setPassword] = useState(); - const [dialogError, setDialogError] = useState(); +const UnlockKey = ({ + keyName, + keys, + onClose +} : { + keyName: string, + keys: credentials.Keys, + onClose: () => void +}) => { + const [password, setPassword] = useState(""); + const [dialogError, setDialogError] = useState(""); function load_key() { if (!keyName) @@ -320,3 +243,108 @@ const UnlockKey = ({ keyName, keys, onClose }) => { ); }; + +export const CredentialsModal = ({ + dialogResult +} : { + dialogResult: DialogResult +}) => { + const keys = useObject(() => credentials.keys_instance(), null, []); + const [addNewKey, setAddNewKey] = useState(false); + const [dialogError, setDialogError] = useState(); + const [unlockKey, setUnlockKey] = useState(); + + useEvent(keys as unknown as cockpit.EventSource, "changed"); + + if (!keys) + return null; + + function onToggleKey(id: string, enable: boolean) { + const key = keys.items[id]; + + if (!key || !key.name) + return; + + /* Key needs to be loaded, show load UI */ + if (enable && !key.loaded) { + setUnlockKey(key.name); + /* Key needs to be unloaded, do that directly */ + } else if (!enable && key.loaded) { + keys.unload(key).catch(ex => setDialogError(ex.message)); + } + } + + return ( + <> + dialogResult.resolve()} + title={_("SSH keys")} + id="credentials-modal" + footer={} + > + + {dialogError && } + + {_("Use the following keys to authenticate against other systems")} + + + {addNewKey && setAddNewKey(false)} />} + { + const currentKey = keys.items[currentKeyId] || { name: 'test' }; + const tabRenderers = [ + { + data: { currentKey }, + name: _("Details"), + renderer: KeyDetails, + }, + { + data: { currentKey }, + name: _("Public key"), + renderer: PublicKey, + }, + { + data: { currentKey, keys, setDialogError }, + name: _("Password"), + renderer: KeyPassword, + }, + ]; + const expandedContent = ( + + ); + + return ({ + columns: [ + { + title: currentKey.name || currentKey.comment, + }, + { + title: onToggleKey(currentKeyId, value)} />, + } + ], + expandedContent, + props: { key: currentKey.fingerprint, 'data-name': currentKey.name || currentKey.comment, 'data-loaded': !!currentKey.loaded }, + }); + })} /> + + + {unlockKey && { setUnlockKey(undefined); setAddNewKey(false) }} />} + + ); +}; 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..6e2f8e3d0f04 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,27 @@ export const EarlyFailure = () => { }; const EarlyFailureReady = ({ - loading, + loading = false, title, paragraph, - reconnect, - troubleshoot, - onTroubleshoot, - watchdog_problem, - onReconnect + reconnect = false, + troubleshoot = false, + onTroubleshoot = () => {}, + onReconnect = () => {}, +} : { + loading?: boolean, + title: string, + paragraph: string, + reconnect?: boolean, + troubleshoot?: boolean, + onTroubleshoot?: () => void, + onReconnect?: () => void, }) => { return (
- @@ -98,10 +107,9 @@ export const Disconnected = ({ problem }) => { return ( { cockpit.sessionStorage.clear(); - window.location.reload(true); + window.location.reload(); }} paragraph={cockpit.message(problem)} /> ); 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..6dfef84ca63c 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) => 1} 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/machines/machines.d.ts b/pkg/shell/machines/machines.d.ts index 6a8b7bac0a36..064101991992 100644 --- a/pkg/shell/machines/machines.d.ts +++ b/pkg/shell/machines/machines.d.ts @@ -5,7 +5,7 @@ import { Manifests } from "../manifests"; export function generate_connection_string(user: string | null, port: string | null, addr: string) : string; export function split_connection_string (conn_to: string) : { address: string, user?: string, port?: number }; export function get_init_superuser_for_options (options: {[key: string]: string }) : string | null; -export function host_superuser_storage_key (host: string): string; +export function host_superuser_storage_key (host?: string): string | null; export interface Machine { key: string; @@ -14,6 +14,7 @@ export interface Machine { user?: string; port?: number; label: string; + color?: string; state: null | "failed" | "connecting" | "connected"; manifests?: Manifests; checksum?: string; @@ -32,6 +33,8 @@ export interface Machines extends EventSource { ready: boolean; lookup: (conection_string: string) => Machine; + list: Machines[]; + change: (key: string, props: Partial) => void; } interface Loader { diff --git a/pkg/shell/nav.tsx b/pkg/shell/nav.tsx index 790a720b5bfb..0685d5d4298d 100644 --- a/pkg/shell/nav.tsx +++ b/pkg/shell/nav.tsx @@ -77,9 +77,7 @@ interface NavKeyword { goto: string | null; } -interface NavItem extends ManifestItem { - keyword: NavKeyword; -} +type NavItem = T & { keyword: NavKeyword } interface ItemGroup { name: string; @@ -90,13 +88,13 @@ interface ItemGroup { } | undefined; } -interface CockpitNavProps { - groups: ItemGroup[]; +interface CockpitNavProps { + groups: ItemGroup[]; selector: string; current: string; - filtering: (item: ManifestItem, term: string) => NavItem | null; - sorting: (a: NavItem, b: NavItem) => number; - item_render: (item: NavItem, term: string) => React.ReactNode; + filtering: (item: T, term: string) => NavItem | null; + sorting: (a: NavItem, b: NavItem) => number; + item_render: (item: NavItem, term: string) => React.ReactNode; jump: (loc: Partial) => void; } @@ -105,11 +103,11 @@ interface CockpitNavState { current: string; } -export class CockpitNav extends React.Component { - props: CockpitNavProps; +export class CockpitNav extends React.Component { + props: CockpitNavProps; state: CockpitNavState; - constructor(props : CockpitNavProps) { + constructor(props : CockpitNavProps) { super(props); this.state = { @@ -172,7 +170,7 @@ export class CockpitNav extends React.Component { document.getElementById(sel)?.addEventListener("keyup", navigate_apps); } - static getDerivedStateFromProps(nextProps: CockpitNavProps, prevState: CockpitNavState) { + static getDerivedStateFromProps(nextProps: CockpitNavProps, prevState: CockpitNavState) { if (nextProps.current !== prevState.current) return { search: "", @@ -186,7 +184,7 @@ export class CockpitNav extends React.Component { } render() { - const groups: ItemGroup[] = []; + const groups: ItemGroup>[] = []; const term = this.state.search.toLowerCase(); this.props.groups.forEach(g => { const new_items = g.items.map(i => this.props.filtering(i, term)).filter(i => !!i); @@ -319,7 +317,7 @@ export const PageNav = ({ state } : { state: ShellState }) => { cockpit.assert(current_machine_manifest_items && current_manifest_item); // Filtering of navigation by term - function keyword_filter(item: ManifestItem, term: string): NavItem | null { + function keyword_filter(item: ManifestItem, term: string): NavItem | null { function keyword_relevance(current_best: NavKeyword, item: ManifestKeyword) { const translate = item.translate || false; const weight = item.weight || 0; @@ -353,7 +351,7 @@ export const PageNav = ({ state } : { state: ShellState }) => { return current_best; } - const new_item: NavItem = Object.assign({ keyword: { keyword: "", score: -1, goto: null } }, item); + const new_item: NavItem = Object.assign({ keyword: { keyword: "", score: -1, goto: null } }, item); if (!term) return new_item; const best_keyword = new_item.keywords.reduce(keyword_relevance, { keyword: "", score: -1, goto: null }); @@ -365,7 +363,7 @@ export const PageNav = ({ state } : { state: ShellState }) => { } // Rendering of separate navigation menu items - function nav_item(item: NavItem, term: string) { + function nav_item(item: NavItem, term: string) { const active = current_manifest_item?.path === item.path; // Parse path 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..5bdc78e5b165 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; @@ -158,7 +160,6 @@ const UnlockDialog = ({ proxy, host }) => { { variant="medium" onClose={cancel} title={title} - titleIconVariant={title_icon} + titleIconVariant={title_icon || undefined} footer={footer}> {body} @@ -283,7 +284,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 +339,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) =>