File cockpit-docker-devel-16.obscpio of Package cockpit-docker

07070100000000000041ED000000000000000000000002662A077800000000000000000000000000000000000000000000002000000000cockpit-docker-devel-16/pkg/lib07070100000001000081A4000000000000000000000001662A0778000000F6000000000000000000000000000000000000002700000000cockpit-docker-devel-16/pkg/lib/README# Cockpit shared components

This directory contains React components, JavaScript modules, esbuild
plugins, and build tools which are shared between all Cockpit projects.
External projects usually clone this directory into their own source tree.
07070100000002000081A4000000000000000000000001662A0778000002A0000000000000000000000000000000000000003700000000cockpit-docker-devel-16/pkg/lib/_global-variables.scss@import "@patternfly/patternfly/sass-utilities/all";

/*
 * PatternFly 4 adapting the lists too early.
 * When PF4 has a breakpoint of 768px width, it's actually 1108 for us, as the sidebar is 340px.
 * (This does use the intended content area, but there's a mismatch between content and browser width as we use iframes.)
 * So redefine grid breakpoints
 */
$pf-v5-global--breakpoint--xs: 0 !default;
// Do not override the sm breakpoint as for width < 768px the left nav is hidden
$pf-v5-global--breakpoint--md: 428px !default;
$pf-v5-global--breakpoint--lg: 652px !default;
$pf-v5-global--breakpoint--xl: 860px !default;
$pf-v5-global--breakpoint--2xl: 1110px !default;
07070100000003000081A4000000000000000000000001662A077800000CE0000000000000000000000000000000000000004400000000cockpit-docker-devel-16/pkg/lib/cockpit-components-context-menu.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
 */

import React from "react";
import PropTypes from "prop-types";

import { Menu, MenuContent } from "@patternfly/react-core/dist/esm/components/Menu";

import "context-menu.scss";

/*
 * A context menu component
 *
 * It requires two properties:
 *  - parentId, area in which it listens to left button click
 *  - children, a MenuList to be rendered in the context menu
 */
export const ContextMenu = ({ parentId, children }) => {
    const [visible, setVisible] = React.useState(false);
    const [event, setEvent] = React.useState(null);
    const root = React.useRef(null);

    React.useEffect(() => {
        const _handleContextMenu = (event) => {
            event.preventDefault();

            setVisible(true);
            setEvent(event);
        };

        const _handleClick = (event) => {
            if (event && event.button === 0) {
                const wasOutside = !(event.target.contains === root.current);

                if (wasOutside)
                    setVisible(false);
            }
        };

        const parent = document.getElementById(parentId);
        parent.addEventListener('contextmenu', _handleContextMenu);
        document.addEventListener('click', _handleClick);

        return () => {
            parent.removeEventListener('contextmenu', _handleContextMenu);
            document.removeEventListener('click', _handleClick);
        };
    }, [parentId]);

    React.useEffect(() => {
        if (!event)
            return;

        const clickX = event.clientX;
        const clickY = event.clientY;
        const screenW = window.innerWidth;
        const screenH = window.innerHeight;
        const rootW = root.current.offsetWidth;
        const rootH = root.current.offsetHeight;

        const right = (screenW - clickX) > rootW;
        const left = !right;
        const top = (screenH - clickY) > rootH;
        const bottom = !top;

        if (right) {
            root.current.style.left = `${clickX + 5}px`;
        }

        if (left) {
            root.current.style.left = `${clickX - rootW - 5}px`;
        }

        if (top) {
            root.current.style.top = `${clickY + 5}px`;
        }

        if (bottom) {
            root.current.style.top = `${clickY - rootH - 5}px`;
        }
    }, [event]);

    return visible &&
        <Menu ref={root} className="contextMenu">
            <MenuContent ref={root}>
                {children}
            </MenuContent>
        </Menu>;
};

ContextMenu.propTypes = {
    parentId: PropTypes.string.isRequired,
    children: PropTypes.any
};
07070100000004000081A4000000000000000000000001662A07780000382F000000000000000000000000000000000000003E00000000cockpit-docker-devel-16/pkg/lib/cockpit-components-dialog.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
 */

import cockpit from "cockpit";
import React from "react";
import { createRoot } from "react-dom/client";
import PropTypes from "prop-types";
import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js";
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { Modal } from "@patternfly/react-core/dist/esm/components/Modal/index.js";
import { Popover } from "@patternfly/react-core/dist/esm/components/Popover/index.js";
import { Stack, StackItem } from "@patternfly/react-core/dist/esm/layouts/Stack/index.js";
import { HelpIcon, ExternalLinkAltIcon } from '@patternfly/react-icons';

import "cockpit-components-dialog.scss";

const _ = cockpit.gettext;

/*
 * React template for a Cockpit dialog footer
 * It can wait for an action to complete,
 * has a 'Cancel' button and an action button (defaults to 'OK')
 * Expected props:
 *  - cancel_clicked optional
 *     Callback called when the dialog is canceled
 *  - cancel_button optional, defaults to 'Cancel' text styled as a link
 *  - list of actions, each an object with:
 *      - clicked
 *         Callback function that is expected to return a promise.
 *         parameter: callback to set the progress text
 *      - caption optional, defaults to 'Ok'
 *      - disabled optional, defaults to false
 *      - style defaults to 'secondary', other options: 'primary', 'danger'
 *  - idle_message optional, always show this message on the last row when idle
 *  - dialog_done optional, callback when dialog is finished (param true if success, false on cancel)
 *  - set_error: required, callback to set/clear error message from actions
 */
class DialogFooter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            action_in_progress: false,
            action_in_progress_promise: null,
            action_progress_message: '',
            action_progress_cancel: null,
            action_canceled: false,
        };
        this.update_progress = this.update_progress.bind(this);
        this.cancel_click = this.cancel_click.bind(this);
    }

    update_progress(msg, cancel) {
        this.setState({ action_progress_message: msg, action_progress_cancel: cancel });
    }

    action_click(handler, caption, e) {
        this.setState({
            action_progress_message: '',
            action_in_progress: true,
            action_caption_in_progress: caption,
            action_canceled: false,
        });

        const p = handler(this.update_progress)
                .then(() => {
                    this.props.set_error(null);
                    this.setState({ action_in_progress: false });
                    if (this.props.dialog_done)
                        this.props.dialog_done(true);
                })
                .catch(error => {
                    if (this.state.action_canceled) {
                        if (this.props.dialog_done)
                            this.props.dialog_done(false);
                    } else {
                        this.props.set_error(error);
                        this.setState({ action_in_progress: false });
                    }
                    /* Always log global dialog errors for easier debugging */
                    if (error)
                        console.warn(error.message || error.toString());
                });

        if (p.progress)
            p.progress(this.update_progress);

        this.setState({ action_in_progress_promise: p });
        if (e)
            e.stopPropagation();
    }

    cancel_click(e) {
        this.setState({ action_canceled: true });

        if (this.props.cancel_clicked)
            this.props.cancel_clicked();

        // an action might be in progress, let that handler decide what to do if they added a cancel function
        if (this.state.action_in_progress && this.state.action_progress_cancel) {
            this.state.action_progress_cancel();
            return;
        }
        if (this.state.action_in_progress && 'cancel' in this.state.action_in_progress_promise) {
            this.state.action_in_progress_promise.cancel();
            return;
        }

        if (this.props.dialog_done)
            this.props.dialog_done(false);
        if (e)
            e.stopPropagation();
    }

    render() {
        const cancel_text = this.props?.cancel_button?.text ?? _("Cancel");
        const cancel_variant = this.props?.cancel_button?.variant ?? "link";

        // If an action is in progress, show the spinner with its message and disable all actions.
        // Cancel is only enabled when the action promise has a cancel method, or we get one
        // via the progress reporting.

        let wait_element;
        let actions_disabled;
        let cancel_disabled;
        if (this.state.action_in_progress) {
            actions_disabled = true;
            if (!(this.state.action_in_progress_promise && this.state.action_in_progress_promise.cancel) && !this.state.action_progress_cancel)
                cancel_disabled = true;
            wait_element = <div className="dialog-wait-ct">
                <span>{ this.state.action_progress_message }</span>
            </div>;
        } else if (this.props.idle_message) {
            wait_element = <div className="dialog-wait-ct">
                { this.props.idle_message }
            </div>;
        }

        const action_buttons = this.props.actions.map(action => {
            let caption;
            if ('caption' in action)
                caption = action.caption;
            else
                caption = _("Ok");

            let variant = action.style || "secondary";
            if (variant == "primary" && action.danger)
                variant = "danger";

            return (<Button
                key={ caption }
                className="apply"
                variant={ variant }
                isLoading={ this.state.action_in_progress && this.state.action_caption_in_progress == caption }
                isDanger={ action.danger }
                onClick={ this.action_click.bind(this, action.clicked, caption) }
                isDisabled={ actions_disabled || action.disabled }
            >{ caption }</Button>
            );
        });

        return (
            <>
                { this.props.extra_element }
                { action_buttons }
                <Button variant={cancel_variant} className="cancel" onClick={this.cancel_click} isDisabled={cancel_disabled}>{ cancel_text }</Button>
                { wait_element }
            </>
        );
    }
}

DialogFooter.propTypes = {
    cancel_clicked: PropTypes.func,
    cancel_button: PropTypes.object,
    actions: PropTypes.array.isRequired,
    dialog_done: PropTypes.func,
    set_error: PropTypes.func.isRequired,
};

/*
 * React template for a Cockpit dialog
 * The primary action button is disabled while its action is in progress (waiting for promise)
 * Removes focus on other elements on showing
 * Expected props:
 *  - title (string)
 *  - body (react element, top element should be of class modal-body)
 *      It is recommended for information gathering dialogs to pass references
 *      to the input components to the controller. That way, the controller can
 *      extract all necessary information (e.g. for input validation) when an
 *      action is triggered.
 *  - static_error optional, always show this error after the body element
 *  - footer (react element, top element should be of class modal-footer)
 *  - id optional, id that is assigned to the top level dialog node, but not the backdrop
 *  - variant: See PF4 Modal component's 'variant' property
 *  - titleIconVariant: See PF4 Modal component's 'titleIconVariant' property
 *  - showClose optional, specifies if 'X' button for closing the dialog is present
 */
class Dialog extends React.Component {
    componentDidMount() {
        // For the scenario that cockpit-storage is used inside anaconda Web UI
        // We need to know if there is an open dialog in order to create the backdrop effect
        // on the parent window
        window.sessionStorage.setItem("cockpit_has_modal", true);

        // if we used a button to open this, make sure it's not focused anymore
        if (document.activeElement)
            document.activeElement.blur();
    }

    componentWillUnmount() {
        window.sessionStorage.setItem("cockpit_has_modal", false);
    }

    render() {
        let help = null;
        let footer = null;
        if (this.props.helpLink)
            footer = <a href={this.props.helpLink} target="_blank" rel="noopener noreferrer">{_("Learn more")} <ExternalLinkAltIcon /></a>;

        if (this.props.helpMessage)
            help = <Popover
                  bodyContent={this.props.helpMessage}
                  footerContent={footer}
            >
                <Button variant="plain" aria-label={_("Learn more")}>
                    <HelpIcon />
                </Button>
            </Popover>;

        const error = this.props.error || this.props.static_error;
        const error_alert = error && <Alert variant='danger' isInline title={error} />;

        return (
            <Modal position="top" variant={this.props.variant || "medium"}
                   titleIconVariant={this.props.titleIconVariant}
                   onEscapePress={() => undefined}
                   showClose={!!this.props.showClose}
                   id={this.props.id}
                   isOpen
                   help={help}
                   footer={this.props.footer} title={this.props.title}>
                <Stack hasGutter>
                    { error_alert }
                    <StackItem>
                        { this.props.body }
                    </StackItem>
                </Stack>
            </Modal>
        );
    }
}
Dialog.propTypes = {
    // TODO: fix following by refactoring the logic showing modal dialog (recently show_modal_dialog())
    title: PropTypes.string, // is effectively required, but show_modal_dialog() provides initially no props and resets them later.
    body: PropTypes.element, // is effectively required, see above
    static_error: PropTypes.string,
    error: PropTypes.string,
    footer: PropTypes.element, // is effectively required, see above
    id: PropTypes.string,
    showClose: PropTypes.bool,
};

/* Create and show a dialog
 * For this, create a containing DOM node at the body level
 * The returned object has the following methods:
 *     - setFooterProps replace the current footerProps and render
 *     - setProps       replace the current props and render
 *     - render         render again using the stored props
 * The DOM node and React metadata are freed once the dialog has closed
 */
export function show_modal_dialog(props, footerProps) {
    const dialogName = 'cockpit_modal_dialog';
    // don't allow nested dialogs, just close whatever is open
    const curElement = document.getElementById(dialogName);
    let root;
    if (curElement) {
        root = createRoot(curElement);
        root.unmount();
        curElement.remove();
    }
    // create an element to render into
    const rootElement = document.createElement("div");
    root = createRoot(rootElement);
    rootElement.id = dialogName;
    document.body.appendChild(rootElement);

    // register our own on-close callback
    let origCallback;
    const closeCallback = function() {
        if (origCallback)
            origCallback.apply(this, arguments);
        root.unmount();
        rootElement.remove();
    };

    const dialogObj = { };
    let error = null;
    dialogObj.props = props;
    dialogObj.footerProps = null;
    dialogObj.render = function() {
        dialogObj.props.footer = <DialogFooter {...dialogObj.footerProps} />;
        // Don't render if we are no longer part of the document.
        // This would be mostly harmless except that it will remove
        // the input focus from whatever element has it, which is
        // unpleasant and also disrupts the tests.
        if (rootElement.offsetParent)
            root.render(<Dialog {...dialogObj.props} error={error} />);
    };
    function updateFooterAndRender() {
        if (dialogObj.props === null || dialogObj.props === undefined)
            dialogObj.props = { };
        dialogObj.props.footer = <DialogFooter {...dialogObj.footerProps} />;
        dialogObj.render();
    }
    dialogObj.setFooterProps = function(footerProps) {
        dialogObj.footerProps = footerProps;
        if (dialogObj.footerProps.dialog_done != closeCallback) {
            origCallback = dialogObj.footerProps.dialog_done;
            dialogObj.footerProps.dialog_done = closeCallback;
        }
        dialogObj.footerProps.set_error = e => {
            error = typeof e === 'object' && e !== null ? (e.message || e.toString()) : e;
            dialogObj.render();
        };
        updateFooterAndRender();
    };
    dialogObj.setProps = function(props) {
        dialogObj.props = props;
        updateFooterAndRender();
    };
    dialogObj.setFooterProps(footerProps);
    dialogObj.setProps(props);

    // now actually render
    dialogObj.render();

    return dialogObj;
}

export function apply_modal_dialog(event) {
    const dialog = event.target?.closest("[role=dialog]");
    const button = dialog?.querySelector("button.apply");

    if (button) {
        const event = new MouseEvent('click', {
            view: window,
            bubbles: true,
            cancelable: true,
            button: 0
        });
        button.dispatchEvent(event);
    }

    event.preventDefault();
    return false;
}
07070100000005000081A4000000000000000000000001662A0778000000B2000000000000000000000000000000000000003F00000000cockpit-docker-devel-16/pkg/lib/cockpit-components-dialog.scss.pf-v5-c-modal-box__body .scroll {
  max-block-size: calc(75vh - 10rem);
  overflow: auto;
}

.dialog-wait-ct-spinner {
  margin-inline-start: var(--pf-v5-global--spacer--sm);
}
07070100000006000081A4000000000000000000000001662A077800000BE3000000000000000000000000000000000000004000000000cockpit-docker-devel-16/pkg/lib/cockpit-components-dropdown.jsx/*
 * 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 <http://www.gnu.org/licenses/>.
 */

import React, { useState } from 'react';
import PropTypes from "prop-types";

import { MenuToggle } from "@patternfly/react-core/dist/esm/components/MenuToggle";
import { Dropdown, DropdownList } from "@patternfly/react-core/dist/esm/components/Dropdown";

import { EllipsisVIcon } from '@patternfly/react-icons';

/*
 * A dropdown with a Kebab button, commonly used in Cockpit pages provided as
 * component so not all pages have to re-invent the wheel.
 *
 * isOpen/setIsOpen are optional -- you need to handle the state externally if you render the KebabDropdown in an
 * "unstable" environment such as a dynamic list. When not given, the dropdown will manage its own state.
 *
 * This component expects a list of (non-deprecated!) DropdownItem's, if you
 * require a separator between DropdownItem's use PatternFly's Divivder
 * component.
 */
export const KebabDropdown = ({ dropdownItems, position, isDisabled, toggleButtonId, isOpen, setIsOpen, props }) => {
    const [isKebabOpenInternal, setKebabOpenInternal] = useState(false);
    const isKebabOpen = isOpen ?? isKebabOpenInternal;
    const setKebabOpen = setIsOpen ?? setKebabOpenInternal;

    return (
        <Dropdown
            {...props}
            onOpenChange={isOpen => setKebabOpen(isOpen)}
            onSelect={() => setKebabOpen(false)}
            toggle={(toggleRef) => (
                <MenuToggle
                    id={toggleButtonId}
                    isDisabled={isDisabled}
                    ref={toggleRef}
                    variant="plain"
                    onClick={() => setKebabOpen(!isKebabOpen)}
                    isExpanded={isKebabOpen}
                >
                    <EllipsisVIcon />
                </MenuToggle>
            )}
            isOpen={isKebabOpen}
            popperProps={{ position }}
        >
            <DropdownList>
                {dropdownItems}
            </DropdownList>
        </Dropdown>
    );
};

KebabDropdown.propTypes = {
    dropdownItems: PropTypes.array.isRequired,
    isDisabled: PropTypes.bool,
    toggleButtonId: PropTypes.string,
    position: PropTypes.oneOf(['right', 'left', 'center', 'start', 'end']),
    isOpen: PropTypes.bool,
    setIsOpen: PropTypes.func,
};

KebabDropdown.defaultProps = {
    isDisabled: false,
    position: "end",
};
07070100000007000081A4000000000000000000000001662A077800001A1C000000000000000000000000000000000000004400000000cockpit-docker-devel-16/pkg/lib/cockpit-components-dynamic-list.jsximport React from 'react';
import PropTypes from 'prop-types';
import { Button } from "@patternfly/react-core/dist/esm/components/Button";
import { EmptyState, EmptyStateBody } from "@patternfly/react-core/dist/esm/components/EmptyState";
import { FormFieldGroup, FormFieldGroupHeader } from "@patternfly/react-core/dist/esm/components/Form";
import { HelperText, HelperTextItem } from "@patternfly/react-core/dist/esm/components/HelperText";

import './cockpit-components-dynamic-list.scss';

/* Dynamic list with a variable number of rows. Each row is a custom component, usually an input field(s).
 *
 * Props:
 *   - emptyStateString
 *   - onChange
 *   - id
 *   - itemcomponent
 *   - formclass (optional)
 *   - options (optional)
 *   - onValidationChange: A handler function which updates the parent's component's validation object.
 *                         Its parameter is an array the same structure as 'validationFailed'.
 *   - validationFailed: An array where each item represents a validation error of the corresponding row component index.
 *                       A row is strictly mapped to an item of the array by its index.
 *     Example: Let's have a dynamic form, where each row consists of 2 fields: name and email. Then a validation array of
 *              these rows would look like this:
 *     [
 *       { name: "Name must not be empty }, // first row
 *       { }, // second row
 *       { name: "Name cannot containt number", email: "Email must contain '@'" } // third row
 *     ]
 */
export class DynamicListForm extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            list: [],
        };
        this.keyCounter = 0;
        this.removeItem = this.removeItem.bind(this);
        this.addItem = this.addItem.bind(this);
        this.onItemChange = this.onItemChange.bind(this);
    }

    removeItem(idx) {
        const validationFailedDelta = this.props.validationFailed ? [...this.props.validationFailed] : [];
        // We also need to remove any error messages which the item (row) may have contained
        delete validationFailedDelta[idx];
        this.props.onValidationChange?.(validationFailedDelta);

        this.setState(state => {
            const items = [...state.list];
            // keep the list structure, otherwise all the indexes shift and the ID/key mapping gets broken
            delete items[idx];

            return { list: items };
        }, () => this.props.onChange(this.state.list));
    }

    addItem() {
        this.setState(state => {
            return { list: [...state.list, { key: this.keyCounter++, ...this.props.default }] };
        }, () => this.props.onChange(this.state.list));
    }

    onItemChange(idx, field, value) {
        this.setState(state => {
            const items = [...state.list];
            items[idx][field] = value || null;
            return { list: items };
        }, () => this.props.onChange(this.state.list));
    }

    render () {
        const { id, label, actionLabel, formclass, emptyStateString, helperText, validationFailed, onValidationChange } = this.props;
        const dialogValues = this.state;
        return (
            <FormFieldGroup header={
                <FormFieldGroupHeader
                    titleText={{ text: label }}
                    actions={<Button variant="secondary" className="btn-add" onClick={this.addItem}>{actionLabel}</Button>}
                />
            } className={"dynamic-form-group " + formclass}>
                {
                    dialogValues.list.some(item => item !== undefined)
                        ? <>
                            {dialogValues.list.map((item, idx) => {
                                if (item === undefined)
                                    return null;

                                return React.cloneElement(this.props.itemcomponent, {
                                    idx,
                                    item,
                                    id: id + "-" + idx,
                                    key: idx,
                                    onChange: this.onItemChange,
                                    removeitem: this.removeItem,
                                    additem: this.addItem,
                                    options: this.props.options,
                                    validationFailed: validationFailed && validationFailed[idx],
                                    onValidationChange: value => {
                                        // Dynamic list consists of multiple rows. Therefore validationFailed object is presented as an array where each item represents a row
                                        // Each row/item then consists of key-value pairs, which represent a field name and it's validation error
                                        const delta = validationFailed ? [...validationFailed] : [];
                                        // Update validation of only a single row
                                        delta[idx] = value;

                                        // If a row doesn't contain any fields with errors anymore, we delete the item of the array
                                        // Deleting an item of an array replaces an item with an "empty item".
                                        // This guarantees that an array of validation errors maps to the correct rows
                                        if (Object.keys(delta[idx]).length == 0)
                                            delete delta[idx];

                                        onValidationChange?.(delta);
                                    },
                                });
                            })
                            }
                            {helperText &&
                            <HelperText>
                                <HelperTextItem>{helperText}</HelperTextItem>
                            </HelperText>
                            }
                        </>
                        : <EmptyState>
                            <EmptyStateBody>
                                {emptyStateString}
                            </EmptyStateBody>
                        </EmptyState>
                }
            </FormFieldGroup>
        );
    }
}

DynamicListForm.propTypes = {
    emptyStateString: PropTypes.string.isRequired,
    onChange: PropTypes.func.isRequired,
    id: PropTypes.string.isRequired,
    itemcomponent: PropTypes.object.isRequired,
    formclass: PropTypes.string,
    options: PropTypes.object,
    validationFailed: PropTypes.array,
    onValidationChange: PropTypes.func,
};
07070100000008000081A4000000000000000000000001662A077800000599000000000000000000000000000000000000004500000000cockpit-docker-devel-16/pkg/lib/cockpit-components-dynamic-list.scss@import "global-variables";

.dynamic-form-group {
    .pf-v5-c-empty-state {
        padding: 0;
    }

    .pf-v5-c-form__label {
        // Don't allow labels to wrap
        white-space: nowrap;
    }

    .remove-button-group {
        // Move 'Remove' button the the end of the row
        grid-column: -1;
        // Move 'Remove' button to the bottom of the line so as to align with the other form fields
        display: flex;
        align-items: flex-end;
    }

    // Set check to the same height as input widgets and vertically align
    .pf-v5-c-form__group-control > .pf-v5-c-check {
        // Set height to the same as inputs
        // Font height is font size * line height (1rem * 1.5)
        // Widgets have 5px padding, 1px border (top & bottom): (5 + 1) * 2 = 12
        // This all equals to 36px
        block-size: calc(var(--pf-v5-global--FontSize--md) * var(--pf-v5-global--LineHeight--md) + 12px);
        align-content: center;
    }

    // We use FormFieldGroup PF component for the nested look and for ability to add buttons to the header
    // However we want to save space and not add indent to the left so we need to override it
    .pf-v5-c-form__field-group-body {
        // Stretch content fully
        --pf-v5-c-form__field-group-body--GridColumn: 1 / -1;
        // Reduce padding at the top
        --pf-v5-c-form__field-group-body--PaddingTop: var(--pf-v5-global--spacer--xs);
    }
}
07070100000009000081A4000000000000000000000001662A077800000048000000000000000000000000000000000000004300000000cockpit-docker-devel-16/pkg/lib/cockpit-components-empty-state.css.pf-v5-c-empty-state .pf-v5-c-button.pf-m-primary.slim {
  margin: 0;
}
0707010000000A000081A4000000000000000000000001662A077800000A81000000000000000000000000000000000000004300000000cockpit-docker-devel-16/pkg/lib/cockpit-components-empty-state.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
 */

import React from "react";
import PropTypes from 'prop-types';
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { EmptyStateActions, EmptyState, EmptyStateBody, EmptyStateFooter, EmptyStateHeader, EmptyStateIcon, EmptyStateVariant } from "@patternfly/react-core/dist/esm/components/EmptyState/index.js";
import { Spinner } from "@patternfly/react-core/dist/esm/components/Spinner/index.js";
import "./cockpit-components-empty-state.css";

export const EmptyStatePanel = ({ title, paragraph, loading, icon, action, isActionInProgress, onAction, secondary, headingLevel }) => {
    const slimType = title || paragraph ? "" : "slim";
    return (
        <EmptyState variant={EmptyStateVariant.full}>
            <EmptyStateHeader titleText={title} headingLevel={headingLevel} icon={(loading || icon) && <EmptyStateIcon icon={loading ? Spinner : icon} />} />
            <EmptyStateBody>
                {paragraph}
            </EmptyStateBody>
            {(action || secondary) && <EmptyStateFooter>
                { action && (typeof action == "string"
                    ? <Button variant="primary" className={slimType}
                          isLoading={isActionInProgress}
                          isDisabled={isActionInProgress}
                          onClick={onAction}>{action}</Button>
                    : action)}
                { secondary && <EmptyStateActions>{secondary}</EmptyStateActions> }
            </EmptyStateFooter>}
        </EmptyState>
    );
};

EmptyStatePanel.propTypes = {
    loading: PropTypes.bool,
    icon: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.func]),
    title: PropTypes.string,
    paragraph: PropTypes.node,
    action: PropTypes.node,
    isActionInProgress: PropTypes.bool,
    onAction: PropTypes.func,
    secondary: PropTypes.node,
};

EmptyStatePanel.defaultProps = {
    headingLevel: "h1",
    isActionInProgress: false,
};
0707010000000B000081A4000000000000000000000001662A077800001D0B000000000000000000000000000000000000004900000000cockpit-docker-devel-16/pkg/lib/cockpit-components-file-autocomplete.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
 */

import cockpit from "cockpit";
import React from "react";
import { Select, SelectOption } from "@patternfly/react-core/dist/esm/deprecated/components/Select/index.js";
import PropTypes from "prop-types";
import { debounce } from 'throttle-debounce';

const _ = cockpit.gettext;

export class FileAutoComplete extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            directory: '', // The current directory we list files/dirs from
            displayFiles: [],
            isOpen: false,
            value: this.props.value || null,
        };

        this.typeaheadInputValue = "";
        this.allowFilesUpdate = true;
        this.updateFiles = this.updateFiles.bind(this);
        this.finishUpdate = this.finishUpdate.bind(this);
        this.onToggle = this.onToggle.bind(this);
        this.clearSelection = this.clearSelection.bind(this);
        this.onCreateOption = this.onCreateOption.bind(this);

        this.onPathChange = (value) => {
            if (!value) {
                this.clearSelection();
                return;
            }

            this.typeaheadInputValue = value;

            const cb = (dirPath) => this.updateFiles(dirPath == '' ? '/' : dirPath);

            let path = value;
            if (value.lastIndexOf('/') == value.length - 1)
                path = value.slice(0, value.length - 1);

            const match = this.state.displayFiles
                    .find(entry => (entry.type == 'directory' && entry.path == path + '/') || (entry.type == 'file' && entry.path == path));

            if (match) {
                // If match file path is a prefix of another file, do not update current directory,
                // since we cannot tell file/directory user wants to select
                // https://bugzilla.redhat.com/show_bug.cgi?id=2097662
                const isPrefix = this.state.displayFiles.filter(entry => entry.path.startsWith(value)).length > 1;
                // If the inserted string corresponds to a directory listed in the results
                // update the current directory and refetch results
                if (match.type == 'directory' && !isPrefix)
                    cb(match.path);
                else
                    this.setState({ value: match.path });
            } else {
                // If the inserted string's parent directory is not matching the `directory`
                // in the state object we need to update the parent directory and recreate the displayFiles
                const parentDir = value.slice(0, value.lastIndexOf('/'));

                if (parentDir + '/' != this.state.directory) {
                    return this.updateFiles(parentDir + '/');
                }
            }
        };
        this.debouncedChange = debounce(300, this.onPathChange);
        this.onPathChange(this.state.value);
    }

    componentWillUnmount() {
        this.allowFilesUpdate = false;
    }

    onCreateOption(newValue) {
        this.setState(prevState => ({
            displayFiles: [...prevState.displayFiles, { type: "file", path: newValue }]
        }));
    }

    updateFiles(path) {
        if (this.state.directory == path)
            return;

        const channel = cockpit.channel({
            payload: "fslist1",
            path,
            superuser: this.props.superuser,
            watch: false,
        });
        const results = [];

        channel.addEventListener("ready", () => {
            this.finishUpdate(results, null, path);
        });

        channel.addEventListener("close", (ev, data) => {
            this.finishUpdate(results, data.message, path);
        });

        channel.addEventListener("message", (ev, data) => {
            const item = JSON.parse(data);
            if (item && item.path && item.event == 'present') {
                item.path = item.path + (item.type == 'directory' ? '/' : '');
                results.push(item);
            }
        });
    }

    finishUpdate(results, error, directory) {
        if (!this.allowFilesUpdate)
            return;
        results = results.sort((a, b) => a.path.localeCompare(b.path, { sensitivity: 'base' }));

        const listItems = results.map(file => ({
            type: file.type,
            path: (directory == '' ? '/' : directory) + file.path
        }));

        if (directory) {
            listItems.unshift({
                type: "directory",
                path: directory
            });
        }

        if (error || !this.state.value)
            this.props.onChange('', error);

        if (!error)
            this.setState({ displayFiles: listItems, directory });
        this.setState({
            error,
        });
    }

    onToggle(_, isOpen) {
        this.setState({ isOpen });
    }

    clearSelection() {
        this.typeaheadInputValue = "";
        this.updateFiles("/");
        this.setState({
            value: null,
            isOpen: false
        });
        this.props.onChange('', null);
    }

    render() {
        const placeholder = this.props.placeholder || _("Path to file");

        const selectOptions = this.state.displayFiles
                .map(option => <SelectOption key={option.path}
                                             className={option.type}
                                             value={option.path} />);
        return (
            <Select
                variant="typeahead"
                id={this.props.id}
                isInputValuePersisted
                onTypeaheadInputChanged={this.debouncedChange}
                placeholderText={placeholder}
                noResultsFoundText={this.state.error || _("No such file or directory")}
                selections={this.state.value}
                onSelect={(_, value) => {
                    this.setState({ value, isOpen: false });
                    this.debouncedChange(value);
                    this.props.onChange(value || '', null);
                }}
                onToggle={this.onToggle}
                onClear={this.clearSelection}
                isOpen={this.state.isOpen}
                isCreatable={this.props.isOptionCreatable}
                createText={_("Create")}
                onCreateOption={this.onCreateOption}
                menuAppendTo="parent">
                {selectOptions}
            </Select>
        );
    }
}
FileAutoComplete.propTypes = {
    id: PropTypes.string,
    placeholder: PropTypes.string,
    superuser: PropTypes.string,
    isOptionCreatable: PropTypes.bool,
    onChange: PropTypes.func,
    value: PropTypes.string,
};
FileAutoComplete.defaultProps = {
    isOptionCreatable: false,
    onChange: () => '',
};
0707010000000C000081A4000000000000000000000001662A0778000020BE000000000000000000000000000000000000004900000000cockpit-docker-devel-16/pkg/lib/cockpit-components-firewalld-request.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
 */
import React, { useState } from 'react';
import { Alert, AlertActionCloseButton, AlertActionLink } from "@patternfly/react-core/dist/esm/components/Alert/index.js";
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { Select, SelectOption } from "@patternfly/react-core/dist/esm/deprecated/components/Select/index.js";
import { Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem } from "@patternfly/react-core/dist/esm/components/Toolbar/index.js";
import { PageSection } from "@patternfly/react-core/dist/esm/components/Page/index.js";

import cockpit from 'cockpit';
import './cockpit-components-firewalld-request.scss';

const _ = cockpit.gettext;
const firewalld = cockpit.dbus('org.fedoraproject.FirewallD1', { superuser: "try" });

function debug() {
    if (window.debugging == "all" || window.debugging?.includes("firewall"))
        console.debug.apply(console, arguments);
}

/* React component for an info alert to enable some new service in firewalld.
 * Use this when enabling some network-facing service. The alert will only be shown
 * if firewalld is running, has at least one active zone, and the service is not enabled
 * in any zone yet. It will allow the user to enable the service in any active zone,
 * or go to the firewall page  for more fine-grained configuration.
 *
 * Properties:
 *   - service (string, required): firewalld service name
 *   - title (string, required): Human readable/translated alert title
 *   - pageSection (bool, optional, default false): Render the alert inside a <PageSection>
 */
export const FirewalldRequest = ({ service, title, pageSection }) => {
    const [zones, setZones] = useState(null);
    const [selectedZone, setSelectedZone] = useState(null);
    const [zoneSelectorOpened, setZoneSelectorOpened] = useState(false);
    const [enabledAnywhere, setEnabledAnywhere] = useState(null);
    const [enableError, setEnableError] = useState(null);
    debug("FirewalldRequest", service, "zones", JSON.stringify(zones), "selected zone", selectedZone, "enabledAnywhere", enabledAnywhere);

    if (!service)
        return null;

    // query zones on component initialization
    if (zones === null) {
        firewalld.call("/org/fedoraproject/FirewallD1", "org.fedoraproject.FirewallD1.zone", "getActiveZones")
                .then(([info]) => {
                    const names = Object.keys(info);
                    Promise.all(names.map(name => firewalld.call("/org/fedoraproject/FirewallD1", "org.fedoraproject.FirewallD1.zone", "getZoneSettings2", [name])))
                            .then(zoneInfos => {
                                setEnabledAnywhere(!!zoneInfos.find(zoneInfo => ((zoneInfo[0].services || {}).v || []).indexOf(service) >= 0));
                                setZones(names);
                            })
                            .catch(ex => {
                                console.warn("FirewalldRequest: getZoneSettings failed:", JSON.stringify(ex));
                                setZones([]);
                            });

                    firewalld.call("/org/fedoraproject/FirewallD1", "org.fedoraproject.FirewallD1", "getDefaultZone")
                            .then(([zone]) => setSelectedZone(zone))
                            .catch(ex => console.warn("FirewalldRequest: getDefaultZone failed:", JSON.stringify(ex)));
                })
                .catch(ex => {
                    // firewalld not running
                    debug("FirewalldRequest: getActiveZones failed, considering firewall inactive:", JSON.stringify(ex));
                    setZones([]);
                });
    }

    const onAddService = () => {
        firewalld.call("/org/fedoraproject/FirewallD1", "org.fedoraproject.FirewallD1.zone", "addService",
                       [selectedZone, service, 0])
                // permanent config
                .then(() => firewalld.call("/org/fedoraproject/FirewallD1/config",
                                           "org.fedoraproject.FirewallD1.config",
                                           "getZoneByName", [selectedZone]))
                .then(([path]) => firewalld.call(path, "org.fedoraproject.FirewallD1.config.zone", "addService", [service]))
                // all successful, hide alert
                .then(() => setEnabledAnywhere(true))
                .catch(ex => {
                    // may already be enabled in permanent config, that's ok
                    if (ex.message && ex.message.indexOf("ALREADY_ENABLED") >= 0) {
                        setEnabledAnywhere(true);
                        return;
                    }

                    setEnableError(ex.toString());
                    setEnabledAnywhere(true);
                    console.error("Failed to enable", service, "in firewalld:", JSON.stringify(ex));
                });
    };

    let alert;

    if (enableError) {
        alert = (
            <Alert isInline variant="warning"
                   title={ cockpit.format(_("Failed to enable $0 in firewalld"), service) }
                   actionClose={ <AlertActionCloseButton onClose={ () => setEnableError(null) } /> }
                   actionLinks={
                       <AlertActionLink onClick={() => cockpit.jump("/network/firewall")}>
                           { _("Visit firewall") }
                       </AlertActionLink>
                   }>
                {enableError}
            </Alert>
        );
    // don't show anything if firewalld is not active, or service is already enabled somewhere
    } else if (!zones || zones.length === 0 || !selectedZone || enabledAnywhere) {
        return null;
    } else {
        alert = (
            <Alert isInline variant="info" title={title} className="pf-v5-u-box-shadow-sm">
                <Toolbar className="ct-alert-toolbar">
                    <ToolbarContent>
                        <ToolbarGroup spaceItems={{ default: "spaceItemsMd" }}>
                            <ToolbarItem variant="label">{ _("Zone") }</ToolbarItem>
                            <ToolbarItem>
                                <Select
                                    aria-label={_("Zone")}
                                    onToggle={(_event, isOpen) => setZoneSelectorOpened(isOpen)}
                                    isOpen={zoneSelectorOpened}
                                    onSelect={ (e, sel) => { setSelectedZone(sel); setZoneSelectorOpened(false) } }
                                    selections={selectedZone}
                                    toggleId={"firewalld-request-" + service}>
                                    { zones.map(zone => <SelectOption key={zone} value={zone}>{zone}</SelectOption>) }
                                </Select>
                            </ToolbarItem>

                            <ToolbarItem>
                                <Button variant="primary" onClick={onAddService}>{ cockpit.format(_("Add $0"), service) }</Button>
                            </ToolbarItem>
                        </ToolbarGroup>

                        <ToolbarItem variant="separator" />

                        <ToolbarItem>
                            <Button variant="link" onClick={() => cockpit.jump("/network/firewall")}>
                                { _("Visit firewall") }
                            </Button>
                        </ToolbarItem>
                    </ToolbarContent>
                </Toolbar>
            </Alert>
        );
    }

    if (pageSection)
        return <PageSection className="ct-no-bottom-padding">{alert}</PageSection>;
    else
        return alert;
};
0707010000000D000081A4000000000000000000000001662A0778000001B4000000000000000000000000000000000000004A00000000cockpit-docker-devel-16/pkg/lib/cockpit-components-firewalld-request.scss// Required for `pf-v5-u-box-shadow-sm` as inline alert don't have a box shadow
@use "../../node_modules/@patternfly/patternfly/utilities/BoxShadow/box-shadow.css";

.pf-v5-c-page__main-section.ct-no-bottom-padding {
  --pf-v5-c-page__main-section--PaddingBottom: 0;
}

// <Toolbar> embedded into an <Alert>
.pf-v5-c-toolbar.ct-alert-toolbar {
  --pf-v5-c-toolbar--BackgroundColor: transparent;
  --pf-v5-c-toolbar--PaddingBottom: 0;
}
0707010000000E000081A4000000000000000000000001662A077800000657000000000000000000000000000000000000004300000000cockpit-docker-devel-16/pkg/lib/cockpit-components-form-helper.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2023 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 <http://www.gnu.org/licenses/>.
 */

import React from "react";

import { FormHelperText } from "@patternfly/react-core/dist/esm/components/Form/index.js";
import { HelperText, HelperTextItem } from "@patternfly/react-core/dist/esm/components/HelperText";

export const FormHelper = ({ helperText, helperTextInvalid, variant, icon, fieldId }) => {
    const formHelperVariant = variant || (helperTextInvalid ? "error" : "default");

    if (!(helperText || helperTextInvalid))
        return null;

    return (
        <FormHelperText>
            <HelperText>
                <HelperTextItem
                    id={fieldId ? (fieldId + "-helper") : undefined}
                    variant={formHelperVariant}
                    icon={icon}>
                    {formHelperVariant === "error" ? helperTextInvalid : helperText}
                </HelperTextItem>
            </HelperText>
        </FormHelperText>
    );
};
0707010000000F000081A4000000000000000000000001662A077800000088000000000000000000000000000000000000004B00000000cockpit-docker-devel-16/pkg/lib/cockpit-components-inline-notification.css.alert-link.more-button {
  margin-inline-start: var(--pf-v5-global--spacer--sm);
}

.notification-message {
  white-space: pre-wrap;
}
07070100000010000081A4000000000000000000000001662A077800000CCC000000000000000000000000000000000000004B00000000cockpit-docker-devel-16/pkg/lib/cockpit-components-inline-notification.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
 */
import React from 'react';
import PropTypes from 'prop-types';
import cockpit from 'cockpit';

import { Alert, AlertActionCloseButton } from "@patternfly/react-core/dist/esm/components/Alert/index.js";
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import './cockpit-components-inline-notification.css';

const _ = cockpit.gettext;

function mouseClick(fun) {
    return function (event) {
        if (!event || event.button !== 0)
            return;
        event.preventDefault();
        return fun(event);
    };
}

export class InlineNotification extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            isDetail: false,
        };

        this.toggleDetail = this.toggleDetail.bind(this);
    }

    toggleDetail () {
        this.setState({
            isDetail: !this.state.isDetail,
        });
    }

    render () {
        const { text, detail, type, onDismiss } = this.props;

        let detailButton = null;
        if (detail) {
            let detailButtonText = _("show more");
            if (this.state.isDetail) {
                detailButtonText = _("show less");
            }

            detailButton = (<Button variant="link" isInline className='alert-link more-button'
                onClick={mouseClick(this.toggleDetail)}>{detailButtonText}</Button>);
        }
        const extraProps = {};
        if (onDismiss)
            extraProps.actionClose = <AlertActionCloseButton onClose={onDismiss} />;

        return (
            <Alert variant={type || 'danger'}
                isLiveRegion={this.props.isLiveRegion}
                isInline={this.props.isInline != undefined ? this.props.isInline : true}
                title={<> {text} {detailButton} </>} {...extraProps}>
                {this.state.isDetail && (<p>{detail}</p>)}
            </Alert>
        );
    }
}

InlineNotification.propTypes = {
    onDismiss: PropTypes.func,
    isInline: PropTypes.bool,
    text: PropTypes.string.isRequired, // main information to render
    detail: PropTypes.string, // optional, more detailed information. If empty, the more/less button is not rendered.
    type: PropTypes.string,
};

export const ModalError = ({ dialogError, dialogErrorDetail, id, isExpandable }) => {
    return (
        <Alert id={id} variant='danger' isInline title={dialogError} isExpandable={!!isExpandable}>
            { typeof dialogErrorDetail === 'string' ? <p>{dialogErrorDetail}</p> : dialogErrorDetail }
        </Alert>
    );
};
07070100000011000081A4000000000000000000000001662A077800000266000000000000000000000000000000000000004600000000cockpit-docker-devel-16/pkg/lib/cockpit-components-install-dialog.css.package-list-ct {
  margin-block: 1em;
  margin-inline: 0;
  padding: 0;
  max-inline-size: 110rem;
  text-align: start;
  box-sizing: border-box;
}

.package-list-ct li {
  text-align: start;
  box-sizing: border-box;
  inline-size: 22rem;
  padding-block: 0;
  padding-inline: 1ex;
  display: inline-block;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.scale-up-ct {
  animation: dialogScaleUpCt 1s;
}

@keyframes dialogScaleUpCt {
  0% {
    opacity: 0;
    max-block-size: 0;
  }

  25% {
    opacity: 0;
  }

  50% {
    max-block-size: 100vh;
  }

  100% {
    opacity: 1;
  }
}
07070100000012000081A4000000000000000000000001662A077800001F31000000000000000000000000000000000000004600000000cockpit-docker-devel-16/pkg/lib/cockpit-components-install-dialog.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2018 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 <http://www.gnu.org/licenses/>.
 */

import cockpit from "cockpit";
import React from "react";

import { Flex } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
import { Spinner } from "@patternfly/react-core/dist/esm/components/Spinner/index.js";
import { WarningTriangleIcon } from "@patternfly/react-icons";

import { show_modal_dialog } from "cockpit-components-dialog.jsx";
import * as PK from "packagekit.js";

import "cockpit-components-install-dialog.css";

const _ = cockpit.gettext;

// TODO - generalize this to arbitrary number of arguments (when needed)
function format_to_fragments(fmt, arg) {
    const index = fmt.indexOf("$0");
    if (index >= 0)
        return <>{fmt.slice(0, index)}{arg}{fmt.slice(index + 2)}</>;
    else
        return fmt;
}

/* Calling install_dialog will open a dialog that lets the user
 * install the given package.
 *
 * The install_dialog function returns a promise that is fulfilled when the dialog closes after
 * a successful installation.  The promise is rejected when the user cancels the dialog.
 *
 * If the package is already installed before the dialog opens, we still go
 * through all the motions and the dialog closes successfully without doing
 * anything when the use hits "Install".
 *
 * You shouldn't call install_dialog unless you know that PackageKit is available.
 * (If you do anyway, the resulting D-Bus errors will be shown to the user.)
 */

export function install_dialog(pkg, options) {
    let data = null;
    let error_message = null;
    let progress_message = null;
    let cancel = null;
    let done = null;

    if (!Array.isArray(pkg))
        pkg = [pkg];

    options = options || { };

    const prom = new Promise((resolve, reject) => { done = f => { if (f) resolve(); else reject(); } });

    let dialog = null;
    function update() {
        let extra_details = null;
        let remove_details = null;
        let footer_message = null;

        const missing_name = <strong>{pkg.join(", ")}</strong>;

        if (data && data.extra_names.length > 0)
            extra_details = (
                <div className="scale-up-ct">
                    {_("Additional packages:")}
                    <ul className="package-list-ct">{data.extra_names.map(id => <li key={id}>{id}</li>)}</ul>
                </div>
            );

        if (data && data.remove_names.length > 0)
            remove_details = (
                <div className="scale-up-ct">
                    <WarningTriangleIcon /> {_("Removals:")}
                    <ul className="package-list">{data.remove_names.map(id => <li key={id}>{id}</li>)}</ul>
                </div>
            );

        if (progress_message)
            footer_message = (
                <Flex spaceItems={{ default: 'spaceItemsSm' }} alignItems={{ default: 'alignItemsCenter' }}>
                    <span>{ progress_message }</span>
                    <Spinner size="sm" />
                </Flex>
            );
        else if (data?.download_size) {
            footer_message = (
                <div>
                    { format_to_fragments(_("Total size: $0"), <strong>{cockpit.format_bytes(data.download_size)}</strong>) }
                </div>
            );
        }

        const body = {
            id: "dialog",
            title: options.title || _("Install software"),
            body: (
                <div className="scroll">
                    <p>{ format_to_fragments(options.text || _("$0 will be installed."), missing_name) }</p>
                    { remove_details }
                    { extra_details }
                </div>
            ),
            static_error: error_message,
        };

        const footer = {
            actions: [
                {
                    caption: _("Install"),
                    style: "primary",
                    clicked: install_missing,
                    disabled: data == null
                }
            ],
            idle_message: footer_message,
            dialog_done: f => { if (!f && cancel) cancel(); done(f) }
        };

        if (dialog) {
            dialog.setProps(body);
            dialog.setFooterProps(footer);
        } else {
            dialog = show_modal_dialog(body, footer);
        }
    }

    function check_missing() {
        PK.check_missing_packages(pkg,
                                  p => {
                                      cancel = p.cancel;
                                      let pm = null;
                                      if (p.waiting)
                                          pm = _("Waiting for other software management operations to finish");
                                      else
                                          pm = _("Checking installed software");
                                      if (pm != progress_message) {
                                          progress_message = pm;
                                          update();
                                      }
                                  })
                .then(d => {
                    if (d.unavailable_names.length > 0)
                        error_message = cockpit.format(_("$0 is not available from any repository."),
                                                       d.unavailable_names[0]);
                    else
                        data = d;
                    progress_message = null;
                    cancel = null;
                    update();
                })
                .catch(e => {
                    progress_message = null;
                    cancel = null;
                    error_message = e.toString();
                    update();
                });
    }

    function install_missing() {
        // We need to return a Cockpit flavoured promise since we want
        // to use progress notifications.
        const dfd = cockpit.defer();

        PK.install_missing_packages(data,
                                    p => {
                                        let text = null;
                                        if (p.waiting) {
                                            text = _("Waiting for other software management operations to finish");
                                        } else if (p.package) {
                                            let fmt;
                                            if (p.info == PK.Enum.INFO_DOWNLOADING)
                                                fmt = _("Downloading $0");
                                            else if (p.info == PK.Enum.INFO_REMOVING)
                                                fmt = _("Removing $0");
                                            else
                                                fmt = _("Installing $0");
                                            text = format_to_fragments(fmt, <strong>{p.package}</strong>);
                                        }
                                        dfd.notify(text, p.cancel);
                                    })
                .then(() => {
                    dfd.resolve();
                })
                .catch(error => {
                    dfd.reject(error);
                });

        return dfd.promise;
    }

    update();
    check_missing();
    return prom;
}
07070100000013000081A4000000000000000000000001662A077800000CCE000000000000000000000000000000000000004500000000cockpit-docker-devel-16/pkg/lib/cockpit-components-listing-panel.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
 */

import PropTypes from 'prop-types';
import React from 'react';
import { Tab, TabTitleText, Tabs } from "@patternfly/react-core/dist/esm/components/Tabs/index.js";
import './cockpit-components-listing-panel.scss';

/* tabRenderers optional: list of tab renderers for inline expansion, array of objects with
 *     - name tab name (has to be unique in the entry, used as react key)
 *     - renderer react component
 *     - data render data passed to the tab renderer
 */
export class ListingPanel extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            activeTab: props.initiallyActiveTab ? props.initiallyActiveTab : 0, // currently active tab in expanded mode, defaults to first tab
        };
        this.handleTabClick = this.handleTabClick.bind(this);
    }

    handleTabClick(event, tabIndex) {
        event.preventDefault();
        if (this.state.activeTab !== tabIndex) {
            this.setState({ activeTab: tabIndex });
        }
    }

    render() {
        let listingDetail;
        if ('listingDetail' in this.props) {
            listingDetail = (
                <span className="ct-listing-panel-caption">
                    {this.props.listingDetail}
                </span>
            );
        }

        return (
            <div className="ct-listing-panel">
                {listingDetail && <div className="ct-listing-panel-actions pf-v5-c-tabs">
                    {listingDetail}
                </div>}
                {this.props.tabRenderers.length && <Tabs activeKey={this.state.activeTab} className="ct-listing-panel-tabs" mountOnEnter onSelect={this.handleTabClick}>
                    {this.props.tabRenderers.map((itm, tabIdx) => {
                        const Renderer = itm.renderer;
                        const rendererData = itm.data;

                        return (
                            <Tab key={tabIdx} eventKey={tabIdx} title={<TabTitleText>{itm.name}</TabTitleText>}>
                                <div className="ct-listing-panel-body" key={tabIdx} data-key={tabIdx}>
                                    <Renderer {...rendererData} />
                                </div>
                            </Tab>
                        );
                    })}
                </Tabs>}
            </div>
        );
    }
}
ListingPanel.defaultProps = {
    tabRenderers: [],
};

ListingPanel.propTypes = {
    tabRenderers: PropTypes.array,
    listingDetail: PropTypes.node,
    initiallyActiveTab: PropTypes.number,
};
07070100000014000081A4000000000000000000000001662A077800000A34000000000000000000000000000000000000004600000000cockpit-docker-devel-16/pkg/lib/cockpit-components-listing-panel.scss.ct-listing-panel {
  display: flex;
  flex-wrap: wrap;

  &-actions {
    order: 2;
    flex-grow: 1;
    padding-block: var(--pf-v5-global--spacer--sm);
    padding-inline: var(--pf-v5-global--spacer--md) var(--pf-v5-global--spacer--lg);
  }

  &-caption {
    margin-inline-start: auto;
  }

  &-tabs {
    flex-grow: 1;
    order: 1;
  }

  .pf-v5-c-tab-content {
    order: 3;
    flex-basis: 100%;
  }

  &-body {
    // Don't let PF4 automatically add a border in tables inside the body
    --pf-v5-c-table__expandable-row--after--BorderLeftWidth: 0;
    --pf-v5-c-table--border-width--base: 0;

    // Add some sizing to the body
    padding-block: var(--pf-v5-global--spacer--md);
    padding-inline: var(--pf-v5-global--spacer--lg);
    inline-size: 100%;

    // Containing hack part 1
    float: inline-start;

    &::after {
      // Containing hack part 2: Clearfix CSS hack,
      // to allow children content to float fine without setting overflow
      content: "";
      clear: both;
      display: table;
    }
  }
}

.ct-table {
  > tbody > .pf-v5-c-table__expandable-row {
    // Don't scroll table's expanded contents vertically.
    // Instead, rely on page scrolling.
    // Important for mobile; also useful for desktop.
    overflow-block: visible !important;
    max-block-size: unset !important;
  }
}

// PF4 upstream issue to adopt expand animation:
// https://github.com/patternfly/patternfly-design/issues/899

@media not all and (prefers-reduced-motion: reduce) {
  // Add expansion animations when prefers-reduced isn't enabled
  .ct-table .pf-v5-c-table__expandable-row-content {
    // Animation ends at or before 2/3 in most cases; so we extend by 1.5 to compensate
    animation: ctListingPanelShow calc(var(--pf-v5-global--TransitionDuration) * 1.5) var(--pf-v5-global--TimingFunction);
  }
}

@keyframes ctListingPanelShow {
  0% {
    // The animation needs to flow downward to feel natural
    transform-origin: top;
    // Overflow will revert when done (but should be hidden during animation)
    overflow: hidden;
    max-block-size: 0;
    // Padding should 'tween between 0 and the actual padding (unstated)
    padding-block: 0;
  }

  67% {
    // Max height is tricky in animations, as auto doesn't work
    // 100vh makes sense, but would cause different speeds on different devices
    // Screens are almost all =< 12000px; data is almost always smaller
    // we'll relax it to to 100vh at 100%, just in case.
    max-block-size: 1200px;
  }

  100% {
    // Allow content to extend to the height of the screen (just in case)
    max-block-size: 100vh;
  }
}
07070100000015000081A4000000000000000000000001662A077800001941000000000000000000000000000000000000004200000000cockpit-docker-devel-16/pkg/lib/cockpit-components-logs-panel.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
 */

import cockpit from "cockpit";
import React from "react";

import { Badge } from "@patternfly/react-core/dist/esm/components/Badge/index.js";
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { Card, CardBody, CardHeader, CardTitle } from '@patternfly/react-core/dist/esm/components/Card/index.js';
import { ExclamationTriangleIcon, TimesCircleIcon } from '@patternfly/react-icons';

import { journal } from "journal";
import "journal.css";
import "cockpit-components-logs-panel.scss";

const _ = cockpit.gettext;

/* JournalOutput implements the interface expected by
   journal.renderer, and also collects the output.
 */

export class JournalOutput {
    constructor(search_options) {
        this.logs = [];
        this.reboot_key = 0;
        this.search_options = search_options || {};
    }

    onEvent(ev, cursor, full_content) {
        // only consider primary mouse button for clicks
        if (ev.type === 'click') {
            if (ev.button !== 0)
                return;

            // Ignore if text is being selected - less than 3 characters most likely means misclick
            const selection = window.getSelection().toString();
            if (selection && selection.length > 2 && full_content.indexOf(selection) >= 0)
                return;
        }

        // only consider enter button for keyboard events
        if (ev.type === 'KeyDown' && ev.key !== "Enter")
            return;

        cockpit.jump("system/logs#/" + cursor + "?parent_options=" + JSON.stringify(this.search_options));
    }

    render_line(ident, prio, message, count, time, entry) {
        let problem = false;
        let warning = false;

        if (ident === 'abrt-notification') {
            problem = true;
            ident = entry.PROBLEM_BINARY;
        } else if (prio < 4) {
            warning = true;
        }

        const full_content = [time, message, ident].join("\n");

        return (
            <div className="cockpit-logline" role="row" tabIndex="0" key={entry.__CURSOR}
                data-cursor={entry.__CURSOR}
                onClick={ev => this.onEvent(ev, entry.__CURSOR, full_content)}
                onKeyDown={ev => this.onEvent(ev, entry.__CURSOR, full_content)}>
                <div className="cockpit-log-warning" role="cell">
                    { warning
                        ? <ExclamationTriangleIcon className="ct-icon-exclamation-triangle" />
                        : null
                    }
                    { problem
                        ? <TimesCircleIcon className="ct-icon-times-circle" />
                        : null
                    }
                </div>
                <div className="cockpit-log-time" role="cell">{time}</div>
                <span className="cockpit-log-message" role="cell">{message}</span>
                {
                    count > 1
                        ? <div className="cockpit-log-service-container" role="cell">
                            <div className="cockpit-log-service-reduced">{ident}</div>
                            <Badge screenReaderText={_("Occurrences")} isRead key={count}>{count}</Badge>
                        </div>
                        : <div className="cockpit-log-service" role="cell">{ident}</div>
                }
            </div>
        );
    }

    render_day_header(day) {
        return <div className="panel-heading" key={day}>{day}</div>;
    }

    render_reboot_separator() {
        return (
            <div className="cockpit-logline" role="row" key={"reboot-" + this.reboot_key++}>
                <div className="cockpit-log-warning" role="cell" />
                <span className="cockpit-log-message cockpit-logmsg-reboot" role="cell">{_("Reboot")}</span>
            </div>
        );
    }

    prepend(item) {
        this.logs.unshift(item);
    }

    append(item) {
        this.logs.push(item);
    }

    remove_first() {
        this.logs.shift();
    }

    remove_last() {
        this.logs.pop();
    }

    limit(max) {
        if (this.logs.length > max)
            this.logs = this.logs.slice(0, max);
    }
}

export class LogsPanel extends React.Component {
    constructor() {
        super();
        this.state = { logs: [] };
    }

    componentDidMount() {
        this.journalctl = journal.journalctl(this.props.match, { count: this.props.max });

        const out = new JournalOutput(this.props.search_options);
        const render = journal.renderer(out);

        this.journalctl.stream((entries) => {
            for (let i = 0; i < entries.length; i++)
                render.prepend(entries[i]);
            render.prepend_flush();
            // "max + 1" since there is always a date header and we
            // want to show "max" entries below it.
            out.limit(this.props.max + 1);
            this.setState({ logs: out.logs });
        });
    }

    componentWillUnmount() {
        this.journalctl.stop();
    }

    render() {
        const actions = (this.state.logs.length > 0 && this.props.goto_url) && <Button variant="secondary" onClick={e => cockpit.jump(this.props.goto_url)}>{_("View all logs")}</Button>;

        return (
            <Card className="cockpit-log-panel">
                <CardHeader actions={{ actions }}>
                    <CardTitle>{this.props.title}</CardTitle>
                </CardHeader>
                <CardBody className={(!this.state.logs.length && this.props.emptyMessage.length) ? "empty-message" : "contains-list"}>
                    { this.state.logs.length ? this.state.logs : this.props.emptyMessage }
                </CardBody>
            </Card>
        );
    }
}
LogsPanel.defaultProps = {
    emptyMessage: [],
};
07070100000016000081A4000000000000000000000001662A07780000015A000000000000000000000000000000000000004300000000cockpit-docker-devel-16/pkg/lib/cockpit-components-logs-panel.scss.panel-body.empty-message {
  padding-block: 0.5rem;
  padding-inline: 1rem;
  text-align: center;
}

.cockpit-log-panel .panel-body {
  padding-inline: 0;
}

.cockpit-log-panel .pf-v5-c-card__header-main > .pf-v5-c-card__title > .pf-v5-c-card__title-text {
  padding: 0;
  font-weight: normal;
  font-size: var(--pf-v5-global--FontSize--2xl);
}
07070100000017000081A4000000000000000000000001662A07780000021F000000000000000000000000000000000000004500000000cockpit-docker-devel-16/pkg/lib/cockpit-components-modifications.css.automation-script-modal pre {
  max-block-size: 20em;
  margin-block-end: 5px;
}

.automation-script-modal span.fa {
  margin-inline-end: 5px;
}

.automation-script-modal i.fa {
  margin-inline: 5px 2px;
}

.automation-script-modal textarea {
  min-block-size: 15rem;
}

.automation-script-modal .ansible-docs-link > svg {
  padding-inline-end: var(--pf-v5-global--spacer--xs);
}

.green-icon {
  color: var(--pf-v5-global--success-color--100);
}

.pf-v5-c-card.modifications-table .pf-v5-c-card__header {
  justify-content: space-between;
}
07070100000018000081A4000000000000000000000001662A077800001E1B000000000000000000000000000000000000004500000000cockpit-docker-devel-16/pkg/lib/cockpit-components-modifications.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
 */

import PropTypes from 'prop-types';
import React from 'react';
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { Card, CardBody, CardHeader, CardTitle } from "@patternfly/react-core/dist/esm/components/Card/index.js";
import { DataList, DataListCell, DataListItem, DataListItemCells, DataListItemRow } from "@patternfly/react-core/dist/esm/components/DataList/index.js";
import { Modal } from "@patternfly/react-core/dist/esm/components/Modal/index.js";
import { Tab, Tabs } from "@patternfly/react-core/dist/esm/components/Tabs/index.js";
import { TextArea } from "@patternfly/react-core/dist/esm/components/TextArea/index.js";
import { CheckIcon, CopyIcon, ExternalLinkAltIcon, OutlinedQuestionCircleIcon } from '@patternfly/react-icons';

import cockpit from "cockpit";
import 'cockpit-components-modifications.css';

const _ = cockpit.gettext;

/* Dialog for showing scripts to modify system
 *
 * Enables showing shell and ansible script. Shell one is mandatory and ansible one can be omitted.
 *
 */
export const ModificationsExportDialog = ({ show, onClose, shell, ansible }) => {
    const [active_tab, setActiveTab] = React.useState("ansible");
    const [copied, setCopied] = React.useState(false);
    const [timeoutId, setTimeoutId] = React.useState(null);

    const handleSelect = (_event, active_tab) => {
        setCopied(false);
        setActiveTab(active_tab);
        if (timeoutId !== null) {
            clearTimeout(timeoutId);
            setTimeoutId(null);
        }
    };

    const copyToClipboard = () => {
        try {
            navigator.clipboard.writeText((active_tab === "ansible" ? ansible : shell).trim())
                    .then(() => {
                        setCopied(true);
                        setTimeoutId(setTimeout(() => {
                            setCopied(false);
                            setTimeoutId(null);
                        }, 3000));
                    })
                    .catch(e => console.error('Text could not be copied: ', e ? e.toString() : ""));
        } catch (error) {
            console.error('Text could not be copied: ', error.toString());
        }
    };

    const footer = (
        <>
            <Button variant='secondary' className="btn-clipboard" onClick={copyToClipboard} icon={copied ? <CheckIcon className="green-icon" /> : <CopyIcon />}>
                { _("Copy to clipboard") }
            </Button>
            <Button variant='secondary' className='btn-cancel' onClick={onClose}>
                { _("Close") }
            </Button>
        </>
    );

    return (
        <Modal isOpen={show} className="automation-script-modal"
               position="top" variant="medium"
               onClose={onClose}
               footer={footer}
               title={_("Automation script") }>
            <Tabs activeKey={active_tab} onSelect={handleSelect}>
                <Tab eventKey="ansible" title={_("Ansible")}>
                    <TextArea resizeOrientation='vertical' readOnlyVariant="default" defaultValue={ansible.trim()} />
                    <div className="ansible-docs-link">
                        <OutlinedQuestionCircleIcon />
                        { _("Create new task file with this content.") }
                        <Button variant="link" component="a" href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html"
                                target="_blank" rel="noopener noreferrer"
                                icon={<ExternalLinkAltIcon />}>
                            { _("Ansible roles documentation") }
                        </Button>
                    </div>
                </Tab>
                <Tab eventKey="shell" title={_("Shell script")}>
                    <TextArea resizeOrientation='vertical' readOnlyVariant="default" defaultValue={shell.trim()} />
                </Tab>
            </Tabs>
        </Modal>
    );
};

ModificationsExportDialog.propTypes = {
    shell: PropTypes.string.isRequired,
    ansible: PropTypes.string.isRequired,
    show: PropTypes.bool.isRequired,
    onClose: PropTypes.func.isRequired,
};

/* Display list of modifications in human readable format
 *
 * Also show `View automation script` button which opens dialog in which different
 * scripts are available. With these scripts it is possible to apply the same
 * configurations to  other machines.
 *
 * Pass array `entries` to show human readable messages.
 * Pass string `shell` and `ansible` with scripts.
 *
 */
export const Modifications = ({ entries, failed, permitted, title, shell, ansible }) => {
    const [showDialog, setShowDialog] = React.useState(false);

    let emptyRow = null;
    let fail_message = permitted ? _("No system modifications") : _("The logged in user is not permitted to view system modifications");
    fail_message = failed || fail_message;
    if (entries === null) {
        emptyRow = <DataListItem>
            <DataListItemRow>
                <DataListItemCells dataListCells={[<DataListCell key="loading">{_("Loading system modifications...")}</DataListCell>]} />
            </DataListItemRow>
        </DataListItem>;
    }
    if (entries?.length === 0) {
        emptyRow = <DataListItem>
            <DataListItemRow>
                <DataListItemCells dataListCells={[<DataListCell key={fail_message}>{fail_message}</DataListCell>]} />
            </DataListItemRow>
        </DataListItem>;
    }

    return (
        <>
            <ModificationsExportDialog show={showDialog} shell={shell} ansible={ansible} onClose={() => setShowDialog(false)} />
            <Card className="modifications-table">
                <CardHeader>
                    <CardTitle component="h2">{title}</CardTitle>
                    { !emptyRow &&
                        <Button variant="secondary" onClick={() => setShowDialog(true)}>
                            {_("View automation script")}
                        </Button>
                    }
                </CardHeader>
                <CardBody className="contains-list">
                    <DataList aria-label={title} isCompact>
                        { emptyRow ||
                            entries.map(entry => <DataListItem key={entry}>
                                <DataListItemRow>
                                    <DataListItemCells dataListCells={[<DataListCell key={entry}>{entry}</DataListCell>]} />
                                </DataListItemRow>
                            </DataListItem>
                            )
                        }
                    </DataList>
                </CardBody>
            </Card>
        </>
    );
};

Modifications.propTypes = {
    failed: PropTypes.string,
    title: PropTypes.string.isRequired,
    permitted: PropTypes.bool.isRequired,
    entries: PropTypes.arrayOf(PropTypes.string),
    shell: PropTypes.string.isRequired,
    ansible: PropTypes.string.isRequired,
};
07070100000019000081A4000000000000000000000001662A077800002261000000000000000000000000000000000000004000000000cockpit-docker-devel-16/pkg/lib/cockpit-components-password.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
 */
import cockpit from 'cockpit';
import React, { useState } from 'react';
import { debounce } from 'throttle-debounce';
import { Button } from '@patternfly/react-core/dist/esm/components/Button/index.js';
import { FormGroup, FormHelperText } from "@patternfly/react-core/dist/esm/components/Form/index.js";
import { InputGroup, InputGroupItem } from '@patternfly/react-core/dist/esm/components/InputGroup/index.js';
import { HelperText, HelperTextItem } from "@patternfly/react-core/dist/esm/components/HelperText/index.js";
import { Popover } from "@patternfly/react-core/dist/esm/components/Popover/index.js";
import { Progress, ProgressMeasureLocation, ProgressSize } from "@patternfly/react-core/dist/esm/components/Progress/index.js";
import { TextInput } from "@patternfly/react-core/dist/esm/components/TextInput/index.js";
import { EyeIcon, EyeSlashIcon, HelpIcon } from '@patternfly/react-icons';

import { FormHelper } from "cockpit-components-form-helper";

import './cockpit-components-password.scss';
import { Flex, FlexItem } from '@patternfly/react-core';

const _ = cockpit.gettext;

export function password_quality(password, force) {
    return new Promise((resolve, reject) => {
        cockpit.spawn('/usr/bin/pwscore', { err: "message" })
                .input(password)
                .done(function(content) {
                    const quality = parseInt(content, 10);
                    if (quality === 0)
                        reject(new Error(_("Password is too weak")));
                    else
                        resolve({ value: quality, message: quality === 100 ? _("Excellent password") : undefined });
                })
                .fail(function(ex) {
                    if (!force)
                        reject(new Error(ex.message || _("Password is not acceptable")));
                    else
                        resolve({ value: 0 });
                });
    });
}

const debounced_password_quality = debounce(300, (value, callback) => {
    password_quality(value).catch(() => ({ value: 0 })).then(callback);
});

export const PasswordFormFields = ({
    password_label, password_confirm_label,
    password_label_info,
    initial_password,
    error_password, error_password_confirm,
    idPrefix, change
}) => {
    const [password, setPassword] = useState(initial_password || "");
    const [passwordConfirm, setConfirmPassword] = useState("");
    const [passwordStrength, setPasswordStrength] = useState();
    const [passwordMessage, setPasswordMessage] = useState("");
    const [passwordHidden, setPasswordHidden] = useState(true);
    const [passwordConfirmHidden, setPasswordConfirmHidden] = useState(true);

    function onPasswordChanged(value) {
        setPassword(value);
        change("password", value);

        if (value) {
            debounced_password_quality(value, strength => {
                setPasswordStrength(strength.value);
                setPasswordMessage(strength.message);
            });
        } else {
            setPasswordStrength();
            setPasswordMessage("");
        }
    }

    let variant;
    let message;
    let messageColor;
    if (passwordStrength > 66) {
        variant = "success";
        messageColor = "pf-v5-u-success-color-200";
        message = _("Strong password");
    } else if (passwordStrength > 33) {
        variant = "warning";
        messageColor = "pf-v5-u-warning-color-200";
        message = _("Acceptable password");
    } else {
        variant = "danger";
        messageColor = "pf-v5-u-danger-color-200";
        message = _("Weak password");
    }

    if (!passwordMessage && message)
        setPasswordMessage(message);

    let passwordStrengthValue = Number.isInteger(passwordStrength) ? Number.parseInt(passwordStrength) : -1;
    if (password !== "" && (passwordStrengthValue >= 0 && passwordStrengthValue < 25))
        passwordStrengthValue = 25;

    return (
        <>
            <FormGroup label={password_label}
                       labelIcon={password_label_info &&
                           <Popover bodyContent={password_label_info}>
                               <button onClick={e => e.preventDefault()}
                                       className="pf-v5-c-form__group-label-help">
                                   <HelpIcon />
                               </button>
                           </Popover>
                       }
                       validated={error_password ? "warning" : "default"}
                       id={idPrefix + "-pw1-group"}
                       fieldId={idPrefix + "-pw1"}>
                <InputGroup>
                    <InputGroupItem isFill>
                        <TextInput className="check-passwords" type={passwordHidden ? "password" : "text"} id={idPrefix + "-pw1"}
                                   autoComplete="new-password" value={password} onChange={(_event, value) => onPasswordChanged(value)}
                                   validated={error_password ? "warning" : "default"} />
                    </InputGroupItem>
                    <InputGroupItem>
                        <Button
                            variant="control"
                            onClick={() => setPasswordHidden(!passwordHidden)}
                            aria-label={passwordHidden ? _("Show password") : _("Hide password")}>
                            {passwordHidden ? <EyeIcon /> : <EyeSlashIcon />}
                        </Button>
                    </InputGroupItem>
                </InputGroup>
                {passwordStrengthValue >= 0 && <Flex spaceItems={{ default: 'spaceItemsSm' }}>
                    <FlexItem>
                        <Progress id={idPrefix + "-meter"}
                            className={"pf-v5-u-pt-xs ct-password-strength-meter " + variant}
                            title={_("password quality")}
                            size={ProgressSize.sm}
                            measureLocation={ProgressMeasureLocation.none}
                            variant={variant}
                            value={passwordStrengthValue} />
                    </FlexItem>
                    <FlexItem>
                        <div id={idPrefix + "-password-meter-message"} className={"pf-v5-c-form__helper-text " + messageColor} aria-live="polite">{passwordMessage}</div>
                    </FlexItem>
                </Flex>}
                {error_password && <FormHelperText>
                    <HelperText component="ul" aria-live="polite" id="password-error-message">
                        <HelperTextItem isDynamic variant="warning" component="li">
                            {error_password}
                        </HelperTextItem>
                    </HelperText>
                </FormHelperText>}
            </FormGroup>

            {password_confirm_label && <FormGroup label={password_confirm_label}
                       id={idPrefix + "-pw2-group"}
                       fieldId={idPrefix + "-pw2"}>
                <InputGroup>
                    <InputGroupItem isFill>
                        <TextInput type={passwordConfirmHidden ? "password" : "text"} id={idPrefix + "-pw2"} autoComplete="new-password"
                            value={passwordConfirm} onChange={(_event, value) => { setConfirmPassword(value); change("password_confirm", value) }} />
                    </InputGroupItem>
                    <InputGroupItem>
                        <Button
                            variant="control"
                            onClick={() => setPasswordConfirmHidden(!passwordConfirmHidden)}
                            aria-label={passwordConfirmHidden ? _("Show confirmation password") : _("Hide confirmation password")}>
                            {passwordConfirmHidden ? <EyeIcon /> : <EyeSlashIcon />}
                        </Button>
                    </InputGroupItem>
                </InputGroup>
                <FormHelper fieldId={idPrefix + "-pw2"} helperTextInvalid={error_password_confirm} />
            </FormGroup>}
        </>
    );
};
0707010000001A000081A4000000000000000000000001662A07780000012C000000000000000000000000000000000000004100000000cockpit-docker-devel-16/pkg/lib/cockpit-components-password.scss@import "global-variables";
@import "@patternfly/patternfly/utilities/Text/text.scss";

.ct-password-strength-meter {
  grid-gap: var(--pf-v5-global--spacer--xs);
  inline-size: var(--pf-v5-global--spacer--2xl);

  .pf-v5-c-progress__description, .pf-v5-c-progress__status {
    display: none;
  }
}
0707010000001B000081A4000000000000000000000001662A07780000499D000000000000000000000000000000000000003C00000000cockpit-docker-devel-16/pkg/lib/cockpit-components-plot.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
 */

import cockpit from "cockpit";

import React, { useState, useRef, useLayoutEffect } from 'react';
import { useEvent } from "hooks.js";

import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { Dropdown, DropdownItem, DropdownSeparator, DropdownToggle } from '@patternfly/react-core/dist/esm/deprecated/components/Dropdown/index.js';

import { AngleLeftIcon, AngleRightIcon, SearchMinusIcon } from '@patternfly/react-icons';

import * as timeformat from "timeformat";
import '@patternfly/patternfly/patternfly-charts.scss';
import "cockpit-components-plot.scss";

const _ = cockpit.gettext;

function time_ticks(data) {
    const first_plot = data[0].data;
    const start_ms = first_plot[0][0];
    const end_ms = first_plot[first_plot.length - 1][0];

    // Determine size between ticks

    const sizes_in_seconds = [
        60, // minute
        5 * 60, // 5 minutes
        10 * 60, // 10 minutes
        30 * 60, // half hour
        60 * 60, // hour
        6 * 60 * 60, // quarter day
        12 * 60 * 60, // half day
        24 * 60 * 60, // day
        7 * 24 * 60 * 60, // week
        30 * 24 * 60 * 60, // month
        183 * 24 * 60 * 60, // half a year
        365 * 24 * 60 * 60, // year
        10 * 365 * 24 * 60 * 60 // 10 years
    ];

    let size;
    for (let i = 0; i < sizes_in_seconds.length; i++) {
        if (((end_ms - start_ms) / 1000) / sizes_in_seconds[i] < 10 || i == sizes_in_seconds.length - 1) {
            size = sizes_in_seconds[i] * 1000;
            break;
        }
    }

    // Determine what to omit from the tick label.  If it's all in the
    // current year, we don't need to include the year; and if it's
    // all happening today, we don't include the month and date.

    const now_date = new Date();
    const start_date = new Date(start_ms);

    let include_year = true;
    let include_month_and_day = true;

    if (start_date.getFullYear() == now_date.getFullYear()) {
        include_year = false;
        if (start_date.getMonth() == now_date.getMonth() && start_date.getDate() == now_date.getDate())
            include_month_and_day = false;
    }

    // Compute the actual ticks

    const ticks = [];
    let t = Math.ceil(start_ms / size) * size;
    while (t < end_ms) {
        ticks.push(t);
        t += size;
    }

    // Render the label

    function pad(n) {
        let str = n.toFixed();
        if (str.length == 1)
            str = '0' + str;
        return str;
    }

    function format_tick(val, index, ticks) {
        const d = new Date(val);
        let label = ' ';

        if (include_month_and_day) {
            if (include_year)
                label += timeformat.date(d) + '\n';
            else
                label += timeformat.formatter({ month: "long" }).format(d) + ' ' + d.getDate().toFixed() + '\n';
        }
        label += pad(d.getHours()) + ':' + pad(d.getMinutes());

        return label;
    }

    return {
        ticks,
        formatter: format_tick,
        start: start_ms,
        end: end_ms
    };
}

function value_ticks(data, config) {
    let max = config.min_max;
    const last_plot = data[data.length - 1].data;
    for (let i = 0; i < last_plot.length; i++) {
        const s = last_plot[i][1] || last_plot[i][2];
        if (s > max)
            max = s;
    }

    // Find the highest power of the base unit that is still below
    // MAX.
    //
    // For example, if the base unit is 1000 and MAX is 402,345,765
    // this will set UNIT to 1,000,000, aka "Mega".
    //
    let unit = 1;
    while (config.base_unit && max > unit * config.base_unit)
        unit *= config.base_unit;

    // Find the highest power of 10 that is below the maximum number
    // on a tick label.  If we use that as the distance between ticks,
    // we get at most 10 ticks.
    //
    // To continue the example, MAX is 402,345,765 and UNIT is thus
    // 1,000,000.  The highest number on a tick label would be MAX /
    // UNIT = 402ish.  The highest power of 10 below that is 100.  Thus
    // the size between ticks is 100*UNIT = 100,000,000.  Ticks would
    // thus be "100 Mega" apart.
    //
    // If the highest number of would be only, say, 81, then we would get
    // a highest power of 10, and ticks would be 10 units apart.
    //
    let size = Math.pow(10, Math.floor(Math.log10(max / unit))) * unit;

    // Get the number of ticks to be around 4, but don't produce
    // fractional numbers.  This is done by doubling or halving the
    // size between ticks until we get MAX / SIZE to be less than 8 or
    // greater than 2.
    //
    // In the example, MAX / SIZE is already in range, so nothing
    // changes here.
    //
    // If MAX / UNIT is close to the next power of ten, such as 999, we
    // would end up with a doubled SIZE of 200,000,000.
    //
    // If on the other hand MAX / UNIT would be closer to the next
    // lower power of 10, like say 110, then we would half the SIZE to
    // get moreticks.  With 110, it will happen twice and SIZE ends up
    // being 25,000,000.
    //
    // However, if we only have single digit tick labels, we don't
    // want to halve them any further, since we don't want tick labels
    // like "0.75".
    //
    while (max / size > 7)
        size *= 2;
    while (max / size < 3 && size / unit >= 10)
        size /= 2;

    // Make a list of tick values, each SIZE apart until we are just
    // above MAX.
    //
    // In the example, we get
    //
    //    [ 0, 100000000, 200000000, 300000000, 400000000, 500000000 ]
    //
    const ticks = [];
    for (let t = 0; t < max + size; t += size)
        ticks.push(t);

    if (config.pull_out_unit) {
        const unit_str = config.formatter(unit, config.base_unit, true)[1];

        return {
            ticks,
            formatter: (val) => config.formatter(val, unit_str, true)[0],
            unit: unit_str,
            max: ticks[ticks.length - 1]
        };
    } else {
        return {
            ticks,
            formatter: config.formatter,
            max: ticks[ticks.length - 1]
        };
    }
}

export const ZoomControls = ({ plot_state }) => {
    function format_range(seconds) {
        let n;
        if (seconds >= 365 * 24 * 60 * 60) {
            n = Math.ceil(seconds / (365 * 24 * 60 * 60));
            return cockpit.format(cockpit.ngettext("$0 year", "$0 years", n), n);
        } else if (seconds >= 30 * 24 * 60 * 60) {
            n = Math.ceil(seconds / (30 * 24 * 60 * 60));
            return cockpit.format(cockpit.ngettext("$0 month", "$0 months", n), n);
        } else if (seconds >= 7 * 24 * 60 * 60) {
            n = Math.ceil(seconds / (7 * 24 * 60 * 60));
            return cockpit.format(cockpit.ngettext("$0 week", "$0 weeks", n), n);
        } else if (seconds >= 24 * 60 * 60) {
            n = Math.ceil(seconds / (24 * 60 * 60));
            return cockpit.format(cockpit.ngettext("$0 day", "$0 days", n), n);
        } else if (seconds >= 60 * 60) {
            n = Math.ceil(seconds / (60 * 60));
            return cockpit.format(cockpit.ngettext("$0 hour", "$0 hours", n), n);
        } else {
            n = Math.ceil(seconds / 60);
            return cockpit.format(cockpit.ngettext("$0 minute", "$0 minutes", n), n);
        }
    }

    const zoom_state = plot_state.zoom_state;

    const [isOpen, setIsOpen] = useState(false);
    useEvent(plot_state, "changed");
    useEvent(zoom_state, "changed");

    function range_item(seconds, title) {
        return (
            <DropdownItem key={title}
                          onClick={() => {
                              setIsOpen(false);
                              zoom_state.set_range(seconds);
                          }}>
                {title}
            </DropdownItem>
        );
    }

    if (!zoom_state)
        return null;

    return (
        <div>
            <Dropdown
                isOpen={isOpen}
                toggle={<DropdownToggle onToggle={(_, isOpen) => setIsOpen(isOpen)}>{format_range(zoom_state.x_range)}</DropdownToggle>}
                dropdownItems={[
                    <DropdownItem key="now" onClick={() => { zoom_state.goto_now(); setIsOpen(false) }}>
                        {_("Go to now")}
                    </DropdownItem>,
                    <DropdownSeparator key="sep" />,
                    range_item(5 * 60, _("5 minutes")),
                    range_item(60 * 60, _("1 hour")),
                    range_item(6 * 60 * 60, _("6 hours")),
                    range_item(24 * 60 * 60, _("1 day")),
                    range_item(7 * 24 * 60 * 60, _("1 week"))
                ]} />
            { "\n" }
            <Button variant="secondary" onClick={() => zoom_state.zoom_out()}
                    isDisabled={!zoom_state.enable_zoom_out}>
                <SearchMinusIcon />
            </Button>
            { "\n" }
            <Button variant="secondary" onClick={() => zoom_state.scroll_left()}
                    isDisabled={!zoom_state.enable_scroll_left}>
                <AngleLeftIcon />
            </Button>
            <Button variant="secondary" onClick={() => zoom_state.scroll_right()}
                    isDisabled={!zoom_state.enable_scroll_right}>
                <AngleRightIcon />
            </Button>
        </div>
    );
};

const useLayoutSize = (init_width, init_height) => {
    const ref = useRef(null);
    const [size, setSize] = useState({ width: init_width, height: init_height });
    /* eslint-disable react-hooks/exhaustive-deps */
    useLayoutEffect(() => {
        if (ref.current) {
            const rect = ref.current.getBoundingClientRect();
            // Some browsers, such as Bromite, add noise to the result
            // of getBoundingClientRect in order to deter
            // fingerprinting. Let's allow for that by only reacting
            // to significant changes.
            if (Math.abs(rect.width - size.width) > 2 || Math.abs(rect.height - size.height) > 2)
                setSize({ width: rect.width, height: rect.height });
        }
    });
    /* eslint-enable */
    return [ref, size];
};

export const SvgPlot = ({ title, config, plot_state, plot_id, className }) => {
    const [container_ref, container_size] = useLayoutSize(0, 0);
    const [measure_ref, measure_size] = useLayoutSize(36, 20);

    useEvent(plot_state, "plot:" + plot_id);
    useEvent(plot_state, "changed");
    useEvent(window, "resize");

    const [selection, setSelection] = useState(null);

    const chart_data = plot_state.data(plot_id);
    if (!chart_data || chart_data.length == 0)
        return null;

    const t_ticks = time_ticks(chart_data);
    const y_ticks = value_ticks(chart_data, config);

    function make_chart() {
        const w = container_size.width;
        const h = container_size.height;

        if (w == 0 || h == 0)
            return null;

        const x_off = t_ticks.start;
        const x_range = (t_ticks.end - t_ticks.start);
        const y_range = y_ticks.max;

        const tick_length = 5;
        const tick_gap = 3;

        // widest string plus gap plus tick
        const m_left = Math.ceil(measure_size.width) + tick_gap + tick_length;

        // half of the time label so that it pops in fully formed at the far right edge
        const m_right = 30;

        // half a line for the top-half of the top-most y-axis label
        // plus one extra line if there is a unit or a title.
        const m_top = (y_ticks.unit || title ? 1.5 : 0.5) * Math.ceil(measure_size.height);

        // x-axis labels can be up to two lines
        const m_bottom = tick_length + tick_gap + 2 * Math.ceil(measure_size.height);

        function x_coord(x) {
            return (x - x_off) / x_range * (w - m_left - m_right) + m_left;
        }

        function x_value(c) {
            return (c - m_left) / (w - m_left - m_right) * x_range + x_off;
        }

        function y_coord(y) {
            return h - Math.max(y, 0) / y_range * (h - m_top - m_bottom) - m_bottom;
        }

        function cmd(op, x, y) {
            return op + x.toFixed() + "," + y.toFixed() + " ";
        }

        function path(data, hover_arg) {
            let d = cmd("M", m_left, h - m_bottom);
            for (let i = 0; i < data.length; i++) {
                d += cmd("L", x_coord(data[i][0]), y_coord(data[i][1]));
            }
            d += cmd("L", w - m_right, h - m_bottom);
            d += "z";

            return (
                <path key={hover_arg} d={d}
                      role="presentation">
                    <title>{hover_arg}</title>
                </path>
            );
        }

        const paths = [];
        for (let i = chart_data.length - 1; i >= 0; i--)
            paths.push(path(chart_data[i].data, chart_data[i].name || true));

        function start_dragging(event) {
            if (event.button !== 0)
                return;

            const bounds = container_ref.current.getBoundingClientRect();
            const x = event.clientX - bounds.x;
            if (x >= m_left && x < w - m_right)
                setSelection({ start: x, stop: x, left: x, right: x });
        }

        function drag(event) {
            const bounds = container_ref.current.getBoundingClientRect();
            let x = event.clientX - bounds.x;
            if (x < m_left) x = m_left;
            if (x > w - m_right) x = w - m_right;
            setSelection({
                start: selection.start,
                stop: x,
                left: Math.min(selection.start, x),
                right: Math.max(selection.start, x)
            });
        }

        function stop_dragging() {
            const left = x_value(selection.left) / 1000;
            const right = x_value(selection.right) / 1000;
            plot_state.zoom_state.zoom_in(right - left, right);
            setSelection(null);
        }

        function cancel_dragging() {
            setSelection(null);
        }

        // This is a thin transparent rectangle placed at the x-axis,
        // on top of all the graphs.  It prevents bogus hover events
        // for parts of the graph that are zero or very very close to
        // it.
        const hover_guard =
            <rect x={0} y={h - m_bottom - 1} width={w} height={5} fill="transparent" />;

        return (
            <svg width={w} height={h}
                 className="ct-plot"
                 aria-label={title}
                 // TODO: Figure out a way to handle a11y without entirely hiding the live-updating graphs
                 aria-hidden="true"
                 role="img"
                 onMouseDown={plot_state.zoom_state?.enable_zoom_in ? start_dragging : null}
                 onMouseUp={selection ? stop_dragging : null}
                 onMouseMove={selection ? drag : null}
                 onMouseLeave={cancel_dragging}>
                <title>{title}</title>
                <text x={0} y={-20} className="ct-plot-widest" ref={measure_ref} aria-hidden="true">{config.widest_string}</text>
                <rect x={m_left} y={m_top} width={w - m_left - m_right} height={h - m_top - m_bottom}
                      className="ct-plot-border" />
                { y_ticks.unit && <text x={m_left - tick_length - tick_gap} y={0.5 * m_top}
                                        className="ct-plot-unit"
                                        textAnchor="end">
                    {y_ticks.unit}
                </text>
                }
                { title && <text x={m_left} y={0.5 * m_top} className="ct-plot-title">
                    {title}
                </text>
                }
                <g className="ct-plot-lines" role="presentation">
                    { y_ticks.ticks.map((t, i) => <line key={i}
                                                        x1={m_left - tick_length} x2={w - m_right}
                                                        y1={y_coord(t)} y2={y_coord(t)} />) }
                </g>
                <g className="ct-plot-ticks" role="presentation">
                    { t_ticks.ticks.map((t, i) => <line key={i}
                                                        x1={x_coord(t)} x2={x_coord(t)}
                                                        y1={h - m_bottom} y2={h - m_bottom + tick_length} />) }
                </g>
                <g className="ct-plot-paths">
                    { paths }
                </g>
                { hover_guard }
                <g className="ct-plot-axis ct-plot-axis-y" textAnchor="end">
                    { y_ticks.ticks.map((t, i) => <text key={i} x={m_left - tick_length - tick_gap} y={y_coord(t) + 5}>
                        {y_ticks.formatter(t)}
                    </text>) }
                </g>
                <g className="ct-plot-axis ct-plot-axis-x" textAnchor="middle">
                    { t_ticks.ticks.map((t, i) => <text key={i} y={h - m_bottom + tick_length + tick_gap}>
                        { t_ticks.formatter(t).split("\n")
                                .map((s, j) =>
                                    <tspan key={i + "." + j} x={x_coord(t)} dy="1.2em">{s}</tspan>) }
                    </text>) }
                </g>
                { selection &&
                <rect x={selection.left} y={m_top} width={selection.right - selection.left} height={h - m_top - m_bottom}
                        className="ct-plot-selection" /> }
            </svg>
        );
    }

    return (
        <div className={className} ref={container_ref}>
            {make_chart()}
        </div>);
};

export const bytes_config = {
    base_unit: 1024,
    min_max: 10240,
    pull_out_unit: true,
    widest_string: "MiB",
    formatter: cockpit.format_bytes
};

export const bytes_per_sec_config = {
    base_unit: 1024,
    min_max: 10240,
    pull_out_unit: true,
    widest_string: "MiB/s",
    formatter: cockpit.format_bytes_per_sec
};

export const bits_per_sec_config = {
    base_unit: 1000,
    min_max: 10000,
    pull_out_unit: true,
    widest_string: "Mbps",
    formatter: cockpit.format_bits_per_sec
};
0707010000001C000081A4000000000000000000000001662A077800000AD6000000000000000000000000000000000000003D00000000cockpit-docker-devel-16/pkg/lib/cockpit-components-plot.scss// Selected set of PF chart colors to optimize for full-spectrum and colorblindness
// using unique part of PF name and a hex color fallback
$plotColors: (
  blue-300   #06c,
  green-100  #bde2b9,
  cyan-200   #73c5c5,
  purple-100 #b2b0ea,
  gold-300   #f4c145,
  orange-300 #ec7a08,
  red-200    #a30000,
  cyan-300   #009596,
  black-500  #4d5258
);

.ct-plot {
  font-family: var(--pf-v5-chart-global--FontFamily);

  &-border {
    stroke: var(--pf-v5-chart-global--Fill--Color--300);
    fill: transparent;
    shape-rendering: crispedges;
  }

  &-title {
    font-size: calc(var(--pf-v5-chart-global--FontSize--md) * 1px);
  }

  // Placeholder string to stretch the column, set offscreen
  &-widest {
    fill: transparent;
  }

  &-axis,
  &-unit {
    font-size: calc(var(--pf-v5-chart-global--FontSize--xs) * 1px);
    fill: var(--pf-v5-chart-global--Fill--Color--700);
    letter-spacing: var(--pf-v5-chart-global--letter-spacing);
  }

  .pf-v5-theme-dark &-axis,
  .pf-v5-theme-dark &-unit {
    fill: var(--pf-v5-chart-global--Fill--Color--400);
  }

  &-lines,
  &-ticks {
    stroke: var(--pf-v5-chart-global--Fill--Color--300);
    shape-rendering: crispedges;
  }

  &-selection {
    fill: tan;
    stroke: black;
    opacity: 0.5;
    shape-rendering: crispedges;
  }

  &-paths {
    stroke-width: var(--pf-v5-chart-global--stroke--Width--sm);
    shape-rendering: geometricprecision;

    > path {
      fill: var(--ct-plot-path-color);
      stroke: var(--ct-plot-path-color);
    }
  }
}

.ct-plot-title {
  fill: var(--pf-v5-global--Color--100);
}

$plotColorCurrent: 0;
$plotColorTotal: 0;

// Count up total number of plot colors
@each $plotColor in $plotColors { $plotColorTotal: $plotColorTotal + 1; }

// Iterate through colors and set each graph area to a color
@each $plotColor, $plotColorBackup in $plotColors {
  $plotColorCurrent: $plotColorCurrent + 1;
  .ct-plot-paths > path:nth-last-child(#{$plotColorTotal}n + #{$plotColorCurrent}) {
    --ct-plot-path-color: var(--pf-v5-chart-color-#{$plotColor}, #{$plotColorBackup});
  }
}

// Make plot colors available to the entire page
:root {
  --ct-plot-color-total: #{$plotColorTotal};

  @for $i from 1 through $plotColorTotal {
    --ct-plot-color-#{i}: #{$plotColors[$i]};
  }
}

[dir="rtl"] {
  // Mirror the entire graph (placement & animation)
  .ct-plot {
    transform: scaleX(-1);

    // Flip the text back (so it's not a mirror image)
    text {
      transform: scaleX(-1);
      transform-origin: 50%;
      transform-box: fill-box;
    }

    // Set the anchor point for y-axis and units
    .ct-plot-axis-y text,
    .ct-plot-unit {
      transform-origin: 0%;
    }

    // Set the anchor point for the title
    .ct-plot-title {
      transform-origin: 100%;
    }
  }
}
0707010000001D000081A4000000000000000000000001662A077800000BA1000000000000000000000000000000000000004200000000cockpit-docker-devel-16/pkg/lib/cockpit-components-privileged.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
 */
import React from 'react';
import PropTypes from 'prop-types';

import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { Tooltip, TooltipPosition } from "@patternfly/react-core/dist/esm/components/Tooltip/index.js";

import cockpit from "cockpit";
import { superuser } from 'superuser';
import { useEvent, useLoggedInUser } from "hooks";

/**
 * UI element wrapper for something that requires privilege. When access is not
 * allowed, then wrap the element into a Tooltip.
 *
 * Note that the wrapped element itself needs to be disabled explicitly, this
 * wrapper cannot do this (unfortunately wrapping it into a disabled span does
 * not inherit).
 */
export function Privileged({ excuse, allowed, placement, tooltipId, children }) {
    // wrap into extra <span> so that a disabled child keeps the tooltip working
    let contents = <span id={allowed ? null : tooltipId}>{ children }</span>;
    if (!allowed) {
        contents = (
            <Tooltip position={ placement || TooltipPosition.top} id={ tooltipId + "_tooltip" }
                     content={ excuse }>
                { contents }
            </Tooltip>);
    }
    return contents;
}

/**
 * Convenience element for a Privilege wrapped Button
 */
export const PrivilegedButton = ({ tooltipId, placement, excuse, buttonId, onClick, ariaLabel, variant, isDanger, children }) => {
    const user = useLoggedInUser();
    useEvent(superuser, "changed");

    return (
        <Privileged allowed={ superuser.allowed } tooltipId={ tooltipId } placement={ placement }
                    excuse={ cockpit.format(excuse, user?.name ?? '') }>
            <Button id={ buttonId } variant={ variant } isDanger={ isDanger } onClick={ onClick }
                    isInline isDisabled={ !superuser.allowed } aria-label={ ariaLabel }>
                { children }
            </Button>
        </Privileged>
    );
};

PrivilegedButton.propTypes = {
    excuse: PropTypes.string.isRequired, // must contain a $0, replaced with user name
    onClick: PropTypes.func,
    variant: PropTypes.string,
    placement: PropTypes.string, // default: top
    buttonId: PropTypes.string,
    tooltipId: PropTypes.string,
    ariaLabel: PropTypes.string,
};
0707010000001E000081A4000000000000000000000001662A077800002E20000000000000000000000000000000000000004000000000cockpit-docker-devel-16/pkg/lib/cockpit-components-shutdown.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
 */

import cockpit from "cockpit";
import React from 'react';
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { Select, SelectOption } from "@patternfly/react-core/dist/esm/deprecated/components/Select/index.js";
import { Modal } from "@patternfly/react-core/dist/esm/components/Modal/index.js";
import { Alert } from "@patternfly/react-core/dist/esm/components/Alert/index.js";
import { Flex } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
import { Form, FormGroup } from "@patternfly/react-core/dist/esm/components/Form/index.js";
import { Divider } from "@patternfly/react-core/dist/esm/components/Divider/index.js";
import { TextArea } from "@patternfly/react-core/dist/esm/components/TextArea/index.js";
import { DatePicker } from "@patternfly/react-core/dist/esm/components/DatePicker/index.js";
import { TimePicker } from "@patternfly/react-core/dist/esm/components/TimePicker/index.js";

import { ServerTime } from 'serverTime.js';
import * as timeformat from "timeformat.js";
import { DialogsContext } from "dialogs.jsx";
import { FormHelper } from "cockpit-components-form-helper";

import "cockpit-components-shutdown.scss";

const _ = cockpit.gettext;

export class ShutdownModal extends React.Component {
    static contextType = DialogsContext;

    constructor(props) {
        super(props);
        this.date_spawn = null;
        this.state = {
            error: "",
            dateError: "",
            message: "",
            isOpen: false,
            selected: "1",
            dateObject: undefined,
            startDate: undefined,
            date: "",
            time: "",
            when: "+1",
            formFilled: false,
        };
        this.onSubmit = this.onSubmit.bind(this);
        this.updateDate = this.updateDate.bind(this);
        this.updateTime = this.updateTime.bind(this);
        this.calculate = this.calculate.bind(this);
        this.dateRangeValidator = this.dateRangeValidator.bind(this);

        this.server_time = new ServerTime();
    }

    componentDidMount() {
        this.server_time.wait()
                .then(() => {
                    const dateObject = this.server_time.utc_fake_now;
                    const date = timeformat.dateShort(dateObject);
                    const hour = this.server_time.utc_fake_now.getUTCHours();
                    const minute = this.server_time.utc_fake_now.getUTCMinutes();
                    this.setState({
                        dateObject,
                        date,
                        startDate: new Date(dateObject.toDateString()),
                        time: hour.toString().padStart(2, "0") + ":" + minute.toString().padStart(2, "0"),
                    });
                })
                .always(() => this.setState({ formFilled: true }));
    }

    updateDate(value, dateObject) {
        this.setState({ date: value, dateObject }, this.calculate);
    }

    updateTime(value, hour, minute) {
        this.setState({ time: value, hour, minute }, this.calculate);
    }

    calculate() {
        if (this.date_spawn)
            this.date_spawn.close("cancelled");

        if (this.state.selected != "x") {
            this.setState(prevState => ({
                when: "+" + prevState.selected,
                error: "",
                dateError: "",
            }));
            return;
        }

        const time_error = this.state.hour === null || this.state.minute === null;
        const date_error = !this.state.dateObject;

        if (time_error && date_error) {
            this.setState({ dateError: _("Invalid date format and invalid time format") });
            return;
        } else if (time_error) {
            this.setState({ dateError: _("Invalid time format") });
            return;
        } else if (date_error) {
            this.setState({ dateError: _("Invalid date format") });
            return;
        }

        const cmd = ["date", "--date=" + (new Intl.DateTimeFormat('en-us').format(this.state.dateObject)) + " " + this.state.time, "+%s"];
        this.date_spawn = cockpit.spawn(cmd, { err: "message" });
        this.date_spawn.then(data => {
            const input_timestamp = parseInt(data, 10);
            const server_timestamp = parseInt(this.server_time.now.getTime() / 1000, 10);
            let offset = Math.ceil((input_timestamp - server_timestamp) / 60);

            /* If the time in minutes just changed, make it happen now */
            if (offset === -1) {
                offset = 0;
            } else if (offset < 0) { // Otherwise it is a failure
                this.setState({ dateError: _("Cannot schedule event in the past") });
                return;
            }

            this.setState({
                when: "+" + offset,
                error: "",
                dateError: "",
            });
        });
        this.date_spawn.catch(e => {
            if (e.problem == "cancelled")
                return;
            this.setState({ error: e.message });
        });
        this.date_spawn.finally(() => { this.date_spawn = null });
    }

    onSubmit(event) {
        const Dialogs = this.context;
        const arg = this.props.shutdown ? "--poweroff" : "--reboot";
        if (!this.props.shutdown)
            cockpit.hint("restart");

        cockpit.spawn(["shutdown", arg, this.state.when, this.state.message], { superuser: "require", err: "message" })
                .then(this.props.onClose || Dialogs.close)
                .catch(e => this.setState({ error: e }));

        event.preventDefault();
        return false;
    }

    dateRangeValidator(date) {
        if (this.state.startDate && date < this.state.startDate) {
            return _("Cannot schedule event in the past");
        }
        return '';
    }

    render() {
        const Dialogs = this.context;
        const options = [
            <SelectOption value="0" key="0">{_("No delay")}</SelectOption>,
            <Divider key="divider" component="li" />,
            <SelectOption value="1" key="1">{_("1 minute")}</SelectOption>,
            <SelectOption value="5" key="5">{_("5 minutes")}</SelectOption>,
            <SelectOption value="20" key="20">{_("20 minutes")}</SelectOption>,
            <SelectOption value="40" key="40">{_("40 minutes")}</SelectOption>,
            <SelectOption value="60" key="60">{_("60 minutes")}</SelectOption>,
            <Divider key="divider-2" component="li" />,
            <SelectOption value="x" key="x">{_("Specific time")}</SelectOption>
        ];

        return (
            <Modal isOpen position="top" variant="medium"
                   onClose={this.props.onClose || Dialogs.close}
                   id="shutdown-dialog"
                   title={this.props.shutdown ? _("Shut down") : _("Reboot")}
                   footer={<>
                       <Button variant='danger' isDisabled={this.state.error || this.state.dateError} onClick={this.onSubmit}>{this.props.shutdown ? _("Shut down") : _("Reboot")}</Button>
                       <Button variant='link' onClick={this.props.onClose || Dialogs.close}>{_("Cancel")}</Button>
                   </>}
            >
                <>
                    <Form isHorizontal onSubmit={this.onSubmit}>
                        <FormGroup fieldId="message" label={_("Message to logged in users")}>
                            <TextArea id="message" resizeOrientation="vertical" value={this.state.message} onChange={(_, v) => this.setState({ message: v })} />
                        </FormGroup>
                        <FormGroup fieldId="delay" label={_("Delay")}>
                            <Flex className="shutdown-delay-group" alignItems={{ default: 'alignItemsCenter' }}>
                                <Select toggleId="delay" isOpen={this.state.isOpen} selections={this.state.selected}
                                        isDisabled={!this.state.formFilled}
                                        className='shutdown-select-delay'
                                        onToggle={(_event, o) => this.setState({ isOpen: o })} menuAppendTo="parent"
                                        onSelect={(e, s) => this.setState({ selected: s, isOpen: false }, this.calculate)}>
                                    {options}
                                </Select>
                                {this.state.selected === "x" && <>
                                    <DatePicker aria-label={_("Pick date")}
                                                buttonAriaLabel={_("Toggle date picker")}
                                                className='shutdown-date-picker'
                                                dateFormat={timeformat.dateShort}
                                                // https://github.com/patternfly/patternfly-react/issues/9721
                                                dateParse={date => {
                                                    const newDate = timeformat.parseShortDate(date);
                                                    return Number.isNaN(newDate.valueOf()) ? false : newDate;
                                                }}
                                                invalidFormatText=""
                                                isDisabled={!this.state.formFilled}
                                                locale={timeformat.dateFormatLang()}
                                                weekStart={timeformat.firstDayOfWeek()}
                                                onBlur={this.calculate}
                                                onChange={(_, d, ds) => this.updateDate(d, ds)}
                                                placeholder={timeformat.dateShortFormat()}
                                                validators={[this.dateRangeValidator]}
                                                value={this.state.date}
                                                appendTo={() => document.body} />
                                    <TimePicker time={this.state.time} is24Hour
                                                className='shutdown-time-picker'
                                                id="shutdown-time"
                                                isDisabled={!this.state.formFilled}
                                                invalidFormatErrorMessage=""
                                                menuAppendTo={() => document.body}
                                                onBlur={this.calculate}
                                                onChange={(_, time, h, m) => this.updateTime(time, h, m) } />
                                </>}
                            </Flex>
                            <FormHelper fieldId="delay" helperTextInvalid={this.state.dateError} />
                        </FormGroup>
                    </Form>
                    {this.state.error && <Alert isInline variant='danger' title={this.state.error} />}
                </>
            </Modal>
        );
    }
}
0707010000001F000081A4000000000000000000000001662A07780000013A000000000000000000000000000000000000004100000000cockpit-docker-devel-16/pkg/lib/cockpit-components-shutdown.scss#shutdown-group {
  overflow: visible;
}

.shutdown-delay-group {
  // Add spacing between rows for when the flex items wrap
  row-gap: var(--pf-v5-global--spacer--sm);

  .shutdown-time-picker {
    max-inline-size: 7rem;
  }

  .shutdown-select-delay,
  .shutdown-date-picker {
    max-inline-size: 10rem;
  }
}
07070100000020000081A4000000000000000000000001662A0778000031AB000000000000000000000000000000000000003D00000000cockpit-docker-devel-16/pkg/lib/cockpit-components-table.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
 */

import React, { useState, useEffect } from 'react';
import {
    ExpandableRowContent,
    Table, Thead, Tbody, Tr, Th, Td,
    SortByDirection,
} from '@patternfly/react-table';
import { EmptyState, EmptyStateBody, EmptyStateFooter, EmptyStateActions } from "@patternfly/react-core/dist/esm/components/EmptyState/index.js";
import { Text, TextContent, TextVariants } from "@patternfly/react-core/dist/esm/components/Text/index.js";

import './cockpit-components-table.scss';

/* This is a wrapper around PF Table component
 * See https://www.patternfly.org/components/table/
 * Properties (all optional unless specified otherwise):
 * - caption
 * - id: optional identifier
 * - className: additional classes added to the Table
 * - actions: additional listing-wide actions (displayed next to the list's title)
 * - columns: { title: string, header: boolean, sortable: boolean }[] or string[]
 * - rows: {
 *      columns: (React.Node or string or { title: string, key: string, ...extraProps: object}}[]
                 Through extraProps the consumers can pass arbitrary properties to the <td>
 *      props: { key: string, ...extraProps: object }
               This property is mandatory and should contain a unique `key`, all additional properties are optional.
               Through extraProps the consumers can pass arbitrary properties to the <tr>
 *      expandedContent: (React.Node)[])
 *      selected: boolean option if the row is selected
 *      initiallyExpanded : the entry will be initially rendered as expanded, but then behaves normally
 *   }[]
 * - emptyCaption: header caption to show if list is empty
 * - emptyCaptionDetail: extra details to show after emptyCaption if list is empty
 * - emptyComponent: Whole empty state component to show if the list is empty
 * - isEmptyStateInTable: if empty state is result of a filter function this should be set, otherwise false
 * - loading: Set to string when the content is still loading. This string is shown.
 * - variant: For compact tables pass 'compact'
 * - gridBreakPoint: Specifies the grid breakpoints ('', 'grid' | 'grid-md' | 'grid-lg' | 'grid-xl' | 'grid-2xl')
 * - sortBy: { index: Number, direction: SortByDirection }
 * - sortMethod: callback function used for sorting rows. Called with 3 parameters: sortMethod(rows, activeSortDirection, activeSortIndex)
 * - style: object of additional css rules
 * - afterToggle: function to be called when content is toggled
 * - onSelect: function to be called when a checkbox is clicked. Called with 5 parameters:
 *   event, isSelected, rowIndex, rowData, extraData. rowData contains props with an id property of the clicked row.
 * - onHeaderSelect: event, isSelected.
 */
export const ListingTable = ({
    actions = [],
    afterToggle,
    caption = '',
    className,
    columns: cells = [],
    emptyCaption = '',
    emptyCaptionDetail,
    emptyComponent,
    isEmptyStateInTable = false,
    loading = '',
    onRowClick,
    onSelect,
    onHeaderSelect,
    rows: tableRows = [],
    showHeader = true,
    sortBy,
    sortMethod,
    ...extraProps
}) => {
    let rows = [...tableRows];
    const [expanded, setExpanded] = useState({});
    const [newItems, setNewItems] = useState([]);
    const [currentRowsKeys, setCurrentRowsKeys] = useState([]);
    const [activeSortIndex, setActiveSortIndex] = useState(sortBy?.index ?? 0);
    const [activeSortDirection, setActiveSortDirection] = useState(sortBy?.direction ?? SortByDirection.asc);
    const rowKeys = rows.map(row => row.props?.key)
            .filter(key => key !== undefined);
    const rowKeysStr = JSON.stringify(rowKeys);
    const currentRowsKeysStr = JSON.stringify(currentRowsKeys);

    useEffect(() => {
        // Don't highlight all when the list gets loaded
        const _currentRowsKeys = JSON.parse(currentRowsKeysStr);
        const _rowKeys = JSON.parse(rowKeysStr);

        if (_currentRowsKeys.length !== 0) {
            const new_keys = _rowKeys.filter(key => _currentRowsKeys.indexOf(key) === -1);
            if (new_keys.length) {
                setTimeout(() => setNewItems(items => items.filter(item => new_keys.indexOf(item) < 0)), 4000);
                setNewItems(ni => [...ni, ...new_keys]);
            }
        }

        setCurrentRowsKeys(crk => [...new Set([...crk, ..._rowKeys])]);
    }, [currentRowsKeysStr, rowKeysStr]);

    const isSortable = cells.some(col => col.sortable);
    const isExpandable = rows.some(row => row.expandedContent);

    const tableProps = {};

    /* Basic table properties */
    tableProps.className = "ct-table";
    if (className)
        tableProps.className = tableProps.className + " " + className;
    if (rows.length == 0)
        tableProps.className += ' ct-table-empty';

    const header = (
        (caption || actions.length != 0)
            ? <header className='ct-table-header'>
                <h3 className='ct-table-heading'> {caption} </h3>
                {actions && <div className='ct-table-actions'> {actions} </div>}
            </header>
            : null
    );

    if (loading)
        return <EmptyState>
            <EmptyStateBody>
                {loading}
            </EmptyStateBody>
        </EmptyState>;

    if (rows == 0) {
        let emptyState = null;
        if (emptyComponent)
            emptyState = emptyComponent;
        else
            emptyState = (
                <EmptyState>
                    <EmptyStateBody>
                        <div>{emptyCaption}</div>
                        <TextContent>
                            <Text component={TextVariants.small}>
                                {emptyCaptionDetail}
                            </Text>
                        </TextContent>
                    </EmptyStateBody>
                    {actions.length > 0 &&
                    <EmptyStateFooter>
                        <EmptyStateActions>{actions}</EmptyStateActions>
                    </EmptyStateFooter>}
                </EmptyState>
            );
        if (!isEmptyStateInTable)
            return emptyState;

        const emptyStateCell = (
            [{
                props: { colSpan: cells.length },
                title: emptyState
            }]
        );

        rows = [{ columns: emptyStateCell }];
    }

    const sortRows = () => {
        const sortedRows = rows.sort((a, b) => {
            const aitem = a.columns[activeSortIndex];
            const bitem = b.columns[activeSortIndex];

            return ((typeof aitem == 'string' ? aitem : (aitem.sortKey || aitem.title)).localeCompare(typeof bitem == 'string' ? bitem : (bitem.sortKey || bitem.title)));
        });
        return activeSortDirection === SortByDirection.asc ? sortedRows : sortedRows.reverse();
    };

    const onSort = (event, index, direction) => {
        setActiveSortIndex(index);
        setActiveSortDirection(direction);
    };

    const rowsComponents = (isSortable ? (sortMethod ? sortMethod(rows, activeSortDirection, activeSortIndex) : sortRows()) : rows).map((row, rowIndex) => {
        const rowProps = row.props || {};
        if (onRowClick) {
            rowProps.isClickable = true;
            rowProps.onRowClick = (event) => onRowClick(event, row);
        }

        if (rowProps.key && newItems.indexOf(rowProps.key) >= 0)
            rowProps.className = (rowProps.className || "") + " ct-new-item";

        const rowKey = rowProps.key || rowIndex;
        const isExpanded = expanded[rowKey] === undefined ? !!row.initiallyExpanded : expanded[rowKey];
        const rowPair = (
            <React.Fragment key={rowKey + "-inner-row"}>
                <Tr {...rowProps}>
                    {isExpandable
                        ? (row.expandedContent
                            ? <Td expand={{
                                rowIndex: rowKey,
                                isExpanded,
                                onToggle: () => {
                                    if (afterToggle)
                                        afterToggle(!expanded[rowKey]);
                                    setExpanded({ ...expanded, [rowKey]: !expanded[rowKey] });
                                }
                            }} />
                            : <Td className="pf-v5-c-table__toggle" />)
                        : null
                    }
                    {onSelect &&
                        <Td select={{
                            rowIndex,
                            onSelect,
                            isSelected: !!row.selected,
                            props: {
                                id: rowKey
                            }
                        }} />
                    }
                    {row.columns.map((cell, cellIndex) => {
                        const { key, ...cellProps } = cell.props || {};
                        const dataLabel = typeof cells[cellIndex] == 'object' ? cells[cellIndex].title : cells[cellIndex];
                        const colKey = dataLabel || cellIndex;
                        if (cells[cellIndex]?.header)
                            return (
                                <Th key={key || `row_${rowKey}_cell_${colKey}`} dataLabel={dataLabel} {...cellProps}>
                                    {typeof cell == 'object' ? cell.title : cell}
                                </Th>
                            );

                        return (
                            <Td key={key || `row_${rowKey}_cell_${colKey}`} dataLabel={dataLabel} {...cellProps}>
                                {typeof cell == 'object' ? cell.title : cell}
                            </Td>
                        );
                    })}
                </Tr>
                {row.expandedContent && <Tr id={"expanded-content" + rowIndex} isExpanded={isExpanded}>
                    <Td noPadding={row.hasPadding !== true} colSpan={row.columns.length + 1 + (onSelect ? 1 : 0)}>
                        <ExpandableRowContent>{row.expandedContent}</ExpandableRowContent>
                    </Td>
                </Tr>}
            </React.Fragment>
        );

        return <Tbody key={rowKey} isExpanded={row.expandedContent && isExpanded}>{rowPair}</Tbody>;
    });

    return (
        <>
            {header}
            <Table {...extraProps} {...tableProps}>
                {showHeader && <Thead>
                    <Tr>
                        {isExpandable && <Th />}
                        {!onHeaderSelect && onSelect && <Th />}
                        {onHeaderSelect && onSelect && <Th select={{
                            onSelect: onHeaderSelect,
                            isSelected: rows.every(r => r.selected)
                        }} />}
                        {cells.map((column, columnIndex) => {
                            const columnProps = column.props;
                            const sortParams = (
                                column.sortable
                                    ? {
                                        sort: {
                                            sortBy: {
                                                index: activeSortIndex,
                                                direction: activeSortDirection
                                            },
                                            onSort,
                                            columnIndex
                                        }
                                    }
                                    : {}
                            );

                            return (
                                <Th key={columnIndex} {...columnProps} {...sortParams}>
                                    {typeof column == 'object' ? column.title : column}
                                </Th>
                            );
                        })}
                    </Tr>
                </Thead>}
                {rowsComponents}
            </Table>
        </>
    );
};
07070100000021000081A4000000000000000000000001662A077800000AD8000000000000000000000000000000000000003E00000000cockpit-docker-devel-16/pkg/lib/cockpit-components-table.scss@import "global-variables";

.ct-table {
  &.pf-m-compact {
    > thead, > tbody {
      > tr:not(.pf-v5-c-table__expandable-row) {
        // We actually want the normal font size for our lists
        --pf-v5-c-table-cell--FontSize: var(--pf-v5-global--FontSize--md);
      }
    }
  }

  &-header {
    display: flex;
    align-items: center;
    flex-wrap: wrap;

    > :only-child {
      flex: auto;
    }
  }

  &-heading {
    // Push buttons to the right by stretching the heading
    flex: auto;
    // Add a bit of minimum margin to the right of the heading
    margin-inline-end: var(--pf-v5-global--spacer--md);
    // Set a minimum height of 3rem, so when buttons wrap, there's spacing
    min-block-size: var(--pf-v5-global--spacer--2xl);
    // Make sure textual content is aligned to the center
    display: flex;
    align-items: center;
  }

  &-actions {
    > * {
      margin-block: var(--pf-v5-global--spacer--xs);
      margin-inline: var(--pf-v5-global--spacer--sm) 0;
    }

    > :first-child {
      margin-inline-start: 0;
    }
  }

  // https://github.com/patternfly/patternfly-react/issues/5379
  &-empty {
    [data-label] {
      display: revert;
    }

    [data-label]::before {
      display: none;
    }
  }

  // Don't wrap labels
  [data-label]::before {
    white-space: nowrap;
  }

  // Fix toggle button alignment
  .pf-v5-c-table__toggle {
    // Workaround: Chrome sometimes oddly expands the table,
    // unless a width is set. (This affects panels the most, but not only.)
    // As the width is smaller than the contents, and this is a table,
    // the cell will stay at the correct width.
    inline-size: 1px;
  }

  // Properly align actions on the end
  > tbody > tr > td:last-child > .btn-group {
    display: flex;
    justify-content: flex-end;
    align-items: center;
  }

  // Use PF4 style headings
  > thead th {
    font-size: var(--pf-v5-global--FontSize--sm);
    font-weight: var(--pf-v5-global--FontWeight--bold);
  }

  // Adjust the padding for nested ct-tables in ct-tables
  // FIXME: https://github.com/patternfly/patternfly/issues/4280
  .ct-table {
    td, th {
      &:first-child {
        --pf-v5-c-table--nested--first-last-child--PaddingLeft: var(--pf-v5-global--spacer--lg);
      }

      &:last-child {
        --pf-v5-c-table--nested--first-last-child--PaddingRight: var(--pf-v5-global--spacer--lg);
      }
    }
  }
}

// Special handling for rows with errors
.pf-v5-c-table tbody tr:first-child.error {
  &, tbody.pf-m-expanded > & {
    background-color: var(--ct-color-list-critical-bg) !important; /* keep red background when expanded */
    border-block-start: 1px solid var(--ct-color-list-critical-border);
    border-block-end: 1px solid var(--ct-color-list-critical-border);
  }
}
07070100000022000081A4000000000000000000000001662A077800003037000000000000000000000000000000000000004000000000cockpit-docker-devel-16/pkg/lib/cockpit-components-terminal.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
 */

import React from "react";
import PropTypes from "prop-types";
import { Modal } from "@patternfly/react-core/dist/esm/components/Modal/index.js";
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { MenuList, MenuItem } from "@patternfly/react-core/dist/esm/components/Menu";
import { Terminal as Term } from "xterm";
import { CanvasAddon } from 'xterm-addon-canvas';

import { ContextMenu } from "cockpit-components-context-menu.jsx";
import cockpit from "cockpit";

import "console.css";

const _ = cockpit.gettext;

const theme_core = {
    yellow: "#b58900",
    brightRed: "#cb4b16",
    red: "#dc322f",
    magenta: "#d33682",
    brightMagenta: "#6c71c4",
    blue: "#268bd2",
    cyan: "#2aa198",
    green: "#859900"
};

const themes = {
    "black-theme": {
        background: "#000000",
        foreground: "#ffffff"
    },
    "dark-theme": Object.assign({}, theme_core, {
        background: "#002b36",
        foreground: "#fdf6e3",
        cursor: "#eee8d5",
        selection: "#ffffff77",
        brightBlack: "#002b36",
        black: "#073642",
        brightGreen: "#586e75",
        brightYellow: "#657b83",
        brightBlue: "#839496",
        brightCyan: "#93a1a1",
        white: "#eee8d5",
        brightWhite: "#fdf6e3"
    }),
    "light-theme": Object.assign({}, theme_core, {
        background: "#fdf6e3",
        foreground: "#002b36",
        cursor: "#073642",
        selection: "#00000044",
        brightWhite: "#002b36",
        white: "#073642",
        brightCyan: "#586e75",
        brightBlue: "#657b83",
        brightYellow: "#839496",
        brightGreen: "#93a1a1",
        black: "#eee8d5",
        brightBlack: "#fdf6e3"
    }),
    "white-theme": {
        background: "#ffffff",
        foreground: "#000000",
        selection: "#00000044",
        cursor: "#000000",
    },
};

/*
 * A terminal component that communicates over a cockpit channel.
 *
 * The only required property is 'channel', which must point to a cockpit
 * stream channel.
 *
 * The size of the terminal can be set with the 'rows' and 'cols'
 * properties. If those properties are not given, the terminal will fill
 * its container.
 *
 * If the 'onTitleChanged' callback property is set, it will be called whenever
 * the title of the terminal changes.
 *
 * Call focus() to set the input focus on the terminal.
 *
 * Also it is possible to set up theme by property 'theme'.
 */
export class Terminal extends React.Component {
    constructor(props) {
        super(props);
        this.onChannelMessage = this.onChannelMessage.bind(this);
        this.onChannelClose = this.onChannelClose.bind(this);
        this.connectChannel = this.connectChannel.bind(this);
        this.disconnectChannel = this.disconnectChannel.bind(this);
        this.reset = this.reset.bind(this);
        this.focus = this.focus.bind(this);
        this.onWindowResize = this.onWindowResize.bind(this);
        this.resizeTerminal = this.resizeTerminal.bind(this);
        this.onFocusIn = this.onFocusIn.bind(this);
        this.onFocusOut = this.onFocusOut.bind(this);
        this.setText = this.setText.bind(this);
        this.getText = this.getText.bind(this);
        this.setTerminalTheme = this.setTerminalTheme.bind(this);

        const term = new Term({
            cols: props.cols || 80,
            rows: props.rows || 25,
            screenKeys: true,
            cursorBlink: true,
            fontSize: props.fontSize || 16,
            fontFamily: 'Menlo, Monaco, Consolas, monospace',
            screenReaderMode: true,
            showPastingModal: false,
        });

        this.terminalRef = React.createRef();

        term.onData(function(data) {
            if (this.props.channel.valid)
                this.props.channel.send(data);
        }.bind(this));

        if (props.onTitleChanged)
            term.onTitleChange(props.onTitleChanged);

        this.terminal = term;
        this.state = {
            showPastingModal: false,
            cols: props.cols || 80,
            rows: props.rows || 25
        };
    }

    componentDidMount() {
        this.terminal.open(this.terminalRef.current);
        this.terminal.loadAddon(new CanvasAddon());
        this.connectChannel();

        if (!this.props.rows) {
            window.addEventListener('resize', this.onWindowResize);
            this.onWindowResize();
        }
        this.setTerminalTheme(this.props.theme || 'black-theme');
        this.terminal.focus();
    }

    resizeTerminal(cols, rows) {
        this.terminal.resize(cols, rows);
        this.props.channel.control({
            window: {
                rows,
                cols
            }
        });
    }

    componentDidUpdate(prevProps, prevState) {
        if (prevProps.fontSize !== this.props.fontSize) {
            this.terminal.options.fontSize = this.props.fontSize;

            // After font size is changed, resize needs to be triggered
            const dimensions = this.calculateDimensions();
            if (dimensions.cols !== this.state.cols || dimensions.rows !== this.state.rows) {
                this.onWindowResize();
            } else {
                // When font size changes but dimensions are the same, we need to force `resize`
                this.resizeTerminal(dimensions.cols - 1, dimensions.rows);
            }
        }

        if (prevState.cols !== this.state.cols || prevState.rows !== this.state.rows)
            this.resizeTerminal(this.state.cols, this.state.rows);

        if (prevProps.theme !== this.props.theme)
            this.setTerminalTheme(this.props.theme);

        if (prevProps.channel !== this.props.channel) {
            this.terminal.reset();
            this.disconnectChannel(prevProps.channel);
            this.connectChannel();
            this.props.channel.control({
                window: {
                    rows: this.state.rows,
                    cols: this.state.cols
                }
            });
        }
        this.terminal.focus();
    }

    render() {
        const contextMenuList = (
            <MenuList>
                <MenuItem className="contextMenuOption" onClick={this.getText}>
                    <div className="contextMenuName"> { _("Copy") } </div>
                    <div className="contextMenuShortcut">{ _("Ctrl+Insert") }</div>
                </MenuItem>
                <MenuItem className="contextMenuOption" onClick={this.setText}>
                    <div className="contextMenuName"> { _("Paste") } </div>
                    <div className="contextMenuShortcut">{ _("Shift+Insert") }</div>
                </MenuItem>
            </MenuList>
        );

        return (
            <>
                <Modal title={_("Paste error")}
                       position="top"
                       variant="small"
                       isOpen={this.state.showPastingModal}
                       onClose={() => this.setState({ showPastingModal: false })}
                       actions={[
                           <Button key="cancel" variant="secondary" onClick={() => this.setState({ showPastingModal: false })}>
                               {_("Close")}
                           </Button>
                       ]}>
                    {_("Your browser does not allow paste from the context menu. You can use Shift+Insert.")}
                </Modal>
                <div ref={this.terminalRef}
                     key={this.terminal}
                     className="console-ct"
                     onFocus={this.onFocusIn}
                     onContextMenu={this.contextMenu}
                     onBlur={this.onFocusOut} />
                <ContextMenu parentId={this.props.parentId}>
                    {contextMenuList}
                </ContextMenu>
            </>
        );
    }

    componentWillUnmount() {
        this.disconnectChannel();
        this.terminal.dispose();
        window.removeEventListener('resize', this.onWindowResize);
        this.onFocusOut();
    }

    setText() {
        try {
            navigator.clipboard.readText()
                    .then(text => this.props.channel.send(text))
                    .catch(e => this.setState({ showPastingModal: true }))
                    .finally(() => this.terminal.focus());
        } catch (error) {
            this.setState({ showPastingModal: true });
        }
    }

    getText() {
        try {
            navigator.clipboard.writeText(this.terminal.getSelection())
                    .catch(e => console.error('Text could not be copied, use Ctrl+Insert ', e ? e.toString() : ""))
                    .finally(() => this.terminal.focus());
        } catch (error) {
            console.error('Text could not be copied, use Ctrl+Insert:', error.toString());
        }
    }

    onChannelMessage(event, data) {
        this.terminal.write(data);
    }

    onChannelClose(event, options) {
        const term = this.terminal;
        term.write('\x1b[31m' + (options.problem || 'disconnected') + '\x1b[m\r\n');
        term.cursorHidden = true;
        term.refresh(term.rows, term.rows);
    }

    connectChannel() {
        const channel = this.props.channel;
        if (channel?.valid) {
            channel.addEventListener('message', this.onChannelMessage.bind(this));
            channel.addEventListener('close', this.onChannelClose.bind(this));
        }
    }

    disconnectChannel(channel) {
        if (channel === undefined)
            channel = this.props.channel;
        if (channel) {
            channel.removeEventListener('message', this.onChannelMessage);
            channel.removeEventListener('close', this.onChannelClose);
        }
        channel.close();
    }

    reset() {
        this.terminal.reset();
        this.props.channel.send(String.fromCharCode(12)); // Send SIGWINCH to show prompt on attaching
    }

    focus() {
        if (this.terminal)
            this.terminal.focus();
    }

    calculateDimensions() {
        const padding = 10; // Leave a bit of space around terminal
        const realHeight = this.terminal._core._renderService.dimensions.css.cell.height;
        const realWidth = this.terminal._core._renderService.dimensions.css.cell.width;
        if (realHeight && realWidth && realWidth !== 0 && realHeight !== 0)
            return {
                rows: Math.floor((this.terminalRef.current.parentElement.clientHeight - padding) / realHeight),
                cols: Math.floor((this.terminalRef.current.parentElement.clientWidth - padding - 12) / realWidth) // Remove 12px for scrollbar
            };

        return { rows: this.state.rows, cols: this.state.cols };
    }

    onWindowResize() {
        this.setState(this.calculateDimensions());
    }

    setTerminalTheme(theme) {
        this.terminal.options.theme = themes[theme];
    }

    onBeforeUnload(event) {
        // Firefox requires this when the page is in an iframe
        event.preventDefault();

        // see "an almost cross-browser solution" at
        // https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
        event.returnValue = '';
        return '';
    }

    onFocusIn() {
        window.addEventListener('beforeunload', this.onBeforeUnload);
    }

    onFocusOut() {
        window.removeEventListener('beforeunload', this.onBeforeUnload);
    }
}

Terminal.propTypes = {
    cols: PropTypes.number,
    rows: PropTypes.number,
    channel: PropTypes.object.isRequired,
    onTitleChanged: PropTypes.func,
    theme: PropTypes.string,
    parentId: PropTypes.string.isRequired
};
07070100000023000081A4000000000000000000000001662A077800000561000000000000000000000000000000000000004000000000cockpit-docker-devel-16/pkg/lib/cockpit-components-truncate.jsx/*
 * 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 <http://www.gnu.org/licenses/>.
 */

/* This is our version of the PatternFly Truncate component. We have
   it since we don't want Patternfly's unconditional tooltip.

   Truncation in the middle doesn't work with Patternsfly's approach
   in mixed RTL/LTR environments, so we only offer truncation at the
   end.
 */

import * as React from "react";
import './cockpit-components-truncate.scss';

export const Truncate = ({
    content,
    ...props
}) => {
    return (
        <span className="pf-v5-c-truncate ct-no-truncate-min-width" {...props}>
            <span className="pf-v5-c-truncate__start">
                {content}
            </span>
        </span>
    );
};
07070100000024000081A4000000000000000000000001662A077800000080000000000000000000000000000000000000004100000000cockpit-docker-devel-16/pkg/lib/cockpit-components-truncate.scss.pf-v5-c-truncate.ct-no-truncate-min-width {
    --pf-v5-c-truncate--MinWidth: 0;
    --pf-v5-c-truncate__start--MinWidth: 0;
}
07070100000025000081A4000000000000000000000001662A0778000009ED000000000000000000000000000000000000003600000000cockpit-docker-devel-16/pkg/lib/cockpit-dark-theme.js/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2022 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 <http://www.gnu.org/licenses/>.
 */

function debug() {
    if (window.debugging == "all" || window.debugging?.includes("style")) {
        console.debug([`cockpit-dark-theme: ${document.documentElement.id}:`, ...arguments].join(" "));
    }
}

function changeDarkThemeClass(documentElement, dark_mode) {
    debug(`Setting cockpit theme to ${dark_mode ? "dark" : "light"}`);

    if (dark_mode) {
        documentElement.classList.add('pf-v5-theme-dark');
    } else {
        documentElement.classList.remove('pf-v5-theme-dark');
    }
}

function _setDarkMode(_style) {
    const style = _style || localStorage.getItem('shell:style') || 'auto';
    let dark_mode;
    // If a user set's an explicit theme, ignore system changes.
    if ((window.matchMedia?.('(prefers-color-scheme: dark)').matches && style === "auto") || style === "dark") {
        dark_mode = true;
    } else {
        dark_mode = false;
    }
    changeDarkThemeClass(document.documentElement, dark_mode);
}

window.addEventListener("storage", event => {
    if (event.key === "shell:style") {
        debug(`Storage element 'shell:style' changed from  ${event.oldValue} to ${event.newValue}`);

        _setDarkMode();
    }
});

// When changing the theme from the shell switcher the localstorage change will not fire for the same page (aka shell)
// so we need to listen for the event on the window object.
window.addEventListener("cockpit-style", event => {
    const style = event.detail.style;
    debug(`Event received from shell with 'cockpit-style'  ${style}`);

    _setDarkMode(style);
});

window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
    debug(`Operating system theme preference changed to ${window.matchMedia?.('(prefers-color-scheme: dark)').matches ? "dark" : "light"}`);
    _setDarkMode();
});

_setDarkMode();
07070100000026000081A4000000000000000000000001662A077800001363000000000000000000000000000000000000003500000000cockpit-docker-devel-16/pkg/lib/cockpit-po-plugin.jsimport fs from "fs";
import glob from "glob";
import path from "path";

import Jed from "jed";
import gettext_parser from "gettext-parser";

const config = {};

const DEFAULT_WRAPPER = 'cockpit.locale(PO_DATA);';

function get_po_files() {
    try {
        const linguas_file = path.resolve(config.srcdir, "po/LINGUAS");
        const linguas = fs.readFileSync(linguas_file, 'utf8').match(/\S+/g);
        return linguas.map(lang => path.resolve(config.srcdir, 'po', lang + '.po'));
    } catch (error) {
        if (error.code !== 'ENOENT') {
            throw error;
        }

        /* No LINGUAS file?  Fall back to globbing.
         * Note: we won't detect .po files being added in this case.
         */
        return glob.sync(path.resolve(config.srcdir, 'po/*.po'));
    }
}

function get_plural_expr(statement) {
    try {
        /* Check that the plural forms isn't being sneaky since we build a function here */
        Jed.PF.parse(statement);
    } catch (ex) {
        console.error("bad plural forms: " + ex.message);
        process.exit(1);
    }

    const expr = statement.replace(/nplurals=[1-9]; plural=([^;]*);?$/, '(n) => $1');
    if (expr === statement) {
        console.error("bad plural forms: " + statement);
        process.exit(1);
    }

    return expr;
}

function buildFile(po_file, subdir, filename, filter) {
    return new Promise((resolve, reject) => {
        // Read the PO file, remove fuzzy/disabled lines to avoid tripping up the validator
        const po_data = fs.readFileSync(po_file, 'utf8')
                .split('\n')
                .filter(line => !line.startsWith('#~'))
                .join('\n');
        const parsed = gettext_parser.po.parse(po_data, { defaultCharset: 'utf8', validation: true });
        delete parsed.translations[""][""]; // second header copy

        const rtl_langs = ["ar", "fa", "he", "ur"];
        const dir = rtl_langs.includes(parsed.headers.Language) ? "rtl" : "ltr";

        // cockpit.js only looks at "plural-forms" and "language"
        const chunks = [
            '{\n',
            ' "": {\n',
            `  "plural-forms": ${get_plural_expr(parsed.headers['Plural-Forms'])},\n`,
            `  "language": "${parsed.headers.Language}",\n`,
            `  "language-direction": "${dir}"\n`,
            ' }'
        ];
        for (const [msgctxt, context] of Object.entries(parsed.translations)) {
            const context_prefix = msgctxt ? msgctxt + '\u0004' : ''; /* for cockpit.ngettext */

            for (const [msgid, translation] of Object.entries(context)) {
                /* Only include msgids which appear in this source directory */
                const references = translation.comments.reference.split(/\s/);
                if (!references.some(str => str.startsWith(`pkg/${subdir}`) || str.startsWith(config.src_directory) || str.startsWith(`pkg/lib`)))
                    continue;

                if (translation.comments.flag?.match(/\bfuzzy\b/))
                    continue;

                if (!references.some(filter))
                    continue;

                const key = JSON.stringify(context_prefix + msgid);
                // cockpit.js always ignores the first item
                chunks.push(`,\n ${key}: [\n  null`);
                for (const str of translation.msgstr) {
                    chunks.push(',\n  ' + JSON.stringify(str));
                }
                chunks.push('\n ]');
            }
        }
        chunks.push('\n}');

        const wrapper = config.wrapper?.(subdir) || DEFAULT_WRAPPER;
        const output = wrapper.replace('PO_DATA', chunks.join('')) + '\n';

        const out_path = path.join(subdir ? (subdir + '/') : '', filename);
        fs.writeFileSync(path.resolve(config.outdir, out_path), output);
        return resolve();
    });
}

function init(options) {
    config.srcdir = process.env.SRCDIR || './';
    config.subdirs = options.subdirs || [''];
    config.src_directory = options.src_directory || 'src';
    config.wrapper = options.wrapper;
    config.outdir = options.outdir || './dist';
}

function run() {
    const promises = [];
    for (const subdir of config.subdirs) {
        for (const po_file of get_po_files()) {
            const lang = path.basename(po_file).slice(0, -3);
            promises.push(Promise.all([
                // Separate translations for the manifest.json file and normal pages
                buildFile(po_file, subdir, `po.${lang}.js`, str => !str.includes('manifest.json')),
                buildFile(po_file, subdir, `po.manifest.${lang}.js`, str => str.includes('manifest.json'))
            ]));
        }
    }
    return Promise.all(promises);
}

export const cockpitPoEsbuildPlugin = options => ({
    name: 'cockpitPoEsbuildPlugin',
    setup(build) {
        init({ ...options, outdir: build.initialOptions.outdir });
        build.onEnd(async result => { result.errors.length === 0 && await run() });
    },
});
07070100000027000081A4000000000000000000000001662A0778000005C9000000000000000000000000000000000000003800000000cockpit-docker-devel-16/pkg/lib/cockpit-rsync-plugin.jsimport child_process from "child_process";

const config = {};

function init(options) {
    config.dest = options.dest || "";
    config.source = options.source || "dist/";
    config.ssh_host = process.env.RSYNC || process.env.RSYNC_DEVEL;

    // ensure the target directory exists
    if (config.ssh_host) {
        config.rsync_dir = process.env.RSYNC ? "/usr/local/share/cockpit/" : "~/.local/share/cockpit/";
        child_process.spawnSync("ssh", [config.ssh_host, "mkdir", "-p", config.rsync_dir], { stdio: "inherit" });
    }
}

function run(callback) {
    if (config.ssh_host) {
        const proc = child_process.spawn("rsync", ["--recursive", "--info=PROGRESS2", "--delete",
            config.source, config.ssh_host + ":" + config.rsync_dir + config.dest], { stdio: "inherit" });
        proc.on('close', (code) => {
            if (code !== 0) {
                process.exit(1);
            } else {
                callback();
            }
        });
    } else {
        callback();
    }
}

export const cockpitRsyncEsbuildPlugin = options => ({
    name: 'cockpitRsyncPlugin',
    setup(build) {
        init(options || {});
        build.onEnd(result => result.errors.length === 0 ? run(() => {}) : {});
    },
});

export class CockpitRsyncWebpackPlugin {
    constructor(options) {
        init(options || {});
    }

    apply(compiler) {
        compiler.hooks.afterEmit.tapAsync('WebpackHookPlugin', (_compilation, callback) => run(callback));
    }
}
07070100000028000081A4000000000000000000000001662A077800001392000000000000000000000000000000000000003900000000cockpit-docker-devel-16/pkg/lib/cockpit-upload-helper.ts/*
 * 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 <http://www.gnu.org/licenses/>.
 */

import cockpit from 'cockpit';

// These are the same values used by the bridge (in channel.py)
const BLOCK_SIZE = 16 << 10; // 16kiB
const FLOW_WINDOW = 2 << 20; // 2MiB

function debug(...args: unknown[]) {
    if (window.debugging == 'all' || window.debugging?.includes('upload'))
        console.debug('upload', ...args);
}

class UploadError extends Error {
    name = 'UploadError';
}

class Waiter {
    unblock = () => { };
    block(): Promise<void> {
        return new Promise(resolve => { this.unblock = resolve });
    }
}

export async function upload(
    destination: string,
    stream: ReadableStream,
    progress?: (bytes_sent: number) => void,
    signal?: AbortSignal,
    options?: cockpit.JsonObject
) {
    let close_message = null as (cockpit.JsonObject | null);
    let outstanding = 0; // for flow control
    let delivered = 0; // for progress reporting

    // This variable is the most important thing in this function.  The main
    // upload loop will do work for as long as it can, and then it .block()s on
    // the waiter until something changes (ack, close, abort, etc).  All of
    // those things call .unblock() to resume the loop.
    const event_waiter = new Waiter();

    if (signal) {
        signal.throwIfAborted(); // early exit
        signal.addEventListener('abort', event_waiter.unblock);
    }

    const opts = {
        payload: 'fsreplace1',
        path: destination,
        binary: true,
        'send-acks': 'bytes',
        ...options,
    } as const;
    debug('requesting channel', opts);
    const channel = cockpit.channel(opts);
    channel.addEventListener('control', (_ev, message) => {
        debug('control', message);
        if (message.command === 'ack') {
            cockpit.assert(typeof message.bytes === 'number', 'bytes not a number');
            delivered += message.bytes;
            if (progress) {
                debug('progress', delivered);
                progress(delivered);
            }
            outstanding -= message.bytes;
            debug('outstanding -- to', outstanding);
            event_waiter.unblock();
        }
    });
    channel.addEventListener('close', (_ev, message) => {
        debug('close', message);
        close_message = message;
        event_waiter.unblock();
    });

    try {
        debug('starting file send', stream);
        const reader = stream.getReader({ mode: 'byob' }); // byob to choose the block size
        let eof = false;

        // eslint-disable-next-line no-unmodified-loop-condition
        while (!close_message) {
            /* We do the following steps for as long as the channel is open:
             *  - if there is room to write more data, do that
             *  - otherwise, block on the waiter until something changes
             *  - in any case, check for cancellation, repeat
             * The idea here is that each loop iteration will `await` one
             * thing, and once it returns, we need to re-evaluate our state.
             */
            if (!eof && outstanding < FLOW_WINDOW) {
                const { done, value } = await reader.read(new Uint8Array(BLOCK_SIZE));
                if (done) {
                    debug('sending done');
                    channel.control({ command: 'done' });
                    eof = true;
                } else {
                    debug('sending', value.length, 'bytes');
                    channel.send(value);
                    outstanding += value.length;
                    debug('outstanding ++ to', outstanding);
                }
                if (signal) {
                    signal.throwIfAborted();
                }
            } else {
                debug('sleeping', outstanding, 'of', FLOW_WINDOW, 'eof', eof);
                await event_waiter.block();
            }
            if (signal) {
                signal.throwIfAborted();
            }
        }

        if (close_message.problem) {
            throw new UploadError(cockpit.message(close_message));
        } else {
            cockpit.assert(typeof close_message.tag === 'string', "tag missing on close message");
            return close_message.tag;
        }
    } finally {
        debug('finally');
        channel.close(); // maybe we got aborted
    }
}
07070100000029000081A4000000000000000000000001662A0778000023D4000000000000000000000000000000000000002D00000000cockpit-docker-devel-16/pkg/lib/cockpit.d.ts/* This file is part of Cockpit.
 *
 * Copyright (C) 2024 Red Hat, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

declare module 'cockpit' {
    type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
    type JsonObject = Record<string, JsonValue>;

    class BasicError {
        problem: string;
        message: string;
        toString(): string;
    }

    function assert(predicate: unknown, message?: string): asserts predicate;

    export let language: string;

    /* === jQuery compatible promise ============== */

    interface DeferredPromise<T> extends Promise<T> {
        /* jQuery Promise compatibility */
        done(callback: (data: T) => void): DeferredPromise<T>
        fail(callback: (exc: Error) => void): DeferredPromise<T>
        always(callback: () => void): DeferredPromise<T>
        progress(callback: (message: T, cancel: () => void) => void): DeferredPromise<T>
    }

    interface Deferred<T> {
        resolve(): Deferred<T>;
        reject(): Deferred<T>;
        notify(): Deferred<T>;
        promise: DeferredPromise<T>
    }

    function defer<T>(): Deferred<T>;

    /* === Events mix-in ========================= */

    interface EventMap {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        [_: string]: (...args: any[]) => void;
    }

    type EventListener<E extends (...args: unknown[]) => void> =
        (event: CustomEvent<Parameters<E>>, ...args: Parameters<E>) => void;

    interface EventSource<EM extends EventMap> {
        addEventListener<E extends keyof EM>(event: E, listener: EventListener<EM[E]>): void;
        removeEventListener<E extends keyof EM>(event: E, listener: EventListener<EM[E]>): void;
        dispatchEvent<E extends keyof EM>(event: E, ...args: Parameters<EM[E]>): void;
    }

    interface CockpitEvents extends EventMap {
        locationchanged(): void;
        visibilitychange(): void;
    }

    function addEventListener<E extends keyof CockpitEvents>(
        event: E, listener: EventListener<CockpitEvents[E]>
    ): void;
    function removeEventListener<E extends keyof CockpitEvents>(
        event: E, listener: EventListener<CockpitEvents[E]>
    ): void;

    interface ChangedEvents {
        changed(): void;
    }

    /* === Channel =============================== */

    interface ControlMessage extends JsonObject {
        command: string;
    }

    interface ChannelEvents<T> extends EventMap {
        control(options: JsonObject): void;
        ready(options: JsonObject): void;
        close(options: JsonObject): void;
        message(data: T): void;
    }

    interface Channel<T> extends EventSource<ChannelEvents<T>> {
        id: string | null;
        binary: boolean;
        options: JsonObject;
        ready: boolean;
        valid: boolean;
        send(data: T): void;
        control(options: ControlMessage): void;
        wait(): Promise<void>;
        close(options?: JsonObject): void;
    }

    interface ChannelOptions {
        payload: string;
        superuser?: "try" | "require";
        [_: string]: JsonValue | undefined;
    }

    function channel(options: ChannelOptions & { binary?: false; }): Channel<string>;
    function channel(options: ChannelOptions & { binary: true; }): Channel<Uint8Array>;

    /* === cockpit.spawn ============================= */

    interface Spawn<T> extends DeferredPromise<T> {
        input(message: T, stream?: boolean): DeferredPromise<T>;
        stream(callback: (data: T) => void): DeferredPromise<T>;
        close(): void;
    }

    interface SpawnOptions {
        binary?: boolean,
        directory?: string;
        err?: "out" | "ignore" | "message";
        environ?: string[];
        pty?: boolean;
        superuser?: "try" | "require";
    }

    function spawn(
        args: string[],
        options?: SpawnOptions & { binary?: false }
    ): Spawn<string>;
    function spawn(
        args: string[],
        options: SpawnOptions & { binary: true }
    ): Spawn<Uint8Array>;

    /* === cockpit.location ========================== */

    interface Location {
        url_root: string;
        options: { [name: string]: string | Array<string> };
        path: Array<string>;
        href: string;
        go(path: Location | string, options?: { [key: string]: string }): void;
        replace(path: Location | string, options?: { [key: string]: string }): void;
    }

    export const location: Location;

    /* === cockpit.dbus ========================== */

    interface DBusProxyEvents extends EventMap {
        changed(changes: { [property: string]: unknown }): void;
    }

    interface DBusProxy extends EventSource<DBusProxyEvents> {
        valid: boolean;
        [property: string]: unknown;
    }

    interface DBusOptions {
        bus?: string;
        address?: string;
        superuser?: "require" | "try";
        track?: boolean;
    }

    interface DBusClient {
        readonly unique_name: string;
        readonly options: DBusOptions;
        proxy(interface: string, path: string, options?: { watch?: boolean }): DBusProxy;
        close(): void;
    }

    type VariantType = string | Uint8Array | number | boolean | VariantType[];
    interface Variant {
        t: string;
        v: VariantType;
    }

    function dbus(name: string | null, options?: DBusOptions): DBusClient;

    function variant(type: string, value: VariantType): Variant;
    function byte_array(string: string): string;

    /* === cockpit.file ========================== */

    interface FileSyntaxObject<T, B> {
        parse(content: B): T;
        stringify(content: T): B;
    }

    type FileTag = string;

    type FileWatchCallback<T> = (data: T | null, tag: FileTag | null, error: BasicError | null) => void;
    interface FileWatchHandle {
        remove(): void;
    }

    interface FileHandle<T> {
        read(): Promise<T>;
        replace(content: T): Promise<FileTag>;
        watch(callback: FileWatchCallback<T>, options?: { read?: boolean }): FileWatchHandle;
        modify(callback: (data: T) => T): Promise<[T, FileTag]>;
        close(): void;
        path: string;
    }

    type FileOpenOptions = {
        max_read_size?: number;
        superuser?: string;
    };

    function file(
        path: string,
        options?: FileOpenOptions & { binary?: false; syntax?: undefined; }
    ): FileHandle<string>;
    function file(
        path: string,
        options: FileOpenOptions & { binary: true; syntax?: undefined; }
    ): FileHandle<Uint8Array>;
    function file<T>(
        path: string,
        options: FileOpenOptions & { binary?: false; syntax: FileSyntaxObject<T, string>; }
    ): FileHandle<T>;
    function file<T>(
        path: string,
        options: FileOpenOptions & { binary: true; syntax: FileSyntaxObject<T, Uint8Array>; }
    ): FileHandle<T>;

    /* === cockpit.user ========================== */

    type UserInfo = {
        id: number;
        name: string;
        full_name: string;
        groups: Array<string>;
        home: string;
        shell: string;
    };
    export function user(): Promise<UserInfo>;

    /* === String helpers ======================== */

    function message(problem: string | JsonObject): string;

    function gettext(message: string): string;
    function gettext(context: string, message?: string): string;
    function ngettext(message1: string, messageN: string, n: number): string;
    function ngettext(context: string, message1: string, messageN: string, n: number): string;

    function format(format_string: string, ...args: unknown[]): string;

    /* === Number formatting ===================== */

    type FormatOptions = {
        precision?: number;
        base2?: boolean;
    };
    type MaybeNumber = number | null | undefined;

    function format_number(n: MaybeNumber, precision?: number): string
    function format_bytes(n: MaybeNumber, options?: FormatOptions): string;
    function format_bytes_per_sec(n: MaybeNumber, options?: FormatOptions): string;
    function format_bits_per_sec(n: MaybeNumber, options?: FormatOptions & { base2?: false }): string;

    /** @deprecated */ function format_bytes(n: MaybeNumber, factor: unknown, options?: object | boolean): string | string[];
    /** @deprecated */ function format_bytes_per_sec(n: MaybeNumber, factor: unknown, options?: object | boolean): string | string[];
    /** @deprecated */ function format_bits_per_sec(n: MaybeNumber, factor: unknown, options?: object | boolean): string | string[];
}
0707010000002A000081A4000000000000000000000001662A077800022A0E000000000000000000000000000000000000002B00000000cockpit-docker-devel-16/pkg/lib/cockpit.js/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
 */

/* eslint-disable indent,no-empty */

let url_root;

const meta_url_root = document.head.querySelector("meta[name='url-root']");
if (meta_url_root) {
    url_root = meta_url_root.content.replace(/^\/+|\/+$/g, '');
} else {
    // fallback for cockpit-ws < 272
    try {
        // Sometimes this throws a SecurityError such as during testing
        url_root = window.localStorage.getItem('url-root');
    } catch (e) { }
}

/* injected by tests */
var mock = mock || { }; // eslint-disable-line no-use-before-define, no-var

const cockpit = { };
event_mixin(cockpit, { });

/*
 * The debugging property is a global that is used
 * by various parts of the code to show/hide debug
 * messages in the javascript console.
 *
 * We support using storage to get/set that property
 * so that it carries across the various frames or
 * alternatively persists across refreshes.
 */
if (typeof window.debugging === "undefined") {
    try {
        // Sometimes this throws a SecurityError such as during testing
        Object.defineProperty(window, "debugging", {
            get: function() { return window.sessionStorage.debugging || window.localStorage.debugging },
            set: function(x) { window.sessionStorage.debugging = x }
        });
    } catch (e) { }
}

function in_array(array, val) {
    const length = array.length;
    for (let i = 0; i < length; i++) {
        if (val === array[i])
            return true;
    }
    return false;
}

function is_function(x) {
    return typeof x === 'function';
}

function is_object(x) {
    return x !== null && typeof x === 'object';
}

function is_plain_object(x) {
    return is_object(x) && Object.prototype.toString.call(x) === '[object Object]';
}

/* Also works for negative zero */
function is_negative(n) {
    return ((n = +n) || 1 / n) < 0;
}

function invoke_functions(functions, self, args) {
    const length = functions?.length ?? 0;
    for (let i = 0; i < length; i++) {
        if (functions[i])
            functions[i].apply(self, args);
    }
}

function iterate_data(data, callback, batch) {
    let binary = false;
    let len = 0;

    if (!batch)
        batch = 64 * 1024;

    if (data) {
         if (data.byteLength) {
             len = data.byteLength;
             binary = true;
         } else if (data.length) {
             len = data.length;
         }
    }

    for (let i = 0; i < len; i += batch) {
        const n = Math.min(len - i, batch);
        if (binary)
            callback(new window.Uint8Array(data.buffer, i, n));
        else
            callback(data.substr(i, n));
    }
}

/* -------------------------------------------------------------------------
 * Channels
 *
 * Public: https://cockpit-project.org/guide/latest/api-base1.html
 */

let default_transport = null;
let public_transport = null;
let reload_after_disconnect = false;
let expect_disconnect = false;
let init_callback = null;
let default_host = null;
let process_hints = null;
let incoming_filters = null;
let outgoing_filters = null;

let transport_origin = window.location.origin;

if (!transport_origin) {
    transport_origin = window.location.protocol + "//" + window.location.hostname +
        (window.location.port ? ':' + window.location.port : '');
}

function array_from_raw_string(str, constructor) {
    const length = str.length;
    const data = new (constructor || Array)(length);
    for (let i = 0; i < length; i++)
        data[i] = str.charCodeAt(i) & 0xFF;
    return data;
}

function array_to_raw_string(data) {
    const length = data.length;
    let str = "";
    for (let i = 0; i < length; i++)
        str += String.fromCharCode(data[i]);
    return str;
}

/*
 * These are the polyfills from Mozilla. It's pretty nasty that
 * these weren't in the typed array standardization.
 *
 * https://developer.mozilla.org/en-US/docs/Glossary/Base64
 */

function uint6_to_b64 (x) {
    return x < 26 ? x + 65 : x < 52 ? x + 71 : x < 62 ? x - 4 : x === 62 ? 43 : x === 63 ? 47 : 65;
}

function base64_encode(data) {
    if (typeof data === "string")
        return window.btoa(data);
    /* For when the caller has chosen to use ArrayBuffer */
    if (data instanceof window.ArrayBuffer)
        data = new window.Uint8Array(data);
    const length = data.length;
    let mod3 = 2;
    let str = "";
    for (let uint24 = 0, i = 0; i < length; i++) {
        mod3 = i % 3;
        uint24 |= data[i] << (16 >>> mod3 & 24);
        if (mod3 === 2 || length - i === 1) {
            str += String.fromCharCode(uint6_to_b64(uint24 >>> 18 & 63),
                                       uint6_to_b64(uint24 >>> 12 & 63),
                                       uint6_to_b64(uint24 >>> 6 & 63),
                                       uint6_to_b64(uint24 & 63));
            uint24 = 0;
        }
    }

    return str.substr(0, str.length - 2 + mod3) + (mod3 === 2 ? '' : mod3 === 1 ? '=' : '==');
}

function b64_to_uint6 (x) {
    return x > 64 && x < 91
        ? x - 65
        : x > 96 && x < 123
        ? x - 71
        : x > 47 && x < 58 ? x + 4 : x === 43 ? 62 : x === 47 ? 63 : 0;
}

function base64_decode(str, constructor) {
    if (constructor === String)
        return window.atob(str);
    const ilen = str.length;
    let eq;
    for (eq = 0; eq < 3; eq++) {
        if (str[ilen - (eq + 1)] != '=')
            break;
    }
    const olen = (ilen * 3 + 1 >> 2) - eq;
    const data = new (constructor || Array)(olen);
    for (let mod3, mod4, uint24 = 0, oi = 0, ii = 0; ii < ilen; ii++) {
        mod4 = ii & 3;
        uint24 |= b64_to_uint6(str.charCodeAt(ii)) << 18 - 6 * mod4;
        if (mod4 === 3 || ilen - ii === 1) {
            for (mod3 = 0; mod3 < 3 && oi < olen; mod3++, oi++)
                data[oi] = uint24 >>> (16 >>> mod3 & 24) & 255;
            uint24 = 0;
        }
    }
    return data;
}

window.addEventListener('beforeunload', function() {
    expect_disconnect = true;
}, false);

function transport_debug() {
    if (window.debugging == "all" || window.debugging?.includes("channel"))
        console.debug.apply(console, arguments);
}

/*
 * Extends an object to have the standard DOM style addEventListener
 * removeEventListener and dispatchEvent methods. The dispatchEvent
 * method has the additional capability to create a new event from a type
 * string and arguments.
 */
function event_mixin(obj, handlers) {
    Object.defineProperties(obj, {
        addEventListener: {
            enumerable: false,
            value: function addEventListener(type, handler) {
                if (handlers[type] === undefined)
                    handlers[type] = [];
                handlers[type].push(handler);
            }
        },
        removeEventListener: {
            enumerable: false,
            value: function removeEventListener(type, handler) {
                const length = handlers[type] ? handlers[type].length : 0;
                for (let i = 0; i < length; i++) {
                    if (handlers[type][i] === handler) {
                        handlers[type][i] = null;
                        break;
                    }
                }
            }
        },
        dispatchEvent: {
            enumerable: false,
            value: function dispatchEvent(event) {
                let type, args;
                if (typeof event === "string") {
                    type = event;
                    args = Array.prototype.slice.call(arguments, 1);

                    let detail = null;
                    if (arguments.length == 2)
                        detail = arguments[1];
                    else if (arguments.length > 2)
                        detail = args;

                    event = new CustomEvent(type, {
                        bubbles: false,
                        cancelable: false,
                        detail
                    });

                    args.unshift(event);
                } else {
                    type = event.type;
                    args = arguments;
                }
                if (is_function(obj['on' + type]))
                    obj['on' + type].apply(obj, args);
                invoke_functions(handlers[type], obj, args);
            }
        }
    });
}

function calculate_application() {
    let path = window.location.pathname || "/";
    let _url_root = url_root;
    if (window.mock?.pathname)
        path = window.mock.pathname;
    if (window.mock?.url_root)
        _url_root = window.mock.url_root;

    if (_url_root && path.indexOf('/' + _url_root) === 0)
        path = path.replace('/' + _url_root, '') || '/';

    if (path.indexOf("/cockpit/") !== 0 && path.indexOf("/cockpit+") !== 0) {
        if (path.indexOf("/=") === 0)
            path = "/cockpit+" + path.split("/")[1];
        else
            path = "/cockpit";
    }

    return path.split("/")[1];
}

function calculate_url(suffix) {
    if (!suffix)
        suffix = "socket";
    const window_loc = window.location.toString();
    let _url_root = url_root;

    if (window.mock?.url)
        return window.mock.url;
    if (window.mock?.url_root)
        _url_root = window.mock.url_root;

    let prefix = calculate_application();
    if (_url_root)
        prefix = _url_root + "/" + prefix;

    if (window_loc.indexOf('http:') === 0) {
        return "ws://" + window.location.host + "/" + prefix + "/" + suffix;
    } else if (window_loc.indexOf('https:') === 0) {
        return "wss://" + window.location.host + "/" + prefix + "/" + suffix;
    } else {
        transport_debug("Cockpit must be used over http or https");
        return null;
    }
}

function join_data(buffers, binary) {
    if (!binary)
        return buffers.join("");

    let total = 0;
    const length = buffers.length;
    for (let i = 0; i < length; i++)
        total += buffers[i].length;

    const data = window.Uint8Array ? new window.Uint8Array(total) : new Array(total);

    if (data.set) {
        for (let j = 0, i = 0; i < length; i++) {
            data.set(buffers[i], j);
            j += buffers[i].length;
        }
    } else {
        for (let j = 0, i = 0; i < length; i++) {
            for (let k = 0; k < buffers[i].length; k++)
                data[i + j] = buffers[i][k];
            j += buffers[i].length;
        }
    }

    return data;
}

/*
 * A WebSocket that connects to parent frame. The mechanism
 * for doing this will eventually be documented publicly,
 * but for now:
 *
 *  * Forward raw cockpit1 string protocol messages via window.postMessage
 *  * Listen for cockpit1 string protocol messages via window.onmessage
 *  * Never accept or send messages to another origin
 *  * An empty string message means "close" (not completely used yet)
 */
function ParentWebSocket(parent) {
    const self = this;
    self.readyState = 0;

    window.addEventListener("message", function receive(event) {
        if (event.origin !== transport_origin || event.source !== parent)
            return;
        const data = event.data;
        if (data === undefined || (data.length === undefined && data.byteLength === undefined))
            return;
        if (data.length === 0) {
            self.readyState = 3;
            self.onclose();
        } else {
            self.onmessage(event);
        }
    }, false);

    self.send = function send(message) {
        parent.postMessage(message, transport_origin);
    };

    self.close = function close() {
        self.readyState = 3;
        parent.postMessage("", transport_origin);
        self.onclose();
    };

    window.setTimeout(function() {
        self.readyState = 1;
        self.onopen();
    }, 0);
}

function parse_channel(data) {
    let channel;

    /* A binary message, split out the channel */
    if (data instanceof window.ArrayBuffer) {
        const binary = new window.Uint8Array(data);
        const length = binary.length;
        let pos;
        for (pos = 0; pos < length; pos++) {
            if (binary[pos] == 10) /* new line */
                break;
        }
        if (pos === length) {
            console.warn("binary message without channel");
            return null;
        } else if (pos === 0) {
            console.warn("binary control message");
            return null;
        } else {
            channel = String.fromCharCode.apply(null, binary.subarray(0, pos));
        }

    /* A textual message */
    } else {
        const pos = data.indexOf('\n');
        if (pos === -1) {
            console.warn("text message without channel");
            return null;
        }
        channel = data.substring(0, pos);
    }

    return channel;
}

/* Private Transport class */
function Transport() {
    const self = this;
    self.application = calculate_application();

    /* We can trigger events */
    event_mixin(self, { });

    let last_channel = 0;
    let channel_seed = "";

    if (window.mock)
        window.mock.last_transport = self;

    let ws;
    let ignore_health_check = false;
    let got_message = false;

    /* See if we should communicate via parent */
    if (window.parent !== window && window.name.indexOf("cockpit1:") === 0)
        ws = new ParentWebSocket(window.parent);

    let check_health_timer;

    if (!ws) {
        const ws_loc = calculate_url();
        transport_debug("connecting to " + ws_loc);

        if (ws_loc) {
            if ("WebSocket" in window) {
                ws = new window.WebSocket(ws_loc, "cockpit1");
            } else {
                console.error("WebSocket not supported, application will not work!");
            }
        }

        check_health_timer = window.setInterval(function () {
            if (self.ready)
                ws.send("\n{ \"command\": \"ping\" }");
            if (!got_message) {
                if (ignore_health_check) {
                    console.log("health check failure ignored");
                } else {
                    console.log("health check failed");
                    self.close({ problem: "timeout" });
                }
            }
            got_message = false;
        }, 30000);
    }

    if (!ws) {
        ws = { close: function() { } };
        window.setTimeout(function() {
            self.close({ problem: "no-cockpit" });
        }, 50);
    }

    const control_cbs = { };
    const message_cbs = { };
    let waiting_for_init = true;
    self.ready = false;

    /* Called when ready for channels to interact */
    function ready_for_channels() {
        if (!self.ready) {
            self.ready = true;
            self.dispatchEvent("ready");
        }
    }

    ws.onopen = function() {
        if (ws) {
            if (typeof ws.binaryType !== "undefined")
                ws.binaryType = "arraybuffer";
            ws.send("\n{ \"command\": \"init\", \"version\": 1 }");
        }
    };

    ws.onclose = function() {
        transport_debug("WebSocket onclose");
        ws = null;
        if (reload_after_disconnect) {
            expect_disconnect = true;
            window.location.reload(true);
        }
        self.close();
    };

    ws.onmessage = self.dispatch_data = function(arg) {
        got_message = true;

        /* The first line of a message is the channel */
        const message = arg.data;

        const channel = parse_channel(message);
        if (channel === null)
            return false;

        const payload = message instanceof window.ArrayBuffer
            ? new window.Uint8Array(message, channel.length + 1)
            : message.substring(channel.length + 1);
        let control;

        /* A control message, always string */
        if (!channel) {
            transport_debug("recv control:", payload);
            control = JSON.parse(payload);
        } else {
            transport_debug("recv " + channel + ":", payload);
        }

        const length = incoming_filters ? incoming_filters.length : 0;
        for (let i = 0; i < length; i++) {
            if (incoming_filters[i](message, channel, control) === false)
                return false;
        }

        if (!channel)
            process_control(control);
        else
            process_message(channel, payload);

        return true;
    };

    self.close = function close(options) {
        if (!options)
            options = { problem: "disconnected" };
        options.command = "close";
        window.clearInterval(check_health_timer);
        const ows = ws;
        ws = null;
        if (ows)
            ows.close();
        if (expect_disconnect)
            return;
        ready_for_channels(); /* ready to fail */

        /* Broadcast to everyone */
        for (const chan in control_cbs)
            control_cbs[chan].apply(null, [options]);
    };

    self.next_channel = function next_channel() {
        last_channel++;
        return channel_seed + String(last_channel);
    };

    function process_init(options) {
        if (options.problem) {
            self.close({ problem: options.problem });
            return;
        }

        if (options.version !== 1) {
            console.error("received unsupported version in init message: " + options.version);
            self.close({ problem: "not-supported" });
            return;
        }

        if (options["channel-seed"])
            channel_seed = String(options["channel-seed"]);
        if (options.host)
            default_host = options.host;

        if (public_transport) {
            public_transport.options = options;
            public_transport.csrf_token = options["csrf-token"];
            public_transport.host = default_host;
        }

        if (init_callback)
            init_callback(options);

        if (waiting_for_init) {
            waiting_for_init = false;
            ready_for_channels();
        }
    }

    function process_control(data) {
        const channel = data.channel;

        /* Init message received */
        if (data.command == "init") {
            process_init(data);
        } else if (waiting_for_init) {
            waiting_for_init = false;
            if (data.command != "close" || channel) {
                console.error("received message before init: ", data.command);
                data = { problem: "protocol-error" };
            }
            self.close(data);

        /* Any pings get sent back as pongs */
        } else if (data.command == "ping") {
            data.command = "pong";
            self.send_control(data);
        } else if (data.command == "pong") {
            /* Any pong commands are ignored */

        } else if (data.command == "hint") {
            if (process_hints)
                process_hints(data);
        } else if (channel !== undefined) {
            const func = control_cbs[channel];
            if (func)
                func(data);
        }
    }

    function process_message(channel, payload) {
        const func = message_cbs[channel];
        if (func)
            func(payload);
    }

    /* The channel/control arguments is used by filters, and auto-populated if necessary */
    self.send_data = function send_data(data, channel, control) {
        if (!ws) {
            return false;
        }

        const length = outgoing_filters ? outgoing_filters.length : 0;
        for (let i = 0; i < length; i++) {
            if (channel === undefined)
                channel = parse_channel(data);
            if (!channel && control === undefined)
                control = JSON.parse(data);
            if (outgoing_filters[i](data, channel, control) === false)
                return false;
        }

        ws.send(data);
        return true;
    };

    /* The control arguments is used by filters, and auto populated if necessary */
    self.send_message = function send_message(payload, channel, control) {
        if (channel)
            transport_debug("send " + channel, payload);
        else
            transport_debug("send control:", payload);

        /* A binary message */
        if (payload.byteLength || Array.isArray(payload)) {
            if (payload instanceof window.ArrayBuffer)
                payload = new window.Uint8Array(payload);
            const output = join_data([array_from_raw_string(channel), [10], payload], true);
            return self.send_data(output.buffer, channel, control);

        /* A string message */
        } else {
            return self.send_data(channel.toString() + "\n" + payload, channel, control);
        }
    };

    self.send_control = function send_control(data) {
        if (!ws && (data.command == "close" || data.command == "kill"))
            return; /* don't complain if closed and closing */
        if (check_health_timer &&
            data.command == "hint" && data.hint == "ignore_transport_health_check") {
            /* This is for us, process it directly. */
            ignore_health_check = data.data;
            return;
        }
        return self.send_message(JSON.stringify(data), "", data);
    };

    self.register = function register(channel, control_cb, message_cb) {
        control_cbs[channel] = control_cb;
        message_cbs[channel] = message_cb;
    };

    self.unregister = function unregister(channel) {
        delete control_cbs[channel];
        delete message_cbs[channel];
    };
}

function ensure_transport(callback) {
    if (!default_transport)
        default_transport = new Transport();
    const transport = default_transport;
    if (transport.ready) {
        callback(transport);
    } else {
        transport.addEventListener("ready", function() {
            callback(transport);
        });
    }
}

/* Always close the transport explicitly: allows parent windows to track us */
window.addEventListener("unload", function() {
    if (default_transport)
        default_transport.close();
});

function Channel(options) {
    const self = this;

    /* We can trigger events */
    event_mixin(self, { });

    let transport;
    let ready = null;
    let closed = null;
    let waiting = null;
    let received_done = false;
    let sent_done = false;
    let id = null;
    const binary = (options.binary === true);

    /*
     * Queue while waiting for transport, items are tuples:
     * [is_control ? true : false, payload]
     */
    const queue = [];

    /* Handy for callers, but not used by us */
    self.valid = true;
    self.options = options;
    self.binary = binary;
    self.id = id;

    function on_message(payload) {
        if (received_done) {
            console.warn("received message after done");
            self.close("protocol-error");
        } else {
            self.dispatchEvent("message", payload);
        }
    }

    function on_close(data) {
        closed = data;
        self.valid = false;
        if (transport && id)
            transport.unregister(id);
        if (closed.message && !options.err)
            console.warn(closed.message);
        self.dispatchEvent("close", closed);
        if (waiting)
            waiting.resolve(closed);
    }

    function on_ready(data) {
        ready = data;
        self.dispatchEvent("ready", ready);
    }

    function on_control(data) {
        if (data.command == "close") {
            on_close(data);
            return;
        } else if (data.command == "ready") {
            on_ready(data);
        }

        const done = data.command === "done";
        if (done && received_done) {
            console.warn("received two done commands on channel");
            self.close("protocol-error");
        } else {
            if (done)
                received_done = true;
            self.dispatchEvent("control", data);
        }
    }

    function send_payload(payload) {
        if (!binary) {
            if (typeof payload !== "string")
                payload = String(payload);
        }
        transport.send_message(payload, id);
    }

    ensure_transport(function(trans) {
        transport = trans;
        if (closed)
            return;

        id = transport.next_channel();
        self.id = id;

        /* Register channel handlers */
        transport.register(id, on_control, on_message);

        /* Now open the channel */
        const command = { };
        for (const i in options)
            command[i] = options[i];
        command.command = "open";
        command.channel = id;

        if (!command.host) {
            if (default_host)
                command.host = default_host;
        }

        if (binary)
            command.binary = "raw";
        else
            delete command.binary;

        command["flow-control"] = true;
        transport.send_control(command);

        /* Now drain the queue */
        while (queue.length > 0) {
            const item = queue.shift();
            if (item[0]) {
                item[1].channel = id;
                transport.send_control(item[1]);
            } else {
                send_payload(item[1]);
            }
        }
    });

    self.send = function send(message) {
        if (closed)
            console.warn("sending message on closed channel");
        else if (sent_done)
            console.warn("sending message after done");
        else if (!transport)
            queue.push([false, message]);
        else
            send_payload(message);
    };

    self.control = function control(options) {
        options = options || { };
        if (!options.command)
            options.command = "options";
        if (options.command === "done")
            sent_done = true;
        options.channel = id;
        if (!transport)
            queue.push([true, options]);
        else
            transport.send_control(options);
    };

    self.wait = function wait(callback) {
        if (!waiting) {
            waiting = cockpit.defer();
            if (closed) {
                waiting.reject(closed);
            } else if (ready) {
                waiting.resolve(ready);
            } else {
                self.addEventListener("ready", function(event, data) {
                    waiting.resolve(data);
                });
                self.addEventListener("close", function(event, data) {
                    waiting.reject(data);
                });
            }
        }
        const promise = waiting.promise;
        if (callback)
            promise.then(callback, callback);
        return promise;
    };

    self.close = function close(options) {
        if (closed)
            return;

        if (!options)
            options = { };
        else if (typeof options == "string")
            options = { problem: options };
        options.command = "close";
        options.channel = id;

        if (!transport)
            queue.push([true, options]);
        else
            transport.send_control(options);
        on_close(options);
    };

    self.buffer = function buffer(callback) {
        const buffers = [];
        buffers.callback = callback;
        buffers.squash = function squash() {
            return join_data(buffers, binary);
        };

        function on_message(event, data) {
            buffers.push(data);
            if (buffers.callback) {
                const block = join_data(buffers, binary);
                if (block.length > 0) {
                    const consumed = buffers.callback.call(self, block);
                    if (typeof consumed !== "number" || consumed === block.length) {
                        buffers.length = 0;
                    } else if (consumed === 0) {
                        buffers.length = 1;
                        buffers[0] = block;
                    } else if (consumed !== 0) {
                        buffers.length = 1;
                        if (block.subarray)
                            buffers[0] = block.subarray(consumed);
                        else if (block.substring)
                            buffers[0] = block.substring(consumed);
                        else
                            buffers[0] = block.slice(consumed);
                    }
                }
            }
        }

        function on_close() {
            self.removeEventListener("message", on_message);
            self.removeEventListener("close", on_close);
        }

        self.addEventListener("message", on_message);
        self.addEventListener("close", on_close);

        return buffers;
    };

    self.toString = function toString() {
        const host = options.host || "localhost";
        return "[Channel " + (self.valid ? id : "<invalid>") + " -> " + host + "]";
    };
}

/* Resolve dots and double dots */
function resolve_path_dots(parts) {
    const out = [];
    const length = parts.length;
    for (let i = 0; i < length; i++) {
        const part = parts[i];
        if (part === "" || part == ".") {
            continue;
        } else if (part == "..") {
            if (out.length === 0)
                return null;
            out.pop();
        } else {
            out.push(part);
        }
    }
    return out;
}

function factory() {
    cockpit.channel = function channel(options) {
        return new Channel(options);
    };

    cockpit.event_target = function event_target(obj) {
        event_mixin(obj, { });
        return obj;
    };

    /* obsolete backwards compatible shim */
    cockpit.extend = Object.assign;

    /* These can be filled in by loading ../manifests.js */
    cockpit.manifests = { };

    /* ------------------------------------------------------------
     * Text Encoding
     */

    function Utf8TextEncoder(constructor) {
        const self = this;
        self.encoding = "utf-8";

        self.encode = function encode(string, options) {
            const data = window.unescape(encodeURIComponent(string));
            if (constructor === String)
                return data;
            return array_from_raw_string(data, constructor);
        };
    }

    function Utf8TextDecoder(fatal) {
        const self = this;
        let buffer = null;
        self.encoding = "utf-8";

        self.decode = function decode(data, options) {
            const stream = options?.stream;

            if (data === null || data === undefined)
                data = "";
            if (typeof data !== "string")
                data = array_to_raw_string(data);
            if (buffer) {
                data = buffer + data;
                buffer = null;
            }

            /* We have to scan to do non-fatal and streaming */
            const len = data.length;
            let beg = 0;
            let i = 0;
            let str = "";

            while (i < len) {
                const p = data.charCodeAt(i);
                const x = p == 255
                    ? 0
                    : p > 251 && p < 254
                    ? 6
                    : p > 247 && p < 252
                    ? 5
                    : p > 239 && p < 248
                    ? 4
                    : p > 223 && p < 240
                    ? 3
                    : p > 191 && p < 224
                    ? 2
                    : p < 128 ? 1 : 0;

                let ok = (i + x <= len);
                if (!ok && stream) {
                    buffer = data.substring(i);
                    break;
                }
                if (x === 0)
                    ok = false;
                for (let j = 1; ok && j < x; j++)
                    ok = (data.charCodeAt(i + j) & 0x80) !== 0;

                if (!ok) {
                    if (fatal) {
                        i = len;
                        break;
                    }

                    str += decodeURIComponent(window.escape(data.substring(beg, i)));
                    str += "\ufffd";
                    i++;
                    beg = i;
                } else {
                    i += x;
                }
            }

            str += decodeURIComponent(window.escape(data.substring(beg, i)));
            return str;
        };
    }

    cockpit.utf8_encoder = function utf8_encoder(constructor) {
        return new Utf8TextEncoder(constructor);
    };

    cockpit.utf8_decoder = function utf8_decoder(fatal) {
        return new Utf8TextDecoder(!!fatal);
    };

    cockpit.base64_encode = base64_encode;
    cockpit.base64_decode = base64_decode;

    cockpit.kill = function kill(host, group) {
        const options = { };
        if (host)
            options.host = host;
        if (group)
            options.group = group;
        cockpit.transport.control("kill", options);
    };

    /* Not public API ... yet? */
    cockpit.hint = function hint(name, options) {
        if (!default_transport)
            return;
        if (!options)
            options = default_host;
        if (typeof options == "string")
            options = { host: options };
        options.hint = name;
        cockpit.transport.control("hint", options);
    };

    cockpit.transport = public_transport = {
        wait: ensure_transport,
        inject: function inject(message, out) {
            if (!default_transport)
                return false;
            if (out === undefined || out)
                return default_transport.send_data(message);
            else
                return default_transport.dispatch_data({ data: message });
        },
        filter: function filter(callback, out) {
            if (out) {
                if (!outgoing_filters)
                    outgoing_filters = [];
                outgoing_filters.push(callback);
            } else {
                if (!incoming_filters)
                    incoming_filters = [];
                incoming_filters.push(callback);
            }
        },
        close: function close(problem) {
            if (default_transport)
                default_transport.close(problem ? { problem } : undefined);
            default_transport = null;
            this.options = { };
        },
        origin: transport_origin,
        options: { },
        uri: calculate_url,
        control: function(command, options) {
            options = { ...options, command };
            ensure_transport(function(transport) {
                transport.send_control(options);
            });
        },
        application: function () {
            if (!default_transport || window.mock)
                return calculate_application();
            return default_transport.application;
        },
    };

    /* ------------------------------------------------------------------------------------
     * An ordered queue of functions that should be called later.
     */

    let later_queue = [];
    let later_timeout = null;

    function later_drain() {
        const queue = later_queue;
        later_timeout = null;
        later_queue = [];
        for (;;) {
            const func = queue.shift();
            if (!func)
                break;
            func();
        }
    }

    function later_invoke(func) {
        if (func)
            later_queue.push(func);
        if (later_timeout === null)
            later_timeout = window.setTimeout(later_drain, 0);
    }

    /* ------------------------------------------------------------------------------------
     * Promises.
     * Based on Q and angular promises, with some jQuery compatibility. See the angular
     * license in COPYING.node for license lineage. There are some key differences with
     * both Q and jQuery.
     *
     *  * Exceptions thrown in handlers are not treated as rejections or failures.
     *    Exceptions remain actual exceptions.
     *  * Unlike jQuery callbacks added to an already completed promise don't execute
     *    immediately. Wait until control is returned to the browser.
     */

    function promise_then(state, fulfilled, rejected, updated) {
        if (fulfilled === undefined && rejected === undefined && updated === undefined)
            return null;
        const result = new Deferred();
        state.pending = state.pending || [];
        state.pending.push([result, fulfilled, rejected, updated]);
        if (state.status > 0)
            schedule_process_queue(state);
        return result.promise;
    }

    function create_promise(state) {
        /* Like jQuery the promise object is callable */
        const self = function Promise(target) {
            if (target) {
                Object.assign(target, self);
                return target;
            }
            return self;
        };

        state.status = 0;

        self.then = function then(fulfilled, rejected, updated) {
            return promise_then(state, fulfilled, rejected, updated) || self;
        };

        self.catch = function catch_(callback) {
            return promise_then(state, null, callback) || self;
        };

        self.finally = function finally_(callback, updated) {
            return promise_then(state, function() {
                return handle_callback(arguments, true, callback);
            }, function() {
                return handle_callback(arguments, false, callback);
            }, updated) || self;
        };

        /* Basic jQuery Promise compatibility */
        self.done = function done(fulfilled) {
            promise_then(state, fulfilled);
            return self;
        };

        self.fail = function fail(rejected) {
            promise_then(state, null, rejected);
            return self;
        };

        self.always = function always(callback) {
            promise_then(state, callback, callback);
            return self;
        };

        self.progress = function progress(updated) {
            promise_then(state, null, null, updated);
            return self;
        };

        self.state = function state_() {
            if (state.status == 1)
                return "resolved";
            if (state.status == 2)
                return "rejected";
            return "pending";
        };

        /* Promises are recursive like jQuery */
        self.promise = self;

        return self;
    }

    function process_queue(state) {
        const pending = state.pending;
        state.process_scheduled = false;
        state.pending = undefined;
        for (let i = 0, ii = pending.length; i < ii; ++i) {
            state.pur = true;
            const deferred = pending[i][0];
            const fn = pending[i][state.status];
            if (is_function(fn)) {
                deferred.resolve(fn.apply(state.promise, state.values));
            } else if (state.status === 1) {
                deferred.resolve.apply(deferred.resolve, state.values);
            } else {
                deferred.reject.apply(deferred.reject, state.values);
            }
        }
    }

    function schedule_process_queue(state) {
        if (state.process_scheduled || !state.pending)
            return;
        state.process_scheduled = true;
        later_invoke(function() { process_queue(state) });
    }

    function deferred_resolve(state, values) {
        let then;
        let done = false;
        if (is_object(values[0]) || is_function(values[0]))
            then = values[0]?.then;
        if (is_function(then)) {
            state.status = -1;
            then.call(values[0], function(/* ... */) {
                if (done)
                    return;
                done = true;
                deferred_resolve(state, arguments);
            }, function(/* ... */) {
                if (done)
                    return;
                done = true;
                deferred_reject(state, arguments);
            }, function(/* ... */) {
                deferred_notify(state, arguments);
            });
        } else {
            state.values = values;
            state.status = 1;
            schedule_process_queue(state);
        }
    }

    function deferred_reject(state, values) {
        state.values = values;
        state.status = 2;
        schedule_process_queue(state);
    }

    function deferred_notify(state, values) {
        const callbacks = state.pending;
        if ((state.status <= 0) && callbacks?.length) {
            later_invoke(function() {
                for (let i = 0, ii = callbacks.length; i < ii; i++) {
                    const result = callbacks[i][0];
                    const callback = callbacks[i][3];
                    if (is_function(callback))
                        result.notify(callback.apply(state.promise, values));
                    else
                        result.notify.apply(result, values);
                }
            });
        }
    }

    function Deferred() {
        const self = this;
        const state = { };
        self.promise = state.promise = create_promise(state);

        self.resolve = function resolve(/* ... */) {
            if (arguments[0] === state.promise)
                throw new Error("Expected promise to be resolved with other value than itself");
            if (!state.status)
                deferred_resolve(state, arguments);
            return self;
        };

        self.reject = function reject(/* ... */) {
            if (state.status)
                return;
            deferred_reject(state, arguments);
            return self;
        };

        self.notify = function notify(/* ... */) {
            deferred_notify(state, arguments);
            return self;
        };
    }

    function prep_promise(values, resolved) {
        const result = cockpit.defer();
        if (resolved)
            result.resolve.apply(result, values);
        else
            result.reject.apply(result, values);
        return result.promise;
    }

    function handle_callback(values, is_resolved, callback) {
        let callback_output = null;
        if (is_function(callback))
            callback_output = callback();
        if (callback_output && is_function(callback_output.then)) {
            return callback_output.then(function() {
                return prep_promise(values, is_resolved);
            }, function() {
                return prep_promise(arguments, false);
            });
        } else {
            return prep_promise(values, is_resolved);
        }
    }

    cockpit.when = function when(value, fulfilled, rejected, updated) {
        const result = cockpit.defer();
        result.resolve(value);
        return result.promise.then(fulfilled, rejected, updated);
    };

    cockpit.resolve = function resolve(result) {
        return cockpit.defer().resolve(result).promise;
    };

    cockpit.reject = function reject(ex) {
        return cockpit.defer().reject(ex).promise;
    };

    cockpit.defer = function() {
        return new Deferred();
    };

    /* ---------------------------------------------------------------------
     * Utilities
     */

    const fmt_re = /\$\{([^}]+)\}|\$([a-zA-Z0-9_]+)/g;
    cockpit.format = function format(fmt, args) {
        if (arguments.length != 2 || !is_object(args) || args === null)
            args = Array.prototype.slice.call(arguments, 1);

        function replace(m, x, y) {
            const value = args[x || y];

            /* Special-case 0 (also catches 0.0). All other falsy values return
             * the empty string.
             */
            if (value === 0)
                return '0';

            return value || '';
        }

        return fmt.replace(fmt_re, replace);
    };

    cockpit.format_number = function format_number(number, precision) {
        /* We show given number of digits of precision (default 3), but avoid scientific notation.
         * We also show integers without digits after the comma.
         *
         * We want to localise the decimal separator, but we never want to
         * show thousands separators (to avoid ambiguity).  For this
         * reason, for integers and large enough numbers, we use
         * non-localised conversions (and in both cases, show no
         * fractional part).
         */
        if (precision === undefined)
            precision = 3;
        const lang = cockpit.language === undefined ? undefined : cockpit.language.replace('_', '-');
        const smallestValue = 10 ** (-precision);

        if (!number && number !== 0)
            return "";
        else if (number % 1 === 0)
            return number.toString();
        else if (number > 0 && number <= smallestValue)
            return smallestValue.toLocaleString(lang);
        else if (number < 0 && number >= -smallestValue)
            return (-smallestValue).toLocaleString(lang);
        else if (number > 999 || number < -999)
            return number.toFixed(0);
        else
            return number.toLocaleString(lang, {
                maximumSignificantDigits: precision,
                minimumSignificantDigits: precision,
            });
    };

    let deprecated_format_warned = false;
    function format_units(suffixes, number, second_arg, third_arg) {
        let options = second_arg;
        let factor = options?.base2 ? 1024 : 1000;

        // compat API: we used to accept 'factor' as a separate second arg
        if (third_arg || (second_arg && !is_object(second_arg))) {
            if (!deprecated_format_warned) {
                console.warn(`cockpit.format_{bytes,bits}[_per_sec](..., ${second_arg}, ${third_arg}) is deprecated.`);
                deprecated_format_warned = true;
            }

            factor = second_arg || 1000;
            options = third_arg;
            // double backwards compat: "options" argument position used to be a boolean flag "separate"
            if (!is_object(options))
                options = { separate: options };
        }

        let suffix = null;

        /* Find that factor string */
        if (!number && number !== 0) {
            suffix = null;
        } else if (typeof (factor) === "string") {
            /* Prefer larger factors */
            const keys = [];
            for (const key in suffixes)
                keys.push(key);
            keys.sort().reverse();
            for (let y = 0; y < keys.length; y++) {
                for (let x = 0; x < suffixes[keys[y]].length; x++) {
                    if (factor == suffixes[keys[y]][x]) {
                        number = number / Math.pow(keys[y], x);
                        suffix = factor;
                        break;
                    }
                }
                if (suffix)
                    break;
            }

        /* @factor is a number */
        } else if (factor in suffixes) {
            let divisor = 1;
            for (let i = 0; i < suffixes[factor].length; i++) {
                const quotient = number / divisor;
                if (quotient < factor) {
                    number = quotient;
                    suffix = suffixes[factor][i];
                    break;
                }
                divisor *= factor;
            }
        }

        const string_representation = cockpit.format_number(number, options?.precision);
        let ret;

        if (string_representation && suffix)
            ret = [string_representation, suffix];
        else
            ret = [string_representation];

        if (!options?.separate)
            ret = ret.join(" ");

        return ret;
    }

    const byte_suffixes = {
        1000: ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB"],
        1024: ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB"]
    };

    cockpit.format_bytes = function format_bytes(number, ...args) {
        return format_units(byte_suffixes, number, ...args);
    };

    const byte_sec_suffixes = {
        1000: ["B/s", "kB/s", "MB/s", "GB/s", "TB/s", "PB/s", "EB/s", "ZB/s"],
        1024: ["B/s", "KiB/s", "MiB/s", "GiB/s", "TiB/s", "PiB/s", "EiB/s", "ZiB/s"]
    };

    cockpit.format_bytes_per_sec = function format_bytes_per_sec(number, ...args) {
        return format_units(byte_sec_suffixes, number, ...args);
    };

    const bit_suffixes = {
        1000: ["bps", "Kbps", "Mbps", "Gbps", "Tbps", "Pbps", "Ebps", "Zbps"]
    };

    cockpit.format_bits_per_sec = function format_bits_per_sec(number, ...args) {
        return format_units(bit_suffixes, number, ...args);
    };

    /* ---------------------------------------------------------------------
     * Storage Helper.
     *
     * Use application to prefix data stored in browser storage
     * with helpers for compatibility.
     */
    function StorageHelper(storageName) {
        const self = this;
        let storage;

        try {
            storage = window[storageName];
        } catch (e) { }

        self.prefixedKey = function (key) {
            return cockpit.transport.application() + ":" + key;
        };

        self.getItem = function (key, both) {
            let value = storage.getItem(self.prefixedKey(key));
            if (!value && both)
                value = storage.getItem(key);
            return value;
        };

        self.setItem = function (key, value, both) {
            storage.setItem(self.prefixedKey(key), value);
            if (both)
                storage.setItem(key, value);
        };

        self.removeItem = function(key, both) {
            storage.removeItem(self.prefixedKey(key));
            if (both)
                storage.removeItem(key);
        };

        /* Instead of clearing, purge anything that isn't prefixed with an application
         * and anything prefixed with our application.
         */
        self.clear = function(full) {
            let i = 0;
            while (i < storage.length) {
                const k = storage.key(i);
                if (full && k.indexOf("cockpit") !== 0)
                    storage.removeItem(k);
                else if (k.indexOf(cockpit.transport.application()) === 0)
                    storage.removeItem(k);
                else
                    i++;
            }
        };
    }

    cockpit.localStorage = new StorageHelper("localStorage");
    cockpit.sessionStorage = new StorageHelper("sessionStorage");

    /* ---------------------------------------------------------------------
     * Shared data cache.
     *
     * We cannot use sessionStorage when keeping lots of data in memory and
     * sharing it between frames. It has a rather paltry limit on the amount
     * of data it can hold ... so we use window properties instead.
     */

    function lookup_storage(win) {
        let storage;
        if (win.parent && win.parent !== win)
            storage = lookup_storage(win.parent);
        if (!storage) {
            try {
                storage = win["cv1-storage"];
                if (!storage)
                    win["cv1-storage"] = storage = { };
            } catch (ex) { }
        }
        return storage;
    }

    function StorageCache(org_key, provider, consumer) {
        const self = this;
        const key = cockpit.transport.application() + ":" + org_key;

        /* For triggering events and ownership */
        const trigger = window.sessionStorage;
        let last;

        const storage = lookup_storage(window);

        let claimed = false;
        let source;

        function callback() {
            /* Only run the callback if we have a result */
            if (storage[key] !== undefined) {
                const value = storage[key];
                window.setTimeout(function() {
                    if (consumer(value, org_key) === false)
                        self.close();
                });
            }
        }

        function result(value) {
            if (source && !claimed)
                claimed = true;
            if (!claimed)
                return;

            // use a random number to avoid races by separate instances
            const version = Math.floor(Math.random() * 10000000) + 1;

            /* Event for the local window */
            const ev = document.createEvent("StorageEvent");
            ev.initStorageEvent("storage", false, false, key, null,
                                version, window.location, trigger);

            storage[key] = value;
            trigger.setItem(key, version);
            ev.self = self;
            window.dispatchEvent(ev);
        }

        self.claim = function claim() {
            if (source)
                return;

            /* In case we're unclaimed during the callback */
            const claiming = { close: function() { } };
            source = claiming;

            const changed = provider(result, org_key);
            if (source === claiming)
                source = changed;
            else
                changed.close();
        };

        function unclaim() {
            if (source?.close)
                source.close();
            source = null;

            if (!claimed)
                return;

            claimed = false;

            let current_value = trigger.getItem(key);
            if (current_value)
                current_value = parseInt(current_value, 10);
            else
                current_value = null;

            if (last && last === current_value) {
                const ev = document.createEvent("StorageEvent");
                const version = trigger[key];
                ev.initStorageEvent("storage", false, false, key, version,
                                    null, window.location, trigger);
                delete storage[key];
                trigger.removeItem(key);
                ev.self = self;
                window.dispatchEvent(ev);
            }
        }

        function changed(event) {
            if (event.key !== key)
                return;

            /* check where the event came from
               - it came from someone else:
                   if it notifies their unclaim (new value null) and we haven't already claimed, do so
               - it came from ourselves:
                   if the new value doesn't match the actual value in the cache, and
                   we tried to claim (from null to a number), cancel our claim
             */
            if (event.self !== self) {
                if (!event.newValue && !claimed) {
                    self.claim();
                    return;
                }
            } else if (claimed && !event.oldValue && (event.newValue !== trigger.getItem(key))) {
                unclaim();
            }

            let new_value = null;
            if (event.newValue)
                new_value = parseInt(event.newValue, 10);
            if (last !== new_value) {
                last = new_value;
                callback();
            }
        }

        self.close = function() {
            window.removeEventListener("storage", changed, true);
            unclaim();
        };

        window.addEventListener("storage", changed, true);

        /* Always clear this data on unload */
        window.addEventListener("beforeunload", function() {
            self.close();
        });
        window.addEventListener("unload", function() {
            self.close();
        });

        if (trigger.getItem(key))
            callback();
        else
            self.claim();
    }

    cockpit.cache = function cache(key, provider, consumer) {
        return new StorageCache(key, provider, consumer);
    };

    /* ---------------------------------------------------------------------
     * Metrics
     *
     * Implements the cockpit.series and cockpit.grid. Part of the metrics
     * implementations that do not require jquery.
     */

    function SeriesSink(interval, identifier, fetch_callback) {
        const self = this;

        self.interval = interval;
        self.limit = identifier ? 64 * 1024 : 1024;

        /*
         * The cache sits on a window, either our own or a parent
         * window whichever we can access properly.
         *
         * Entries in the index are:
         *
         * { beg: N, items: [], mapping: { }, next: item }
         */
        const index = setup_index(identifier);

        /*
         * A linked list through the index, that we use for expiry
         * of the cache.
         */
        let count = 0;
        let head = null;
        let tail = null;

        function setup_index(id) {
            if (!id)
                return [];

            /* Try and find a good place to cache data */
            const storage = lookup_storage(window);

            let index = storage[id];
            if (!index)
                storage[id] = index = [];
            return index;
        }

        function search(idx, beg) {
            let low = 0;
            let high = idx.length - 1;

            while (low <= high) {
                const mid = (low + high) / 2 | 0;
                const val = idx[mid].beg;
                if (val < beg)
                    low = mid + 1;
                else if (val > beg)
                    high = mid - 1;
                else
                    return mid; /* key found */
            }
            return low;
        }

        function fetch(beg, end, for_walking) {
            if (fetch_callback) {
                if (!for_walking) {
                    /* Stash some fake data synchronously so that we don't ask
                     * again for the same range while they are still fetching
                     * it asynchronously.
                     */
                    stash(beg, new Array(end - beg), { });
                }
                fetch_callback(beg, end, for_walking);
            }
        }

        self.load = function load(beg, end, for_walking) {
            if (end <= beg)
                return;

            const at = search(index, beg);

            const len = index.length;
            let last = beg;

            /* We do this in two phases: First, we walk the index to
             * process what we already have and at the same time make
             * notes about what we need to fetch.  Then we go over the
             * notes and actually fetch what we need.  That way, the
             * fetch callbacks in the second phase can modify the
             * index data structure without disturbing the walk in the
             * first phase.
             */

            const fetches = [];

            /* Data relevant to this range can be at the found index, or earlier */
            for (let i = at > 0 ? at - 1 : at; i < len; i++) {
                const entry = index[i];
                const en = entry.items.length;
                if (!en)
                    continue;

                const eb = entry.beg;
                const b = Math.max(eb, beg);
                const e = Math.min(eb + en, end);

                if (b < e) {
                    if (b > last)
                        fetches.push([last, b]);
                    process(b, entry.items.slice(b - eb, e - eb), entry.mapping);
                    last = e;
                } else if (i >= at) {
                    break; /* no further intersections */
                }
            }

            for (let i = 0; i < fetches.length; i++)
                fetch(fetches[i][0], fetches[i][1], for_walking);

            if (last != end)
                fetch(last, end, for_walking);
        };

        function stash(beg, items, mapping) {
            if (!items.length)
                return;

            let at = search(index, beg);

            const end = beg + items.length;

            const len = index.length;
            let i;
            for (i = at > 0 ? at - 1 : at; i < len; i++) {
                const entry = index[i];
                const en = entry.items.length;
                if (!en)
                    continue;

                const eb = entry.beg;
                const b = Math.max(eb, beg);
                const e = Math.min(eb + en, end);

                /*
                 * We truncate blocks that intersect with this one
                 *
                 * We could adjust them, but in general the loaders are
                 * intelligent enough to only load the required data, so
                 * not doing this optimization yet.
                 */

                if (b < e) {
                    const num = e - b;
                    entry.items.splice(b - eb, num);
                    count -= num;
                    if (b - eb === 0)
                        entry.beg += (e - eb);
                } else if (i >= at) {
                    break; /* no further intersections */
                }
            }

            /* Insert our item into the array */
            const entry = { beg, items, mapping };
            if (!head)
                head = entry;
            if (tail)
                tail.next = entry;
            tail = entry;
            count += items.length;
            index.splice(at, 0, entry);

            /* Remove any items with zero length around insertion point */
            for (at--; at <= i; at++) {
                const entry = index[at];
                if (entry && !entry.items.length) {
                    index.splice(at, 1);
                    at--;
                }
            }

            /* If our index has gotten too big, expire entries */
            while (head && count > self.limit) {
                count -= head.items.length;
                head.items = [];
                head.mapping = null;
                head = head.next || null;
            }

            /* Remove any entries with zero length at beginning */
            const newlen = index.length;
            for (i = 0; i < newlen; i++) {
                if (index[i].items.length > 0)
                    break;
            }
            index.splice(0, i);
        }

        /*
         * Used to populate grids, the keys are grid ids and
         * the values are objects: { grid, rows, notify }
         *
         * The rows field is an object indexed by paths
         * container aliases, and the values are: [ row, path ]
         */
        const registered = { };

        /* An undocumented function called by DataGrid */
        self._register = function _register(grid, id) {
            if (grid.interval != interval)
                throw Error("mismatched metric interval between grid and sink");
            let gdata = registered[id];
            if (!gdata) {
                gdata = registered[id] = { grid, links: [] };
                gdata.links.remove = function remove() {
                    delete registered[id];
                };
            }
            return gdata.links;
        };

        function process(beg, items, mapping) {
            const end = beg + items.length;

            for (const id in registered) {
                const gdata = registered[id];
                const grid = gdata.grid;

                const b = Math.max(beg, grid.beg);
                const e = Math.min(end, grid.end);

                /* Does this grid overlap the bounds of item? */
                if (b < e) {
                    /* Where in the items to take from */
                    const f = b - beg;

                    /* Where and how many to place */
                    const t = b - grid.beg;

                    /* How many to process */
                    const n = e - b;

                    for (let i = 0; i < n; i++) {
                        const klen = gdata.links.length;
                        for (let k = 0; k < klen; k++) {
                            const path = gdata.links[k][0];
                            const row = gdata.links[k][1];

                            /* Calculate the data field to fill in */
                            let data = items[f + i];
                            let map = mapping;
                            const jlen = path.length;
                            for (let j = 0; data !== undefined && j < jlen; j++) {
                                if (!data) {
                                    data = undefined;
                                } else if (map !== undefined && map !== null) {
                                    map = map[path[j]];
                                    if (map)
                                        data = data[map[""]];
                                    else
                                        data = data[path[j]];
                                } else {
                                    data = data[path[j]];
                                }
                            }

                            row[t + i] = data;
                        }
                    }

                    /* Notify the grid, so it can call any functions */
                    grid.notify(t, n);
                }
            }
        }

        self.input = function input(beg, items, mapping) {
            process(beg, items, mapping);
            stash(beg, items, mapping);
        };

        self.close = function () {
            for (const id in registered) {
                const grid = registered[id];
                if (grid?.grid)
                    grid.grid.remove_sink(self);
            }
        };
    }

    cockpit.series = function series(interval, cache, fetch) {
        return new SeriesSink(interval, cache, fetch);
    };

    let unique = 1;

    function SeriesGrid(interval, beg, end) {
        const self = this;

        /* We can trigger events */
        event_mixin(self, { });

        const rows = [];

        self.interval = interval;
        self.beg = 0;
        self.end = 0;

        /*
         * Used to populate table data, the values are:
         * [ callback, row ]
         */
        const callbacks = [];

        const sinks = [];

        let suppress = 0;

        const id = "g1-" + unique;
        unique += 1;

        /* Used while walking */
        let walking = null;
        let offset = null;

        self.notify = function notify(x, n) {
            if (suppress)
                return;
            if (x + n > self.end - self.beg)
                n = (self.end - self.beg) - x;
            if (n <= 0)
                return;
            const jlen = callbacks.length;
            for (let j = 0; j < jlen; j++) {
                const callback = callbacks[j][0];
                const row = callbacks[j][1];
                callback.call(self, row, x, n);
            }

            self.dispatchEvent("notify", x, n);
        };

        self.add = function add(/* sink, path */) {
            const row = [];
            rows.push(row);

            /* Called as add(sink, path) */
            if (is_object(arguments[0])) {
                const sink = arguments[0].series || arguments[0];

                /* The path argument can be an array, or a dot separated string */
                let path = arguments[1];
                if (!path)
                    path = [];
                else if (typeof (path) === "string")
                    path = path.split(".");

                const links = sink._register(self, id);
                if (!links.length)
                    sinks.push({ sink, links });
                links.push([path, row]);

            /* Called as add(callback) */
            } else if (is_function(arguments[0])) {
                const cb = [arguments[0], row];
                if (arguments[1] === true)
                    callbacks.unshift(cb);
                else
                    callbacks.push(cb);

            /* Not called as add() */
            } else if (arguments.length !== 0) {
                throw Error("invalid args to grid.add()");
            }

            return row;
        };

        self.remove = function remove(row) {
            /* Remove from the sinks */
            let ilen = sinks.length;
            for (let i = 0; i < ilen; i++) {
                const jlen = sinks[i].links.length;
                for (let j = 0; j < jlen; j++) {
                    if (sinks[i].links[j][1] === row) {
                        sinks[i].links.splice(j, 1);
                        break;
                    }
                }
            }

            /* Remove from our list of rows */
            ilen = rows.length;
            for (let i = 0; i < ilen; i++) {
                if (rows[i] === row) {
                    rows.splice(i, 1);
                    break;
                }
            }
        };

        self.remove_sink = function remove_sink(sink) {
            const len = sinks.length;
            for (let i = 0; i < len; i++) {
                if (sinks[i].sink === sink) {
                    sinks[i].links.remove();
                    sinks.splice(i, 1);
                    break;
                }
            }
        };

        self.sync = function sync(for_walking) {
            /* Suppress notifications */
            suppress++;

            /* Ask all sinks to load data */
            const len = sinks.length;
            for (let i = 0; i < len; i++) {
                const sink = sinks[i].sink;
                sink.load(self.beg, self.end, for_walking);
            }

            suppress--;

            /* Notify for all rows */
            self.notify(0, self.end - self.beg);
        };

        function move_internal(beg, end, for_walking) {
            if (end === undefined)
                end = beg + (self.end - self.beg);

            if (end < beg)
                beg = end;

            self.beg = beg;
            self.end = end;

            if (!rows.length)
                return;

            rows.forEach(function(row) {
                row.length = 0;
            });

            self.sync(for_walking);
        }

        function stop_walking() {
            window.clearInterval(walking);
            walking = null;
            offset = null;
        }

        self.move = function move(beg, end) {
            stop_walking();
            /* Some code paths use now twice.
             * They should use the same value.
             */
            let now = null;

            /* Treat negative numbers relative to now */
            if (beg === undefined) {
                beg = 0;
            } else if (is_negative(beg)) {
                now = Date.now();
                beg = Math.floor(now / self.interval) + beg;
            }
            if (end !== undefined && is_negative(end)) {
                if (now === null)
                    now = Date.now();
                end = Math.floor(now / self.interval) + end;
            }

            move_internal(beg, end, false);
        };

        self.walk = function walk() {
            /* Don't overflow 32 signed bits with the interval since
             * many browsers will mishandle it.  This means that plots
             * that would make about one step every month don't walk
             * at all, but I guess that is ok.
             *
             * For example,
             * https://developer.mozilla.org/en-US/docs/Web/API/setTimeout
             * says:
             *
             *    Browsers including Internet Explorer, Chrome,
             *    Safari, and Firefox store the delay as a 32-bit
             *    signed Integer internally. This causes an Integer
             *    overflow when using delays larger than 2147483647,
             *    resulting in the timeout being executed immediately.
             */

            const start = Date.now();
            if (self.interval > 2000000000)
                return;

            stop_walking();
            offset = start - self.beg * self.interval;
            walking = window.setInterval(function() {
                const now = Date.now();
                move_internal(Math.floor((now - offset) / self.interval), undefined, true);
            }, self.interval);
        };

        self.close = function close() {
            stop_walking();
            while (sinks.length)
                (sinks.pop()).links.remove();
        };

        self.move(beg, end);
    }

    cockpit.grid = function grid(interval, beg, end) {
        return new SeriesGrid(interval, beg, end);
    };

    /* --------------------------------------------------------------------
     * Basic utilities.
     */

    function BasicError(problem, message) {
        this.problem = problem;
        this.message = message || cockpit.message(problem);
        this.toString = function() {
            return this.message;
        };
    }

    cockpit.logout = function logout(reload, reason) {
        /* fully clear session storage */
        cockpit.sessionStorage.clear(true);

        /* Only clean application data from localStorage,
         * except for login-data. Clear that completely */
        cockpit.localStorage.removeItem('login-data', true);
        cockpit.localStorage.clear(false);

        if (reload !== false)
            reload_after_disconnect = true;
        ensure_transport(function(transport) {
            if (!transport.send_control({ command: "logout", disconnect: true }))
                window.location.reload(reload_after_disconnect);
        });
        window.sessionStorage.setItem("logout-intent", "explicit");
        if (reason)
            window.sessionStorage.setItem("logout-reason", reason);
    };

    /* Not public API ... yet? */
    cockpit.drop_privileges = function drop_privileges() {
        ensure_transport(function(transport) {
            transport.send_control({ command: "logout", disconnect: false });
        });
    };

    /* ---------------------------------------------------------------------
     * User and system information
     */

    cockpit.info = { };
    event_mixin(cockpit.info, { });

    init_callback = function(options) {
        if (options.system)
            Object.assign(cockpit.info, options.system);
        if (options.system)
            cockpit.info.dispatchEvent("changed");
    };

    let the_user = null;
    cockpit.user = function () {
            if (!the_user) {
                const dbus = cockpit.dbus(null, { bus: "internal" });
                return dbus.call("/user", "org.freedesktop.DBus.Properties", "GetAll",
                          ["cockpit.User"], { type: "s" })
                    .then(([user]) => {
                        the_user = {
                            id: user.Id.v,
                            name: user.Name.v,
                            full_name: user.Full.v,
                            groups: user.Groups.v,
                            home: user.Home.v,
                            shell: user.Shell.v
                        };
                        return the_user;
                    })
                    .finally(() => dbus.close());
            } else {
                return Promise.resolve(the_user);
            }
    };

    /* ------------------------------------------------------------------------
     * Override for broken browser behavior
     */

    document.addEventListener("click", function(ev) {
        if (ev.target.classList && in_array(ev.target.classList, 'disabled'))
          ev.stopPropagation();
    }, true);

    /* ------------------------------------------------------------------------
     * Cockpit location
     */

    /* HACK: Mozilla will unescape 'window.location.hash' before returning
     * it, which is broken.
     *
     * https://bugzilla.mozilla.org/show_bug.cgi?id=135309
     */

    let last_loc = null;

    function get_window_location_hash() {
        return (window.location.href.split('#')[1] || '');
    }

    function Location() {
        const self = this;
        const application = cockpit.transport.application();
        self.url_root = url_root || "";

        if (window.mock?.url_root)
            self.url_root = window.mock.url_root;

        if (application.indexOf("cockpit+=") === 0) {
            if (self.url_root)
                self.url_root += '/';
            self.url_root = self.url_root + application.replace("cockpit+", '');
        }

        const href = get_window_location_hash();
        const options = { };
        self.path = decode(href, options);

        function decode_path(input) {
            const parts = input.split('/').map(decodeURIComponent);
            let result, i;
            let pre_parts = [];

            if (self.url_root)
                pre_parts = self.url_root.split('/').map(decodeURIComponent);

            if (input && input[0] !== "/") {
                result = [].concat(self.path);
                result.pop();
                result = result.concat(parts);
            } else {
                result = parts;
            }

            result = resolve_path_dots(result);
            for (i = 0; i < pre_parts.length; i++) {
                if (pre_parts[i] !== result[i])
                    break;
            }
            if (i == pre_parts.length)
                result.splice(0, pre_parts.length);

            return result;
        }

        function encode(path, options, with_root) {
            if (typeof path == "string")
                path = decode_path(path);

            let href = "/" + path.map(encodeURIComponent).join("/");
            if (with_root && self.url_root && href.indexOf("/" + self.url_root + "/") !== 0)
                href = "/" + self.url_root + href;

            /* Undo unnecessary encoding of these */
            href = href.replaceAll("%40", "@");
            href = href.replaceAll("%3D", "=");
            href = href.replaceAll("%2B", "+");
            href = href.replaceAll("%23", "#");

            let opt;
            const query = [];
            function push_option(v) {
                query.push(encodeURIComponent(opt) + "=" + encodeURIComponent(v));
            }

            if (options) {
                for (opt in options) {
                    let value = options[opt];
                    if (!Array.isArray(value))
                        value = [value];
                    value.forEach(push_option);
                }
                if (query.length > 0)
                    href += "?" + query.join("&");
            }
            return href;
        }

        function decode(href, options) {
            if (href[0] == '#')
                href = href.substr(1);

            const pos = href.indexOf('?');
            const first = (pos === -1) ? href : href.substr(0, pos);
            const path = decode_path(first);
            if (pos !== -1 && options) {
                href.substring(pos + 1).split("&")
                .forEach(function(opt) {
                    const parts = opt.split('=');
                    const name = decodeURIComponent(parts[0]);
                    const value = decodeURIComponent(parts[1]);
                    if (options[name]) {
                        let last = options[name];
                        if (!Array.isArray(value))
                            last = options[name] = [last];
                        last.push(value);
                    } else {
                        options[name] = value;
                    }
                });
            }

            return path;
        }

        function href_for_go_or_replace(/* ... */) {
            let href;
            if (arguments.length == 1 && arguments[0] instanceof Location) {
                href = String(arguments[0]);
            } else if (typeof arguments[0] == "string") {
                const options = arguments[1] || { };
                href = encode(decode(arguments[0], options), options);
            } else {
                href = encode.apply(self, arguments);
            }
            return href;
        }

        function replace(/* ... */) {
            if (self !== last_loc)
                return;
            const href = href_for_go_or_replace.apply(self, arguments);
            window.location.replace(window.location.pathname + '#' + href);
        }

        function go(/* ... */) {
            if (self !== last_loc)
                return;
            const href = href_for_go_or_replace.apply(self, arguments);
            window.location.hash = '#' + href;
        }

        Object.defineProperties(self, {
            path: {
                enumerable: true,
                writable: false,
                value: self.path
            },
            options: {
                enumerable: true,
                writable: false,
                value: options
            },
            href: {
                enumerable: true,
                value: href
            },
            go: { value: go },
            replace: { value: replace },
            encode: { value: encode },
            decode: { value: decode },
            toString: { value: function() { return href } }
        });
    }

    Object.defineProperty(cockpit, "location", {
        enumerable: true,
        get: function() {
            if (!last_loc || last_loc.href !== get_window_location_hash())
                last_loc = new Location();
            return last_loc;
        },
        set: function(v) {
            cockpit.location.go(v);
        }
    });

    window.addEventListener("hashchange", function() {
        last_loc = null;
        let hash = window.location.hash;
        if (hash.indexOf("#") === 0)
            hash = hash.substring(1);
        cockpit.hint("location", { hash });
        cockpit.dispatchEvent("locationchanged");
    });

    /* ------------------------------------------------------------------------
     * Cockpit jump
     */

    cockpit.jump = function jump(path, host) {
        if (Array.isArray(path))
            path = "/" + path.map(encodeURIComponent).join("/")
.replaceAll("%40", "@")
.replaceAll("%3D", "=")
.replaceAll("%2B", "+");
        else
            path = "" + path;

        /* When host is not given (undefined), use current transport's host. If
         * it is null, use localhost.
         */
        if (host === undefined)
            host = cockpit.transport.host;

        const options = { command: "jump", location: path, host };
        cockpit.transport.inject("\n" + JSON.stringify(options));
    };

    /* ---------------------------------------------------------------------
     * Cockpit Page Visibility
     */

    (function() {
        let hiddenHint = false;

        function visibility_change() {
            let value = document.hidden;
            if (value === false)
                value = hiddenHint;
            if (cockpit.hidden !== value) {
                cockpit.hidden = value;
                cockpit.dispatchEvent("visibilitychange");
            }
        }

        document.addEventListener("visibilitychange", visibility_change);

        /*
         * Wait for changes in visibility of just our iframe. These are delivered
         * via a hint message from the parent. For now we are the only handler of
         * hint messages, so this is implemented rather simply on purpose.
         */
        process_hints = function(data) {
            if ("hidden" in data) {
                hiddenHint = data.hidden;
                visibility_change();
            }
        };

        /* The first time */
        visibility_change();
    }());

    /* ---------------------------------------------------------------------
     * Spawning
     */

    function ProcessError(options, name) {
        this.problem = options.problem || null;
        this.exit_status = options["exit-status"];
        if (this.exit_status === undefined)
            this.exit_status = null;
        this.exit_signal = options["exit-signal"];
        if (this.exit_signal === undefined)
            this.exit_signal = null;
        this.message = options.message;

        if (this.message === undefined) {
            if (this.problem)
                this.message = cockpit.message(options.problem);
            else if (this.exit_signal !== null)
                this.message = cockpit.format(_("$0 killed with signal $1"), name, this.exit_signal);
            else if (this.exit_status !== undefined)
                this.message = cockpit.format(_("$0 exited with code $1"), name, this.exit_status);
            else
                this.message = cockpit.format(_("$0 failed"), name);
        } else {
            this.message = this.message.trim();
        }

        this.toString = function() {
            return this.message;
        };
    }

    function spawn_debug() {
        if (window.debugging == "all" || window.debugging?.includes("spawn"))
            console.debug.apply(console, arguments);
    }

    /* public */
    cockpit.spawn = function(command, options) {
        const dfd = cockpit.defer();

        const args = { payload: "stream", spawn: [] };
        if (command instanceof Array) {
            for (let i = 0; i < command.length; i++)
                args.spawn.push(String(command[i]));
        } else {
            args.spawn.push(String(command));
        }
        if (options !== undefined)
            Object.assign(args, options);

        spawn_debug("process spawn:", JSON.stringify(args.spawn));

        const name = args.spawn[0] || "process";
        const channel = cockpit.channel(args);

        /* Callback that wants a stream response, see below */
        const buffer = channel.buffer(null);

        channel.addEventListener("close", function(event, options) {
            const data = buffer.squash();
            spawn_debug("process closed:", JSON.stringify(options));
            if (data)
                spawn_debug("process output:", data);
            if (options.message !== undefined)
                spawn_debug("process error:", options.message);

            if (options.problem)
                dfd.reject(new ProcessError(options, name));
            else if (options["exit-status"] || options["exit-signal"])
                dfd.reject(new ProcessError(options, name), data);
            else if (options.message !== undefined)
                dfd.resolve(data, options.message);
            else
                dfd.resolve(data);
        });

        const ret = dfd.promise;
        ret.stream = function(callback) {
            buffer.callback = callback.bind(ret);
            return this;
        };

        ret.input = function(message, stream) {
            if (message !== null && message !== undefined) {
                spawn_debug("process input:", message);
                iterate_data(message, function(data) {
                    channel.send(data);
                });
            }
            if (!stream)
                channel.control({ command: "done" });
            return this;
        };

        ret.close = function(problem) {
            spawn_debug("process closing:", problem);
            if (channel.valid)
                channel.close(problem);
            return this;
        };

        return ret;
    };

    /* public */
    cockpit.script = function(script, args, options) {
        if (!options && is_plain_object(args)) {
            options = args;
            args = [];
        }
        const command = ["/bin/sh", "-c", script, "--"];
        command.push.apply(command, args);
        return cockpit.spawn(command, options);
    };

    function dbus_debug() {
        if (window.debugging == "all" || window.debugging?.includes("dbus"))
            console.debug.apply(console, arguments);
    }

    function DBusError(arg, arg1) {
        if (typeof (arg) == "string") {
            this.problem = arg;
            this.name = null;
            this.message = arg1 || cockpit.message(arg);
        } else {
            this.problem = null;
            this.name = arg[0];
            this.message = arg[1][0] || arg[0];
        }
        this.toString = function() {
            return this.message;
        };
    }

    function DBusCache() {
        const self = this;

        let callbacks = [];
        self.data = { };
        self.meta = { };

        self.connect = function connect(path, iface, callback, first) {
            const cb = [path, iface, callback];
            if (first)
                callbacks.unshift(cb);
            else
                callbacks.push(cb);
            return {
                remove: function remove() {
                    const length = callbacks.length;
                    for (let i = 0; i < length; i++) {
                        const cb = callbacks[i];
                        if (cb[0] === path && cb[1] === iface && cb[2] === callback) {
                            delete cb[i];
                            break;
                        }
                    }
                }
            };
        };

        function emit(path, iface, props) {
            const copy = callbacks.slice();
            const length = copy.length;
            for (let i = 0; i < length; i++) {
                const cb = copy[i];
                if ((!cb[0] || cb[0] === path) &&
                    (!cb[1] || cb[1] === iface)) {
                    cb[2](props, path);
                }
            }
        }

        self.update = function update(path, iface, props) {
            if (!self.data[path])
                self.data[path] = { };
            if (!self.data[path][iface])
                self.data[path][iface] = props;
            else
                props = Object.assign(self.data[path][iface], props);
            emit(path, iface, props);
        };

        self.remove = function remove(path, iface) {
            if (self.data[path]) {
                delete self.data[path][iface];
                emit(path, iface, null);
            }
        };

        self.lookup = function lookup(path, iface) {
            if (self.data[path])
                return self.data[path][iface];
            return undefined;
        };

        self.each = function each(iface, callback) {
            for (const path in self.data) {
                for (const ifa in self.data[path]) {
                    if (ifa == iface)
                        callback(self.data[path][iface], path);
                }
            }
        };

        self.close = function close() {
            self.data = { };
            const copy = callbacks;
            callbacks = [];
            const length = copy.length;
            for (let i = 0; i < length; i++)
                copy[i].callback();
        };
    }

    function DBusProxy(client, cache, iface, path, options) {
        const self = this;
        event_mixin(self, { });

        let valid = false;
        let defined = false;
        const waits = cockpit.defer();

        /* No enumeration on these properties */
        Object.defineProperties(self, {
            client: { value: client, enumerable: false, writable: false },
            path: { value: path, enumerable: false, writable: false },
            iface: { value: iface, enumerable: false, writable: false },
            valid: { get: function() { return valid }, enumerable: false },
            wait: {
                enumerable: false,
                writable: false,
                value: function(func) {
                    if (func)
                        waits.promise.always(func);
                    return waits.promise;
                }
            },
            call: {
                value: function(name, args, options) { return client.call(path, iface, name, args, options) },
                enumerable: false,
                writable: false
            },
            data: { value: { }, enumerable: false }
        });

        if (!options)
            options = { };

        function define() {
            if (!cache.meta[iface])
                return;

            const meta = cache.meta[iface];
            defined = true;

            Object.keys(meta.methods || { }).forEach(function(name) {
                if (name[0].toLowerCase() == name[0])
                    return; /* Only map upper case */

                /* Again, make sure these don't show up in enumerations */
                Object.defineProperty(self, name, {
                    enumerable: false,
                    value: function() {
                        const dfd = cockpit.defer();
                        client.call(path, iface, name, Array.prototype.slice.call(arguments))
                            .done(function(reply) { dfd.resolve.apply(dfd, reply) })
                            .fail(function(ex) { dfd.reject(ex) });
                        return dfd.promise;
                    }
                });
            });

            Object.keys(meta.properties || { }).forEach(function(name) {
                if (name[0].toLowerCase() == name[0])
                    return; /* Only map upper case */

                const config = {
                    enumerable: true,
                    get: function() { return self.data[name] },
                    set: function(v) { throw Error(name + "is not writable") }
                };

                const prop = meta.properties[name];
                if (prop.flags && prop.flags.indexOf('w') !== -1) {
                    config.set = function(v) {
                        client.call(path, "org.freedesktop.DBus.Properties", "Set",
                                [iface, name, cockpit.variant(prop.type, v)])
                            .fail(function(ex) {
                                console.log("Couldn't set " + iface + " " + name +
                                            " at " + path + ": " + ex);
                            });
                    };
                }

                /* Again, make sure these don't show up in enumerations */
                Object.defineProperty(self, name, config);
            });
        }

        function update(props) {
            if (props) {
                Object.assign(self.data, props);
                if (!defined)
                    define();
                valid = true;
            } else {
                valid = false;
            }
            self.dispatchEvent("changed", props);
        }

        cache.connect(path, iface, update, true);
        update(cache.lookup(path, iface));

        function signal(path, iface, name, args) {
            self.dispatchEvent("signal", name, args);
            if (name[0].toLowerCase() != name[0]) {
                args = args.slice();
                args.unshift(name);
                self.dispatchEvent.apply(self, args);
            }
        }

        client.subscribe({ path, interface: iface }, signal, options.subscribe !== false);

        function waited(ex) {
            if (valid)
                waits.resolve();
            else
                waits.reject(ex);
        }

        /* If watching then do a proper watch, otherwise object is done */
        if (options.watch !== false)
            client.watch({ path, interface: iface }).always(waited);
        else
            waited();
    }

    function DBusProxies(client, cache, iface, path_namespace, options) {
        const self = this;
        event_mixin(self, { });

        let waits;

        Object.defineProperties(self, {
            client: { value: client, enumerable: false, writable: false },
            iface: { value: iface, enumerable: false, writable: false },
            path_namespace: { value: path_namespace, enumerable: false, writable: false },
            wait: {
                enumerable: false,
                writable: false,
                value: function(func) {
                    if (func)
                        waits.always(func);
                    return waits;
                }
            }
        });

        /* Subscribe to signals once for all proxies */
        const match = { interface: iface, path_namespace };

        /* Callbacks added by proxies */
        client.subscribe(match);

        /* Watch for property changes */
        if (options.watch !== false) {
            waits = client.watch(match);
        } else {
            waits = cockpit.defer().resolve().promise;
        }

        /* Already added watch/subscribe, tell proxies not to */
        options = { watch: false, subscribe: false, ...options };

        function update(props, path) {
            let proxy = self[path];
            if (path) {
                if (!props && proxy) {
                    delete self[path];
                    self.dispatchEvent("removed", proxy);
                } else if (props) {
                    if (!proxy) {
                        proxy = self[path] = client.proxy(iface, path, options);
                        self.dispatchEvent("added", proxy);
                    }
                    self.dispatchEvent("changed", proxy);
                }
            }
        }

        cache.connect(null, iface, update, false);
        cache.each(iface, update);
    }

    function DBusClient(name, options) {
        const self = this;
        event_mixin(self, { });

        const args = { };
        let track = false;
        let owner = null;

        if (options) {
            if (options.track)
                track = true;

            delete options.track;
            Object.assign(args, options);
        }
        args.payload = "dbus-json3";
        if (name)
            args.name = name;
        self.options = options;
        self.unique_name = null;

        dbus_debug("dbus open: ", args);

        let channel = cockpit.channel(args);
        const subscribers = { };
        let calls = { };
        let cache;

        /* The problem we closed with */
        let closed;

        self.constructors = { "*": DBusProxy };

        /* Allows waiting on the channel if necessary */
        self.wait = channel.wait;

        function ensure_cache() {
            if (!cache)
                cache = new DBusCache();
        }

        function send(payload) {
            if (channel?.valid) {
                dbus_debug("dbus:", payload);
                channel.send(payload);
                return true;
            }
            return false;
        }

        function matches(signal, match) {
            if (match.path && signal[0] !== match.path)
                return false;
            if (match.path_namespace && signal[0].indexOf(match.path_namespace) !== 0)
                return false;
            if (match.interface && signal[1] !== match.interface)
                return false;
            if (match.member && signal[2] !== match.member)
                return false;
            if (match.arg0 && (!signal[3] || signal[3][0] !== match.arg0))
                return false;
            return true;
        }

        function on_message(event, payload) {
            dbus_debug("dbus:", payload);
            let msg;
            try {
                msg = JSON.parse(payload);
            } catch (ex) {
                console.warn("received invalid dbus json message:", ex);
            }
            if (msg === undefined) {
                channel.close({ problem: "protocol-error" });
                return;
            }
            const dfd = (msg.id !== undefined) ? calls[msg.id] : undefined;
            if (msg.reply) {
                if (dfd) {
                    const options = { };
                    if (msg.type)
                        options.type = msg.type;
                    if (msg.flags)
                        options.flags = msg.flags;
                    dfd.resolve(msg.reply[0] || [], options);
                    delete calls[msg.id];
                }
                return;
            } else if (msg.error) {
                if (dfd) {
                    dfd.reject(new DBusError(msg.error));
                    delete calls[msg.id];
                }
                return;
            }

            /*
             * The above promise resolutions or failures are triggered via
             * later_invoke(). In order to preserve ordering guarantees we
             * also have to process other events that way too.
             */
            later_invoke(function() {
                if (msg.signal) {
                    for (const id in subscribers) {
                        const subscription = subscribers[id];
                        if (subscription.callback) {
                            if (matches(msg.signal, subscription.match))
                                subscription.callback.apply(self, msg.signal);
                        }
                    }
                } else if (msg.notify) {
                    notify(msg.notify);
                } else if (msg.meta) {
                    meta(msg.meta);
                } else if (msg.owner !== undefined) {
                    self.dispatchEvent("owner", msg.owner);

                    /*
                     * We won't get this signal with the same
                     * owner twice so if we've seen an owner
                     * before that means it has changed.
                     */
                    if (track && owner)
                        self.close();

                    owner = msg.owner;
                } else {
                    dbus_debug("received unexpected dbus json message:", payload);
                }
            });
        }

        function meta(data) {
            ensure_cache();
            Object.assign(cache.meta, data);
            self.dispatchEvent("meta", data);
        }

        function notify(data) {
            ensure_cache();
            for (const path in data) {
                for (const iface in data[path]) {
                    const props = data[path][iface];
                    if (!props)
                        cache.remove(path, iface);
                    else
                        cache.update(path, iface, props);
                }
            }
            self.dispatchEvent("notify", data);
        }

        this.notify = notify;

        function close_perform(options) {
            closed = options.problem || "disconnected";
            const outstanding = calls;
            calls = { };
            for (const id in outstanding) {
                outstanding[id].reject(new DBusError(closed, options.message));
            }
            self.dispatchEvent("close", options);
        }

        this.close = function close(options) {
            if (typeof options == "string")
                options = { problem: options };
            if (!options)
                options = { };
            if (channel)
                channel.close(options);
            else
                close_perform(options);
        };

        function on_ready(event, message) {
            dbus_debug("dbus ready:", options);
            self.unique_name = message["unique-name"];
        }

        function on_close(event, options) {
            dbus_debug("dbus close:", options);
            channel.removeEventListener("ready", on_ready);
            channel.removeEventListener("message", on_message);
            channel.removeEventListener("close", on_close);
            channel = null;
            close_perform(options);
        }

        channel.addEventListener("ready", on_ready);
        channel.addEventListener("message", on_message);
        channel.addEventListener("close", on_close);

        let last_cookie = 1;

        this.call = function call(path, iface, method, args, options) {
            const dfd = cockpit.defer();
            const id = String(last_cookie);
            last_cookie++;
            const method_call = {
                ...options,
                call: [path, iface, method, args || []],
                id
            };

            const msg = JSON.stringify(method_call);
            if (send(msg))
                calls[id] = dfd;
            else
                dfd.reject(new DBusError(closed));

            return dfd.promise;
        };

        self.signal = function signal(path, iface, member, args, options) {
            if (!channel || !channel.valid)
                return;

            const message = { ...options, signal: [path, iface, member, args || []] };

            send(JSON.stringify(message));
        };

        this.subscribe = function subscribe(match, callback, rule) {
            const subscription = {
                match: { ...match },
                callback
            };

            if (rule !== false)
                send(JSON.stringify({ "add-match": subscription.match }));

            let id;
            if (callback) {
                id = String(last_cookie);
                last_cookie++;
                subscribers[id] = subscription;
            }

            return {
                remove: function() {
                    let prev;
                    if (id) {
                        prev = subscribers[id];
                        if (prev)
                            delete subscribers[id];
                    }
                    if (rule !== false && prev)
                        send(JSON.stringify({ "remove-match": prev.match }));
                }
            };
        };

        self.watch = function watch(path) {
            const match = is_plain_object(path) ? { ...path } : { path: String(path) };

            const id = String(last_cookie);
            last_cookie++;
            const dfd = cockpit.defer();

            const msg = JSON.stringify({ watch: match, id });
            if (send(msg))
                calls[id] = dfd;
            else
                dfd.reject(new DBusError(closed));

            const ret = dfd.promise;
            ret.remove = function remove() {
                if (id in calls) {
                    dfd.reject(new DBusError("cancelled"));
                    delete calls[id];
                }
                send(JSON.stringify({ unwatch: match }));
            };
            return ret;
        };

        self.proxy = function proxy(iface, path, options) {
            if (!iface)
                iface = name;
            iface = String(iface);
            if (!path)
                path = "/" + iface.replaceAll(".", "/");
            let Constructor = self.constructors[iface];
            if (!Constructor)
                Constructor = self.constructors["*"];
            if (!options)
                options = { };
            ensure_cache();
            return new Constructor(self, cache, iface, String(path), options);
        };

        self.proxies = function proxies(iface, path_namespace, options) {
            if (!iface)
                iface = name;
            if (!path_namespace)
                path_namespace = "/";
            if (!options)
                options = { };
            ensure_cache();
            return new DBusProxies(self, cache, String(iface), String(path_namespace), options);
        };
    }

    /* Well known buses */
    const shared_dbus = {
        internal: null,
        session: null,
        system: null,
    };

    /* public */
    cockpit.dbus = function dbus(name, options) {
        if (!options)
            options = { bus: "system" };

        /*
         * Figure out if this we should use a shared bus.
         *
         * This is only the case if a null name *and* the
         * options are just a simple { "bus": "xxxx" }
         */
        const keys = Object.keys(options);
        const bus = options.bus;
        const shared = !name && keys.length == 1 && bus in shared_dbus;

        if (shared && shared_dbus[bus])
            return shared_dbus[bus];

        const client = new DBusClient(name, options);

        /*
         * Store the shared bus for next time. Override the
         * close function to only work when a problem is
         * indicated.
         */
        if (shared) {
            const old_close = client.close;
            client.close = function() {
                if (arguments.length > 0)
                    old_close.apply(client, arguments);
            };
            client.addEventListener("close", function() {
                if (shared_dbus[bus] == client)
                    shared_dbus[bus] = null;
            });
            shared_dbus[bus] = client;
        }

        return client;
    };

    cockpit.variant = function variant(type, value) {
        return { v: value, t: type };
    };

    cockpit.byte_array = function byte_array(string) {
        return window.btoa(string);
    };

    /* File access
     */

    cockpit.file = function file(path, options) {
        options = options || { };
        const binary = options.binary;

        const self = {
            path,
            read,
            replace,
            modify,

            watch,

            close
        };

        const base_channel_options = { ...options };
        delete base_channel_options.syntax;

        function parse(str) {
            if (options.syntax?.parse)
                return options.syntax.parse(str);
            else
                return str;
        }

        function stringify(obj) {
            if (options.syntax?.stringify)
                return options.syntax.stringify(obj);
            else
                return obj;
        }

        let read_promise = null;
        let read_channel;

        function read() {
            if (read_promise)
                return read_promise;

            const dfd = cockpit.defer();
            const opts = {
                ...base_channel_options,
                payload: "fsread1",
                path
            };

            function try_read() {
                read_channel = cockpit.channel(opts);
                const content_parts = [];
                read_channel.addEventListener("message", function (event, message) {
                    content_parts.push(message);
                });
                read_channel.addEventListener("close", function (event, message) {
                    read_channel = null;

                    if (message.problem == "change-conflict") {
                        try_read();
                        return;
                    }

                    read_promise = null;

                    if (message.problem) {
                        const error = new BasicError(message.problem, message.message);
                        fire_watch_callbacks(null, null, error);
                        dfd.reject(error);
                        return;
                    }

                    let content;
                    if (message.tag == "-")
                        content = null;
                    else {
                        try {
                            content = parse(join_data(content_parts, binary));
                        } catch (e) {
                            fire_watch_callbacks(null, null, e);
                            dfd.reject(e);
                            return;
                        }
                    }

                    fire_watch_callbacks(content, message.tag);
                    dfd.resolve(content, message.tag);
                });
            }

            try_read();

            read_promise = dfd.promise;
            return read_promise;
        }

        let replace_channel = null;

        function replace(new_content, expected_tag) {
            const dfd = cockpit.defer();

            let file_content;
            try {
                file_content = (new_content === null) ? null : stringify(new_content);
            } catch (e) {
                dfd.reject(e);
                return dfd.promise;
            }

            if (replace_channel)
                replace_channel.close("abort");

            const opts = {
                ...base_channel_options,
                payload: "fsreplace1",
                path,
                tag: expected_tag
            };
            replace_channel = cockpit.channel(opts);

            replace_channel.addEventListener("close", function (event, message) {
                replace_channel = null;
                if (message.problem) {
                    dfd.reject(new BasicError(message.problem, message.message));
                } else {
                    fire_watch_callbacks(new_content, message.tag);
                    dfd.resolve(message.tag);
                }
            });

            iterate_data(file_content, function(data) {
                replace_channel.send(data);
            });

            replace_channel.control({ command: "done" });
            return dfd.promise;
        }

        function modify(callback, initial_content, initial_tag) {
            const dfd = cockpit.defer();

            function update(content, tag) {
                let new_content = callback(content);
                if (new_content === undefined)
                    new_content = content;
                replace(new_content, tag)
                    .done(function (new_tag) {
                        dfd.resolve(new_content, new_tag);
                    })
                    .fail(function (error) {
                        if (error.problem == "change-conflict")
                            read_then_update();
                        else
                            dfd.reject(error);
                    });
            }

            function read_then_update() {
                read()
                    .done(update)
                    .fail(function (error) {
                        dfd.reject(error);
                    });
            }

            if (initial_content === undefined)
                read_then_update();
            else
                update(initial_content, initial_tag);

            return dfd.promise;
        }

        const watch_callbacks = [];
        let n_watch_callbacks = 0;

        let watch_channel = null;
        let watch_tag;

        function ensure_watch_channel(options) {
            if (n_watch_callbacks > 0) {
                if (watch_channel)
                    return;

                const opts = {
                    payload: "fswatch1",
                    path,
                    superuser: base_channel_options.superuser,
                };
                watch_channel = cockpit.channel(opts);
                watch_channel.addEventListener("message", function (event, message_string) {
                    let message;
                    try {
                        message = JSON.parse(message_string);
                    } catch (e) {
                        message = null;
                    }
                    if (message && message.path == path && message.tag && message.tag != watch_tag) {
                        if (options && options.read !== undefined && !options.read)
                            fire_watch_callbacks(null, message.tag);
                        else
                            read();
                    }
                });
            } else {
                if (watch_channel) {
                    watch_channel.close();
                    watch_channel = null;
                }
            }
        }

        function fire_watch_callbacks(/* content, tag, error */) {
            watch_tag = arguments[1] || null;
            invoke_functions(watch_callbacks, self, arguments);
        }

        function watch(callback, options) {
            if (callback)
                watch_callbacks.push(callback);
            n_watch_callbacks += 1;
            ensure_watch_channel(options);

            watch_tag = null;
            read();

            return {
                remove: function () {
                    if (callback) {
                        const index = watch_callbacks.indexOf(callback);
                        if (index > -1)
                            watch_callbacks[index] = null;
                    }
                    n_watch_callbacks -= 1;
                    ensure_watch_channel(options);
                }
            };
        }

        function close() {
            if (read_channel)
                read_channel.close("cancelled");
            if (replace_channel)
                replace_channel.close("cancelled");
            if (watch_channel)
                watch_channel.close("cancelled");
        }

        return self;
    };

    /* ---------------------------------------------------------------------
     * Localization
     */

    let po_data = { };
    let po_plural;

    cockpit.language = "en";
    cockpit.language_direction = "ltr";
    const test_l10n = window.localStorage.test_l10n;

    cockpit.locale = function locale(po) {
        let lang = cockpit.language;
        let lang_dir = cockpit.language_direction;
        let header;

        if (po) {
            Object.assign(po_data, po);
            header = po[""];
        } else if (po === null) {
            po_data = { };
        }

        if (header) {
            if (header["plural-forms"])
                po_plural = header["plural-forms"];
            if (header.language)
                lang = header.language;
            if (header["language-direction"])
                lang_dir = header["language-direction"];
        }

        cockpit.language = lang;
        cockpit.language_direction = lang_dir;
    };

    cockpit.translate = function translate(/* ... */) {
        let what;

        /* Called without arguments, entire document */
        if (arguments.length === 0)
            what = [document];

        /* Called with a single array like argument */
        else if (arguments.length === 1 && arguments[0].length)
            what = arguments[0];

        /* Called with 1 or more element arguments */
        else
            what = arguments;

        /* Translate all the things */
        const wlen = what.length;
        for (let w = 0; w < wlen; w++) {
            /* The list of things to translate */
            let list = null;
            if (what[w].querySelectorAll)
                list = what[w].querySelectorAll("[translatable], [translate]");
            if (!list)
                continue;

            /* Each element */
            for (let i = 0; i < list.length; i++) {
                const el = list[i];

                let val = el.getAttribute("translate") || el.getAttribute("translatable") || "yes";
                if (val == "no")
                    continue;

                /* Each thing to translate */
                const tasks = val.split(" ");
                val = el.getAttribute("translate-context") || el.getAttribute("context");
                for (let t = 0; t < tasks.length; t++) {
                    if (tasks[t] == "yes" || tasks[t] == "translate")
                        el.textContent = cockpit.gettext(val, el.textContent);
                    else if (tasks[t])
                        el.setAttribute(tasks[t], cockpit.gettext(val, el.getAttribute(tasks[t]) || ""));
                }

                /* Mark this thing as translated */
                el.removeAttribute("translatable");
                el.removeAttribute("translate");
            }
        }
    };

    cockpit.gettext = function gettext(context, string) {
        /* Missing first parameter */
        if (arguments.length == 1) {
            string = context;
            context = undefined;
        }

        const key = context ? context + '\u0004' + string : string;
        if (po_data) {
            const translated = po_data[key];
            if (translated?.[1])
                string = translated[1];
        }

        if (test_l10n === 'true')
            return "»" + string + "«";

        return string;
    };

    function imply(val) {
        return (val === true ? 1 : val || 0);
    }

    cockpit.ngettext = function ngettext(context, string1, stringN, num) {
        /* Missing first parameter */
        if (arguments.length == 3) {
            num = stringN;
            stringN = string1;
            string1 = context;
            context = undefined;
        }

        const key = context ? context + '\u0004' + string1 : string1;
        if (po_data && po_plural) {
            const translated = po_data[key];
            if (translated) {
                const i = imply(po_plural(num)) + 1;
                if (translated[i])
                    return translated[i];
            }
        }
        if (num == 1)
            return string1;
        return stringN;
    };

    cockpit.noop = function noop(arg0, arg1) {
        return arguments[arguments.length - 1];
    };

    /* Only for _() calls here in the cockpit code */
    const _ = cockpit.gettext;

    cockpit.message = function message(arg) {
        if (arg.message)
            return arg.message;

        let problem = null;
        if (arg.problem)
            problem = arg.problem;
        else
            problem = arg + "";
        if (problem == "terminated")
            return _("Your session has been terminated.");
        else if (problem == "no-session")
            return _("Your session has expired. Please log in again.");
        else if (problem == "access-denied")
            return _("Not permitted to perform this action.");
        else if (problem == "authentication-failed")
            return _("Login failed");
        else if (problem == "authentication-not-supported")
            return _("The server refused to authenticate using any supported methods.");
        else if (problem == "unknown-hostkey")
            return _("Untrusted host");
        else if (problem == "unknown-host")
            return _("Untrusted host");
        else if (problem == "invalid-hostkey")
            return _("Host key is incorrect");
        else if (problem == "internal-error")
            return _("Internal error");
        else if (problem == "timeout")
            return _("Connection has timed out.");
        else if (problem == "no-cockpit")
            return _("Cockpit is not installed on the system.");
        else if (problem == "no-forwarding")
            return _("Cannot forward login credentials");
        else if (problem == "disconnected")
            return _("Server has closed the connection.");
        else if (problem == "not-supported")
            return _("Cockpit is not compatible with the software on the system.");
        else if (problem == "no-host")
            return _("Cockpit could not contact the given host.");
        else if (problem == "too-large")
            return _("Too much data");
        else
            return problem;
    };

    function HttpError(arg0, arg1, message) {
        this.status = parseInt(arg0, 10);
        this.reason = arg1;
        this.message = message || arg1;
        this.problem = null;

        this.valueOf = function() {
            return this.status;
        };
        this.toString = function() {
            return this.status + " " + this.message;
        };
    }

    function http_debug() {
        if (window.debugging == "all" || window.debugging?.includes("http"))
            console.debug.apply(console, arguments);
    }

    function find_header(headers, name) {
        if (!headers)
            return undefined;
        name = name.toLowerCase();
        for (const head in headers) {
            if (head.toLowerCase() == name)
                return headers[head];
        }
        return undefined;
    }

    function HttpClient(endpoint, options) {
        const self = this;

        self.options = options;
        options.payload = "http-stream2";

        const active_requests = [];

        if (endpoint !== undefined) {
            if (endpoint.indexOf && endpoint.indexOf("/") === 0) {
                options.unix = endpoint;
            } else {
                const port = parseInt(endpoint, 10);
                if (!isNaN(port))
                    options.port = port;
                else
                    throw Error("The endpoint must be either a unix path or port number");
            }
        }

        if (options.address) {
            if (!options.capabilities)
                options.capabilities = [];
            options.capabilities.push("address");
        }

        function param(obj) {
            return Object.keys(obj).map(function(k) {
                return encodeURIComponent(k) + '=' + encodeURIComponent(obj[k]);
            })
.join('&')
.split('%20')
.join('+'); /* split/join because phantomjs */
        }

        self.request = function request(req) {
            const dfd = cockpit.defer();
            const ret = dfd.promise;

            if (!req.path)
                req.path = "/";
            if (!req.method)
                req.method = "GET";
            if (req.params) {
                if (req.path.indexOf("?") === -1)
                    req.path += "?" + param(req.params);
                else
                    req.path += "&" + param(req.params);
            }
            delete req.params;

            const input = req.body;
            delete req.body;

            const headers = req.headers;
            delete req.headers;

            Object.assign(req, options);

            /* Combine the headers */
            if (options.headers && headers)
                req.headers = { ...options.headers, ...headers };
            else if (options.headers)
                req.headers = options.headers;
            else
                req.headers = headers;

            http_debug("http request:", JSON.stringify(req));

            /* We need a channel for the request */
            const channel = cockpit.channel(req);

            if (input !== undefined) {
                if (input !== "") {
                    http_debug("http input:", input);
                    iterate_data(input, function(data) {
                        channel.send(data);
                    });
                }
                http_debug("http done");
                channel.control({ command: "done" });
            }

            /* Callbacks that want to stream or get headers */
            let streamer = null;
            let responsers = null;

            let resp = null;

            const buffer = channel.buffer(function(data) {
                /* Fire any streamers */
                if (resp && resp.status >= 200 && resp.status <= 299 && streamer)
                    return streamer.call(ret, data);
                return 0;
            });

            function on_control(event, options) {
                /* Anyone looking for response details? */
                if (options.command == "response") {
                    resp = options;
                    if (responsers) {
                        resp.headers = resp.headers || { };
                        invoke_functions(responsers, ret, [resp.status, resp.headers]);
                    }
                }
            }

            function on_close(event, options) {
                const pos = active_requests.indexOf(ret);
                if (pos >= 0)
                    active_requests.splice(pos, 1);

                if (options.problem) {
                    http_debug("http problem: ", options.problem);
                    dfd.reject(new BasicError(options.problem));
                } else {
                    const body = buffer.squash();

                    /* An error, fail here */
                    if (resp && (resp.status < 200 || resp.status > 299)) {
                        let message;
                        const type = find_header(resp.headers, "Content-Type");
                        if (type && !channel.binary) {
                            if (type.indexOf("text/plain") === 0)
                                message = body;
                        }
                        http_debug("http status: ", resp.status);
                        dfd.reject(new HttpError(resp.status, resp.reason, message), body);
                    } else {
                        http_debug("http done");
                        dfd.resolve(body);
                    }
                }

                channel.removeEventListener("control", on_control);
                channel.removeEventListener("close", on_close);
            }

            channel.addEventListener("control", on_control);
            channel.addEventListener("close", on_close);

            ret.stream = function(callback) {
                streamer = callback;
                return ret;
            };
            ret.response = function(callback) {
                if (responsers === null)
                    responsers = [];
                responsers.push(callback);
                return ret;
            };
            ret.input = function(message, stream) {
                if (message !== null && message !== undefined) {
                    http_debug("http input:", message);
                    iterate_data(message, function(data) {
                        channel.send(data);
                    });
                }
                if (!stream) {
                    http_debug("http done");
                    channel.control({ command: "done" });
                }
                return ret;
            };
            ret.close = function(problem) {
                http_debug("http closing:", problem);
                channel.close(problem);
                return ret;
            };

            active_requests.push(ret);
            return ret;
        };

        self.get = function get(path, params, headers) {
            return self.request({
                method: "GET",
                params,
                path,
                body: "",
                headers
            });
        };

        self.post = function post(path, body, headers) {
            headers = headers || { };

            if (is_plain_object(body) || Array.isArray(body)) {
                body = JSON.stringify(body);
                if (find_header(headers, "Content-Type") === undefined)
                    headers["Content-Type"] = "application/json";
            } else if (body === undefined || body === null) {
                body = "";
            } else if (typeof body !== "string") {
                body = String(body);
            }

            return self.request({
                method: "POST",
                path,
                body,
                headers
            });
        };

        self.close = function close(problem) {
            const reqs = active_requests.slice();
            for (let i = 0; i < reqs.length; i++)
                reqs[i].close(problem);
        };
    }

    /* public */
    cockpit.http = function(endpoint, options) {
        if (is_plain_object(endpoint) && options === undefined) {
            options = endpoint;
            endpoint = undefined;
        }
        return new HttpClient(endpoint, options || { });
    };

    /* ---------------------------------------------------------------------
     * Permission
     */

    function check_superuser() {
        return new Promise((resolve, reject) => {
            const ch = cockpit.channel({ payload: "null", superuser: "require" });
            ch.wait()
                .then(() => resolve(true))
                .catch(() => resolve(false))
                .always(() => ch.close());
        });
    }

    function Permission(options) {
        const self = this;
        event_mixin(self, { });

        const api = cockpit.dbus(null, { bus: "internal" }).proxy("cockpit.Superuser", "/superuser");
        api.addEventListener("changed", maybe_reload);

        function maybe_reload() {
            if (api.valid && self.allowed !== null) {
                if (self.allowed != (api.Current != "none"))
                    window.location.reload(true);
            }
        }

        self.allowed = null;
        self.user = options ? options.user : null; // pre-fill for unit tests
        self.is_superuser = options ? options._is_superuser : null; // pre-fill for unit tests

        let group = null;
        let admin = false;

        if (options)
            group = options.group;

        if (options?.admin)
            admin = true;

        function decide(user) {
            if (user.id === 0)
                return true;

            if (group)
                return !!(user.groups || []).includes(group);

            if (admin)
                return self.is_superuser;

            if (user.id === undefined)
                return null;

            return false;
        }

        if (self.user && self.is_superuser !== null) {
            self.allowed = decide(self.user);
        } else {
            Promise.all([cockpit.user(), check_superuser()])
                .then(([user, is_superuser]) => {
                    self.user = user;
                    self.is_superuser = is_superuser;
                    const allowed = decide(user);
                    if (self.allowed !== allowed) {
                        self.allowed = allowed;
                        maybe_reload();
                        self.dispatchEvent("changed");
                    }
                });
        }

        self.close = function close() {
            /* no-op for now */
        };
    }

    cockpit.permission = function permission(arg) {
        return new Permission(arg);
    };

    /* ---------------------------------------------------------------------
     * Metrics
     *
     */

    function MetricsChannel(interval, options_list, cache) {
        const self = this;
        event_mixin(self, { });

        if (options_list.length === undefined)
            options_list = [options_list];

        const channels = [];
        let following = false;

        self.series = cockpit.series(interval, cache, fetch_for_series);
        self.archives = null;
        self.meta = null;

        function fetch_for_series(beg, end, for_walking) {
            if (!for_walking)
                self.fetch(beg, end);
            else
                self.follow();
        }

        function transfer(options_list, callback, is_archive) {
            if (options_list.length === 0)
                return;

            if (!is_archive) {
                if (following)
                    return;
                following = true;
            }

            const options = {
                payload: "metrics1",
                interval,
                source: "internal",
                ...options_list[0]
            };

            delete options.archive_source;

            const channel = cockpit.channel(options);
            channels.push(channel);

            let meta = null;
            let last = null;
            let beg;

            channel.addEventListener("close", function(ev, close_options) {
                if (!is_archive)
                    following = false;

                if (options_list.length > 1 &&
                    (close_options.problem == "not-supported" || close_options.problem == "not-found")) {
                    transfer(options_list.slice(1), callback);
                } else if (close_options.problem) {
                    if (close_options.problem != "terminated" &&
                        close_options.problem != "disconnected" &&
                        close_options.problem != "authentication-failed" &&
                        (close_options.problem != "not-found" || !is_archive) &&
                        (close_options.problem != "not-supported" || !is_archive)) {
                        console.warn("metrics channel failed: " + close_options.problem);
                    }
                } else if (is_archive) {
                    if (!self.archives) {
                        self.archives = true;
                        self.dispatchEvent('changed');
                    }
                }
            });

            channel.addEventListener("message", function(ev, payload) {
                const message = JSON.parse(payload);

                /* A meta message? */
                const message_len = message.length;
                if (message_len === undefined) {
                    meta = message;
                    let timestamp = 0;
                    if (meta.now && meta.timestamp)
                        timestamp = meta.timestamp + (Date.now() - meta.now);
                    beg = Math.floor(timestamp / interval);
                    callback(beg, meta, null, options_list[0]);

                    /* Trigger to outside interest that meta changed */
                    self.meta = meta;
                    self.dispatchEvent('changed');

                /* A data message */
                } else if (meta) {
                    /* Data decompression */
                    for (let i = 0; i < message_len; i++) {
                        const data = message[i];
                        if (last) {
                            for (let j = 0; j < last.length; j++) {
                                const dataj = data[j];
                                if (dataj === null || dataj === undefined) {
                                    data[j] = last[j];
                                } else {
                                    const dataj_len = dataj.length;
                                    if (dataj_len !== undefined) {
                                        const lastj = last[j];
                                        const lastj_len = last[j].length;
                                        let k;
                                        for (k = 0; k < dataj_len; k++) {
                                            if (dataj[k] === null)
                                                dataj[k] = lastj[k];
                                        }
                                        for (; k < lastj_len; k++)
                                            dataj[k] = lastj[k];
                                    }
                                }
                            }
                        }
                        last = data;
                    }

                    /* Return the data */
                    callback(beg, meta, message, options_list[0]);

                    /* Bump timestamp for the next message */
                    beg += message_len;
                    meta.timestamp += (interval * message_len);
                }
            });
        }

        function drain(beg, meta, message, options) {
            /* Generate a mapping object if necessary */
            let mapping = meta.mapping;
            if (!mapping) {
                mapping = { };
                meta.metrics.forEach(function(metric, i) {
                    const map = { "": i };
                    const name = options.metrics_path_names?.[i] ?? metric.name;
                    mapping[name] = map;
                    if (metric.instances) {
                        metric.instances.forEach(function(instance, i) {
                            if (instance === "")
                                instance = "/";
                            map[instance] = { "": i };
                        });
                    }
                });
                meta.mapping = mapping;
            }

            if (message)
                self.series.input(beg, message, mapping);
        }

        self.fetch = function fetch(beg, end) {
            const timestamp = beg * interval - Date.now();
            const limit = end - beg;

            const archive_options_list = [];
            for (let i = 0; i < options_list.length; i++) {
                if (options_list[i].archive_source) {
                    archive_options_list.push({
                                                   ...options_list[i],
                                                   source: options_list[i].archive_source,
                                                   timestamp,
                                                   limit
                                              });
                }
            }

            transfer(archive_options_list, drain, true);
        };

        self.follow = function follow() {
            transfer(options_list, drain);
        };

        self.close = function close(options) {
            const len = channels.length;
            if (self.series)
                self.series.close();

            for (let i = 0; i < len; i++)
                channels[i].close(options);
        };
    }

    cockpit.metrics = function metrics(interval, options) {
        return new MetricsChannel(interval, options);
    };

    /* ---------------------------------------------------------------------
     * Ooops handling.
     *
     * If we're embedded, send oops to parent frame. Since everything
     * could be broken at this point, just do it manually, without
     * involving cockpit.transport or any of that logic.
     */

    cockpit.oops = function oops() {
        if (window.parent !== window && window.name.indexOf("cockpit1:") === 0)
            window.parent.postMessage("\n{ \"command\": \"oops\" }", transport_origin);
    };

    const old_onerror = window.onerror;
    window.onerror = function(msg, url, line) {
        // Errors with url == "" are not logged apparently, so let's
        // not show the "Oops" for them either.
        if (url != "")
            cockpit.oops();
        if (old_onerror)
            return old_onerror(msg, url, line);
        return false;
    };

    cockpit.assert = (predicate, message) => {
        if (!predicate) {
            throw new Error(`Assertion failed: ${message}`);
        }
    };

    return cockpit;
}

// Register cockpit object as global, so that it can be used without ES6 modules
// we need to do that here instead of in pkg/base1/cockpit.js, so that po.js can access cockpit already
window.cockpit = factory();

export default window.cockpit;
0707010000002B000081A4000000000000000000000001662A077800000161000000000000000000000000000000000000002C00000000cockpit-docker-devel-16/pkg/lib/console.css@import "xterm/css/xterm.css";

.console-ct > .terminal {
  display: flex;
  block-size: 100%;
}

.terminal:focus .terminal-cursor {
  border: none;
  animation: blink 1s step-end infinite;
}

/* Ensure the console fits to its container (and doesn't attempt to go beyond the limits) */
.xterm-screen, .xterm-viewport {
  inline-size: auto !important;
}
0707010000002C000081A4000000000000000000000001662A077800000218000000000000000000000000000000000000003200000000cockpit-docker-devel-16/pkg/lib/context-menu.scss.contextMenu {
  position: fixed;
  /* xterm accessibility tree has z-index 100 and we need to be in front of it
     * to be able to handle mouse events.
     */
  z-index: 101;
  /* Move the menu under the mouse */
  transform: translate(calc(-1 * var(--pf-v5-global--spacer--sm)), calc(-1 * var(--pf-v5-global--spacer--sm)));

  &Option .pf-v5-c-menu__item-text {
    display: flex;
    gap: var(--pf-v5-global--spacer--sm);
    justify-content: space-between;
    min-inline-size: 10rem;
  }

  &Shortcut {
    opacity: 0.75;
  }
}
0707010000002D000081A4000000000000000000000001662A0778000002AE000000000000000000000000000000000000004000000000cockpit-docker-devel-16/pkg/lib/credentials-ssh-private-keys.sh#!/bin/sh
set -u

# The first thing we do is list loaded keys
loaded=$(ssh-add -L)
result="$?"

set -e

printf "$loaded"

# Get info for each loaded key
# ssh-keygen -l -f - is not
# supported everywhere so use tempfile
if [ $result -eq 0 ]; then
    tempfile=$(mktemp)
    echo "$loaded" | while read line; do
       echo "$line" > "$tempfile"
       printf "\v%s\v\v" "$line"
       ssh-keygen -l -f "$tempfile" || true
    done
    rm $tempfile
fi

# Try to list keys in this directory
cd "$1" || exit 0

# After that each .pub file gets its on set of blocks
for file in *.pub; do
    printf "\v"
    cat "$file"
    printf "\v%s\v" "$file"
    ssh-keygen -l -f "$file" || true
done
0707010000002E000081A4000000000000000000000001662A07780000007E000000000000000000000000000000000000003E00000000cockpit-docker-devel-16/pkg/lib/credentials-ssh-remove-key.sh#!/bin/sh

set -eu

tempfile=$(mktemp)
echo "$1" > "$tempfile"
ret=0
ssh-add -d "$tempfile" || ret=1
rm "$tempfile"
exit $ret
0707010000002F000081A4000000000000000000000001662A077800002DD4000000000000000000000000000000000000002F00000000cockpit-docker-devel-16/pkg/lib/credentials.js/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
 */

import cockpit from "cockpit";

import lister from "credentials-ssh-private-keys.sh";
import remove_key from "credentials-ssh-remove-key.sh";

const _ = cockpit.gettext;

function Keys() {
    const self = this;

    self.path = null;
    self.items = { };

    let watch = null;
    let proc = null;
    let timeout = null;

    cockpit.event_target(this);

    cockpit.user()
            .then(user => {
                self.path = user.home + '/.ssh';
                refresh();
            });

    function refresh() {
        function on_message(ev, payload) {
            const item = JSON.parse(payload);
            const name = item.path;
            if (name && name.indexOf("/") === -1 && name.slice(-4) === ".pub") {
                if (item.event === "present" || item.event === "created" ||
                item.event === "changed" || item.event === "deleted") {
                    window.clearInterval(timeout);
                    timeout = window.setTimeout(refresh, 100);
                }
            }
        }

        function on_close(ev, data) {
            watch.removeEventListener("close", on_close);
            watch.removeEventListener("message", on_message);
            if (!data.problem || data.problem == "not-found") {
                watch = null; /* Watch again */
            } else {
                console.warn("couldn't watch " + self.path + ": " + (data.message || data.problem));
                watch = false; /* Don't watch again */
            }
        }

        if (watch === null) {
            watch = cockpit.channel({ payload: "fswatch1", path: self.path });
            watch.addEventListener("close", on_close);
            watch.addEventListener("message", on_message);
        }

        if (proc)
            return;

        window.clearTimeout(timeout);
        timeout = null;

        proc = cockpit.script(lister, [self.path], { err: "message" });
        proc
                .then(data => process(data))
                .catch(ex => console.warn("failed to list keys in home directory: " + ex.message))
                .finally(() => {
                    proc = null;

                    if (!timeout)
                        timeout = window.setTimeout(refresh, 5000);
                });
    }

    function process(data) {
        const blocks = data.split('\v');
        let key;
        const items = { };

        /* First block is the data from ssh agent */
        blocks[0].trim().split("\n")
                .forEach(function(line) {
                    key = parse_key(line, items);
                    if (key)
                        key.loaded = true;
                });

        /* Next come individual triples of blocks */
        blocks.slice(1).forEach(function(block, i) {
            switch (i % 3) {
            case 0:
                key = parse_key(block, items);
                break;
            case 1:
                if (key) {
                    block = block.trim();
                    if (block.slice(-4) === ".pub")
                        key.name = block.slice(0, -4);
                    else if (block)
                        key.name = block;
                    else
                        key.agent_only = true;
                }
                break;
            case 2:
                if (key)
                    parse_info(block, key);
                break;
            }
        });

        self.items = items;
        self.dispatchEvent("changed");
    }

    function parse_key(line, items) {
        const parts = line.trim().split(" ");
        let id, type, comment;

        /* SSHv1 keys */
        if (!isNaN(parseInt(parts[0], 10))) {
            id = parts[2];
            type = "RSA1";
            comment = parts.slice(3).join(" ");
        } else if (parts[0].indexOf("ssh-") === 0) {
            id = parts[1];
            type = parts[0].substring(4).toUpperCase();
            comment = parts.slice(2).join(" ");
        } else if (parts[0].indexOf("ecdsa-") === 0) {
            id = parts[1];
            type = "ECDSA";
            comment = parts.slice(2).join(" ");
        } else {
            return;
        }

        let key = items[id];
        if (!key)
            key = items[id] = { };

        key.type = type;
        key.comment = comment;
        key.data = line;
        return key;
    }

    function parse_info(line, key) {
        const parts = line.trim().split(" ")
                .filter(n => !!n);

        key.size = parseInt(parts[0], 10);
        if (isNaN(key.size))
            key.size = null;

        key.fingerprint = parts[1];

        if (!key.name && parts[2] && parts[2].indexOf("/") !== -1)
            key.name = parts[2];
    }

    function ensure_ssh_directory(file) {
        return cockpit.script('dir=$(dirname "$1"); test -e "$dir" || mkdir -m 700 "$dir"', [file]);
    }

    function run_keygen(file, new_type, old_pass, new_pass, two_pass) {
        const old_exps = [/.*Enter old passphrase: $/];
        const new_exps = [/.*Enter passphrase.*/, /.*Enter new passphrase.*/, /.*Enter same passphrase again: $/];
        const bad_exps = [/.*failed: passphrase is too short.*/];

        return new Promise((resolve, reject) => {
            let buffer = "";
            let sent_new = false;
            let failure = _("No such file or directory");

            if (new_pass !== two_pass) {
                reject(new Error(_("The passwords do not match.")));
                return;
            }

            // Exactly one of new_type or old_pass must be given
            console.assert((new_type == null) != (old_pass == null));

            const cmd = ["ssh-keygen", "-f", file];
            if (new_type)
                cmd.push("-t", new_type);
            else
                cmd.push("-p");

            const proc = cockpit.spawn(cmd, { pty: true, environ: ["LC_ALL=C"], err: "out", directory: self.path });

            const timeout = window.setTimeout(() => {
                failure = _("Prompting via ssh-keygen timed out");
                proc.close("terminated");
            }, 10 * 1000);

            proc
                    .stream(data => {
                        buffer += data;
                        if (old_pass) {
                            for (let i = 0; i < old_exps.length; i++) {
                                if (old_exps[i].test(buffer)) {
                                    buffer = "";
                                    failure = _("Old password not accepted");
                                    proc.input(old_pass + "\n", true);
                                    return;
                                }
                            }
                        }

                        for (let i = 0; i < new_exps.length; i++) {
                            if (new_exps[i].test(buffer)) {
                                buffer = "";
                                proc.input(new_pass + "\n", true);
                                failure = _("Failed to change password");
                                sent_new = true;
                                return;
                            }
                        }

                        if (sent_new) {
                            for (let i = 0; i < bad_exps.length; i++) {
                                if (bad_exps[i].test(buffer)) {
                                    failure = _("New password was not accepted");
                                    return;
                                }
                            }
                        }
                    })
                    .then(resolve)
                    .catch(ex => {
                        if (ex.exit_status)
                            ex = new Error(failure);
                        reject(ex);
                    })
                    .finally(() => window.clearInterval(timeout));
        });
    }

    self.change = function change(name, old_pass, new_pass, two_pass) {
        return run_keygen(name, null, old_pass, new_pass, two_pass);
    };

    self.create = function create(name, type, new_pass, two_pass) {
        return ensure_ssh_directory(name)
                .then(() => run_keygen(name, type, null, new_pass, two_pass));
    };

    self.get_pubkey = function get_pubkey(name) {
        return cockpit.file(name + ".pub").read();
    };

    self.load = function(name, password) {
        const ask_exp = /.*Enter passphrase for .*/;
        const perm_exp = /.*UNPROTECTED PRIVATE KEY FILE.*/;
        const bad_exp = /.*Bad passphrase.*/;

        let buffer = "";
        let output = "";
        let failure = _("Not a valid private key");
        let sent_password = false;

        return new Promise((resolve, reject) => {
            const proc = cockpit.spawn(["ssh-add", name],
                                       { pty: true, environ: ["LC_ALL=C"], err: "out", directory: self.path });

            const timeout = window.setTimeout(() => {
                failure = _("Prompting via ssh-add timed out");
                proc.close("terminated");
            }, 10 * 1000);

            proc
                    .stream(data => {
                        buffer += data;
                        output += data;
                        if (perm_exp.test(buffer)) {
                            failure = _("Invalid file permissions");
                            buffer = "";
                        } else if (ask_exp.test(buffer)) {
                            buffer = "";
                            failure = _("Password not accepted");
                            proc.input(password + "\n", true);
                            sent_password = true;
                        } else if (bad_exp.test(buffer)) {
                            buffer = "";
                            proc.input("\n", true);
                        }
                    })
                    .then(() => {
                        refresh();
                        resolve();
                    })
                    .catch(ex => {
                        console.log(output);
                        if (ex.exit_status)
                            ex = new Error(failure);

                        ex.sent_password = sent_password;
                        reject(ex);
                    })
                    .finally(() => window.clearInterval(timeout));
        });
    };

    self.unload = function unload(key) {
        const options = { pty: true, err: "message", directory: self.path };

        const proc = (key.name && !key.agent_only)
            ? cockpit.spawn(["ssh-add", "-d", key.name], options)
            : cockpit.script(remove_key, [key.data], options);

        return proc.then(refresh);
    };

    self.close = function close() {
        if (watch)
            watch.close();
        if (proc)
            proc.close();
        window.clearTimeout(timeout);
        timeout = null;
    };
}

export function keys_instance() {
    return new Keys();
}
07070100000030000081A4000000000000000000000001662A077800000749000000000000000000000000000000000000002D00000000cockpit-docker-devel-16/pkg/lib/ct-card.scss@use "_global-variables.scss" as *;

/* Rely on the margin from the Card for spacing */
.ct-card.pf-v5-c-card .table {
  margin-block-end: 0;
}

// FIXME: Once PF4 provides a property for removing padding: https://github.com/patternfly/patternfly-react/issues/5606
.ct-card.pf-v5-c-card .pf-v5-c-card__body.contains-list {
  padding-inline: 0;
  padding-block-end: 0;

  > .pf-v5-c-table > :last-child > tr:last-child {
    border-block-end: none;
  }

  // Remove excess padding from compact tables toggles
  // And adjust the padding to left align the toggles with the card header
  > .pf-v5-c-table {
    .pf-v5-c-table__toggle {
      padding-inline-start: 0;

      > .pf-v5-c-button {
        padding-inline-start: var(--pf-v5-global--spacer--lg);
      }
    }
  }
}

.ct-card.pf-v5-c-card .pf-v5-c-card__title-text {
  font-weight: normal;
  font-size: var(--pf-v5-global--FontSize--2xl);
}

// Remove excess top padding from top-level empty state in cards,
// as card headers already add enough space
.ct-card > .pf-v5-c-card__body > .pf-v5-c-empty-state {
  --pf-v5-c-empty-state__body--MarginTop: 0;
  padding-block: 0 var(--pf-v5-global--spacer--md);
}

.ct-cards-grid {
  --ct-grid-columns: 2;
  --pf-v5-l-gallery--GridTemplateColumns: repeat(var(--ct-grid-columns), 1fr);

  > .pf-v5-c-card:not(.ct-card-info) {
    // Extend all non-info cards to be full width;
    // let ct-card-info fit 1 column of the grid
    grid-column: 1 / -1;
  }

  @media screen and (max-width: $pf-v5-global--breakpoint--lg) {
    // Shrink to 1 column when space is constrained
    --ct-grid-columns: 1;
  }
}

// Remove redundant padding from embedded toolbars (handled by header)
// Toolbars in card headers are not a common scenario so no need to upstream this
.ct-card.pf-v5-c-card .pf-v5-c-toolbar,
.ct-card.pf-v5-c-card .pf-v5-c-toolbar__content {
  padding: 0;
}
07070100000031000081A4000000000000000000000001662A0778000011A7000000000000000000000000000000000000002C00000000cockpit-docker-devel-16/pkg/lib/dialogs.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2022 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 <http://www.gnu.org/licenses/>.
 */

/* DIALOG PRESENTATION PROTOCOL
 *
 * Example:
 *
 * import { WithDialogs, useDialogs } from "dialogs.jsx";
 *
 * const App = () =>
 *   <WithDialogs>
 *     <Page>
 *       <ExampleButton />
 *     </Page>
 *   </WithDialogs>;
 *
 * const ExampleButton = () => {
 *   const Dialogs = useDialogs();
 *   return <Button onClick={() => Dialogs.show(<MyDialog />)}>Open dialog</Button>;
 * };
 *
 * const MyDialog = () => {
 *   const Dialogs = useDialogs();
 *   return (
 *     <Modal title="My dialog"
 *            isOpen
 *            onClose={Dialogs.close}>
 *       <p>Hello!</p>
 *     </Modal>);
 * };
 *
 * This does two things: It maintains the state of whether the dialog
 * is open, and it does it high up in the DOM, in a stable place.
 * Even if ExampleButton is no longer part of the DOM, the dialog will
 * stay open and remain useable.
 *
 * The "WithDialogs" component enables all its children to show
 * dialogs.  Such a dialog will stay open as long as the WithDialogs
 * component itself is mounted.  Thus, you should put the WithDialogs
 * component somewhere high up in your component tree, maybe even as
 * the very top-most component.
 *
 * If your Cockpit application has multiple pages and navigation
 * between these pages is controlled by the browser URL, then each of
 * these pages should have its own WithDialogs wrapper. This way, a
 * dialog opened on one page closes when the user navigates away from
 * that page. To make sure that React maintains separate states for
 * WithDialogs components, give them unique "key" properties.
 *
 * A component that wants to show a dialogs needs to get hold of the
 * current "Dialogs" context and then call it's "show" method.  For a
 * function component the Dialogs context is returned by
 * "useDialogs()", as shown above in the example.
 *
 * A class component can declare a static context type and then use
 * "this.context" to find the Dialogs object:
 *
 * import { DialogsContext } from "dialogs.jsx";
 *
 * class ExampleButton extends React.Component {
 *   static contextType = DialogsContext;
 *
 *   function render() {
 *     const Dialogs = this.context;
 *     return <Button onClick={() => Dialogs.show(<MyDialog />)}>Open dialog</Button>;
 *   }
 * }
 *
 *
 * - Dialogs.show(component)
 *
 * Calling "Dialogs.show" will render the given component as a direct
 * child of the inner-most enclosing "WithDialogs" component.  The
 * component is of course intended to be a dialog, such as
 * Patternfly's "Modal".  There is only ever one of these; a second
 * call to "show" will remove the previously rendered component.
 * Passing "null" will remove the currently rendered componenet, if any.
 *
 * - Dialogs.close()
 *
 * Same as "Dialogs.show(null)".
 */

import React, { useContext, useRef, useState } from "react";

export const DialogsContext = React.createContext();
export const useDialogs = () => useContext(DialogsContext);

export const WithDialogs = ({ children }) => {
    const is_open = useRef(false); // synchronous
    const [dialog, setDialog] = useState(null);

    const Dialogs = {
        show: component => {
            if (component && is_open.current)
                console.error("Dialogs.show() called for",
                              JSON.stringify(component),
                              "while a dialog is already open:",
                              JSON.stringify(dialog));
            is_open.current = !!component;
            setDialog(component);
        },
        close: () => {
            is_open.current = false;
            setDialog(null);
        },
        isActive: () => dialog !== null
    };

    return (
        <DialogsContext.Provider value={Dialogs}>
            {children}
            {dialog}
        </DialogsContext.Provider>);
};
07070100000032000081A4000000000000000000000001662A07780000021D000000000000000000000000000000000000003A00000000cockpit-docker-devel-16/pkg/lib/esbuild-cleanup-plugin.jsimport fs from 'fs';
import path from 'path';

// always start with a fresh dist/ directory, to change between development and production, or clean up gzipped files
export const cleanPlugin = ({ outdir = './dist', subdir = '' } = {}) => ({
    name: 'clean-dist',
    setup(build) {
        build.onStart(() => {
            try {
                fs.rmSync(path.resolve(outdir, subdir), { recursive: true });
            } catch (e) {
                if (e.code !== 'ENOENT')
                    throw e;
            }
        });
    }
});
07070100000033000081A4000000000000000000000001662A0778000004F5000000000000000000000000000000000000003200000000cockpit-docker-devel-16/pkg/lib/esbuild-common.jsimport { replace } from 'esbuild-plugin-replace';
import { sassPlugin } from 'esbuild-sass-plugin';

// List of directories to use when resolving import statements
const nodePaths = ['pkg/lib'];

export const esbuildStylesPlugins = [
    // Redefine grid breakpoints to count with our shell
    // See https://github.com/patternfly/patternfly-react/issues/3815 and
    // [Redefine grid breakpoints] section in pkg/lib/_global-variables.scss for explanation
    replace({
        include: /\.css$/,
        values: {
            // Do not override the sm breakpoint as for width < 768px the left nav is hidden
            '768px': '428px',
            '992px': '652px',
            '1200px': '876px',
            '1450px': '1100px',
        }
    }),
    sassPlugin({
        loadPaths: [...nodePaths, 'node_modules'],
        quietDeps: true,
        async transform(source, resolveDir, path) {
            if (path.includes('patternfly-5-cockpit.scss')) {
                return source
                        .replace(/url.*patternfly-icons-fake-path.*;/g, 'url("../base1/fonts/patternfly.woff") format("woff");')
                        .replace(/@font-face[^}]*patternfly-fonts-fake-path[^}]*}/g, '');
            }
            return source;
        }
    }),
];
07070100000034000081A4000000000000000000000001662A07780000064E000000000000000000000000000000000000003B00000000cockpit-docker-devel-16/pkg/lib/esbuild-compress-plugin.js/* There is https://www.npmjs.com/package/esbuild-plugin-compress but it does
 * not work together with our PO plugin, they are incompatible due to requiring
 * different values for `write:`. We may be able to change our plugins to work
 * with `write: false`, but this is easy enough to implement ourselves.
*/

import path from 'path';
import fs from "fs";
import util from 'node:util';
import child_process from 'node:child_process';

const NAME = 'cockpitCompressPlugin';

const exec = util.promisify(child_process.execFile);

const getAllFiles = function(dirPath, arrayOfFiles) {
    const files = fs.readdirSync(dirPath);

    arrayOfFiles = arrayOfFiles || [];

    files.forEach(function(file) {
        if (fs.statSync(dirPath + "/" + file).isDirectory()) {
            arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles);
        } else {
            arrayOfFiles.push(path.join(dirPath, "/", file));
        }
    });

    return arrayOfFiles;
};

export const cockpitCompressPlugin = ({ subdir = '', exclude = null } = {}) => ({
    name: NAME,
    setup(build) {
        build.onEnd(async () => {
            const gzipPromises = [];
            const path = "./dist/" + subdir;

            for await (const dirent of getAllFiles(path)) {
                if (exclude && exclude.test(dirent))
                    continue;
                if (dirent.endsWith('.js') || dirent.endsWith('.css')) {
                    gzipPromises.push(exec('gzip', ['-n9', dirent]));
                }
            }
            await Promise.all(gzipPromises);
            return null;
        });
    }
});
07070100000035000081A4000000000000000000000001662A0778000003F6000000000000000000000000000000000000003C00000000cockpit-docker-devel-16/pkg/lib/esbuild-test-html-plugin.jsimport fs from "fs";
import path from 'path';
import _ from 'lodash';

const srcdir = process.env.SRCDIR || '.';
const libdir = path.resolve(srcdir, "pkg", "lib");

export const cockpitTestHtmlPlugin = ({ testFiles }) => ({
    name: 'CockpitTestHtmlPlugin',
    setup(build) {
        build.onEnd(async () => {
            const data = fs.readFileSync(path.resolve(libdir, "qunit-template.html.in"), "utf8");
            testFiles.forEach(file => {
                const test = path.parse(file).name;
                const output = _.template(data.toString())({
                    title: test,
                    builddir: file.split("/").map(() => "../").join(""),
                    script: test + '.js',
                });
                const outdir = './qunit/' + path.dirname(file);
                const outfile = test + ".html";

                fs.mkdirSync(outdir, { recursive: true });
                fs.writeFileSync(path.resolve(outdir, outfile), output);
            });
        });
    }
});
07070100000036000081A4000000000000000000000001662A07780000088D000000000000000000000000000000000000003800000000cockpit-docker-devel-16/pkg/lib/get-timesync-backend.pyimport os
import subprocess

# get-timesync-backend - determine which NTP backend unit timedatectl
#                        will likely enable.

roots = ["/etc", "/run", "/usr/local", "/usr/lib", "/lib"]


def gather_files(name, suffix):
    # This function creates a list of files in the same order that
    # systemd will read them in.
    #
    # First we collect all files in all root directories.  Duplicates
    # are avoided by only storing files with a basename that hasn't
    # been seen yet.  The roots are processed in order so that files
    # in /etc override identically named files in /usr, for example.
    #
    # The files are stored in a dict with their basename (such as
    # "10-chrony.list") as the key and their full pathname (such as
    # "/usr/lib/systemd/ntp-units.d/10-chrony.list") as the value.
    #
    # This arrangement allows for easy checks for duplicate basenames
    # while retaining access to the full pathname later when creating
    # the final result.
    #
    pathname_by_basename = {}
    for r in roots:
        dirname = os.path.join(r, name)
        if os.path.isdir(dirname):
            for basename in os.listdir(dirname):
                if basename.endswith(suffix) and basename not in pathname_by_basename:
                    pathname_by_basename[basename] = os.path.join(dirname, basename)

    # Then we create a list of the full pathnames, sorted by their
    # basenames.
    #
    sorted_basenames = sorted(pathname_by_basename.keys())
    return [pathname_by_basename[basename] for basename in sorted_basenames]


def unit_exists(unit):
    load_state = subprocess.check_output(["systemctl", "show", "--value", "-p", "LoadState", unit],
                                         universal_newlines=True).strip()
    return load_state != "not-found" and load_state != "masked"


def first_unit(files):
    for f in files:
        with open(f) as c:
            for ll in c.readlines():
                w = ll.strip()
                if w != "" and not w.startswith("#") and unit_exists(w):
                    return w
    return None


unit = first_unit(gather_files("systemd/ntp-units.d", ".list"))

if unit:
    print(unit)
07070100000037000081A4000000000000000000000001662A077800002ED6000000000000000000000000000000000000002900000000cockpit-docker-devel-16/pkg/lib/hooks.ts/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
 */

import cockpit from 'cockpit';
import { useState, useEffect, useRef, useReducer } from 'react';
import deep_equal from "deep-equal";

/* HOOKS
 *
 * These are some custom React hooks for Cockpit specific things.
 *
 * Overview:
 *
 * - usePageLocation: For following along with cockpit.location.
 *
 * - useLoggedInUser: For accessing information about the currently
 * logged in user.
 *
 * - useFile: For reading and watching files.
 *
 * - useObject: For maintaining arbitrary stateful objects that get
 * created from the properties of a component.
 *
 * - useEvent: For reacting to events emitted by arbitrary objects.
 *
 * - useInit: For running a function once.
 *
 * - useDeepEqualMemo: A utility hook that can help with things that
 * need deep equal comparisons in places where React only offers
 * Object identity comparisons, such as with useEffect.
 */

/* - usePageLocation()
 *
 * function Component() {
 *   const location = usePageLocation();
 *   const { path, options } = usePageLocation();
 *
 *   ...
 * }
 *
 * This returns the current value of cockpit.location and the
 * component is re-rendered when it changes. "location" is always a
 * valid object and never null.
 *
 * See https://cockpit-project.org/guide/latest/cockpit-location.html
 */

export function usePageLocation() {
    const [location, setLocation] = useState(cockpit.location);

    useEffect(() => {
        function update() { setLocation(cockpit.location) }
        cockpit.addEventListener("locationchanged", update);
        return () => cockpit.removeEventListener("locationchanged", update);
    }, []);

    return location;
}

/* - useLoggedInUser()
 *
 * function Component() {
 *   const user_info = useLoggedInUser();
 *
 *   ...
 * }
 *
 * "user_info" is the object delivered by cockpit.user(), or null
 * while that object is not yet available.
 */

const cockpit_user_promise = cockpit.user();
let cockpit_user: cockpit.UserInfo | null = null;
cockpit_user_promise.then(user => { cockpit_user = user }).catch(err => console.log(err));

export function useLoggedInUser() {
    const [user, setUser] = useState<cockpit.UserInfo | null>(cockpit_user);
    useEffect(() => { if (!cockpit_user) cockpit_user_promise.then(setUser); }, []);
    return user;
}

/* - useDeepEqualMemo(value)
 *
 * function Component(options) {
 *   const memo_options = useDeepEqualMemo(options);
 *   useEffect(() => {
 *       const channel = cockpit.channel(..., memo_options);
 *       ...
 *       return () => channel.close();
 *   }, [memo_options]);
 *
 *   ...
 * }
 *
 * function ParentComponent() {
 *     const options = { superuser: "require", host: "localhost" };
 *     return <Component options={options}/>
 * }
 *
 * Sometimes a useEffect hook has a deeply nested object as one of its
 * dependencies, such as options for a Cockpit channel.  However,
 * React will compare dependency values with Object.is, and would run
 * the effect hook too often.  In the example above, the "options"
 * variable of Component is a different object on each render
 * according to Object.is, but we only want to open a new channel when
 * the value of a field such as "superuser" or "host" has actually
 * changed.
 *
 * A call to useDeepEqualMemo will return some object that is deeply
 * equal to its argument, and it will continue to return the same
 * object (according to Object.is) until the parameter is not deeply
 * equal to it anymore.
 *
 * For the example, this means that "memo_options" will always be the
 * very same object, and the effect hook is only run once.  If we
 * would use "options" directly as a dependency of the effect hook,
 * the channel would be closed and opened on every render. This is
 * very inefficient, doesn't give the asynchronous channel time to do
 * its job, and will also lead to infinite loops when events on the
 * channel cause re-renders (which in turn will run the effect hook
 * again, which will cause a new event, ...).
 */

export function useDeepEqualMemo<T>(value: T): T {
    const ref = useRef(value);
    if (!deep_equal(ref.current, value))
        ref.current = value;
    return ref.current;
}

/* - useFile(path, options)
 * - useFileWithError(path, options)
 *
 * function Component() {
 *   const content = useFile("/etc/hostname", { superuser: "try" });
 *   const [content, error] = useFileWithError("/etc/hostname", { superuser: "try" });
 *
 *   ...
 * }
 *
 * The "path" and "options" parameters are passed unchanged to
 * cockpit.file().  Thus, if you need to parse the content of the
 * file, the best way to do that is via the "syntax" option.
 *
 * The "content" variable will reflect the content of the file
 * "/etc/hostname". When the file changes on disk, the component will
 * be re-rendered with the new content.
 *
 * When the file does not exist or there has been some error reading
 * it, "content" will be false.
 *
 * The "error" variable will contain any errors encountered while
 * reading the file.  It is false when there are no errors.
 *
 * When the file does not exist, "error" will be false.
 *
 * The "content" and "error" variables will be null until the file has
 * been read for the first time.
 *
 * useFile and useFileWithError are pretty much the same. useFile will
 * hide the exact error from the caller, which makes it slightly
 * cleaner to use when the exact error is not part of the UI. In the
 * case of error, useFile will log that error to the console and
 * return false.
 */

type UseFileWithErrorOptions = {
    log_errors?: boolean;
};

export function useFileWithError(path: string, options: cockpit.JsonObject, hook_options: UseFileWithErrorOptions) {
    const [content_and_error, setContentAndError] = useState<[string | false | null, cockpit.BasicError | false | null]>([null, null]);
    const memo_options = useDeepEqualMemo(options);
    const memo_hook_options = useDeepEqualMemo(hook_options);

    useEffect(() => {
        const handle = cockpit.file(path, memo_options);
        handle.watch((data, _tag, error) => {
            setContentAndError([data || false, error || false]);
            if (!data && memo_hook_options?.log_errors)
                console.warn("Can't read " + path + ": " + (error ? error.toString() : "not found"));
        });
        return handle.close;
    }, [path, memo_options, memo_hook_options]);

    return content_and_error;
}

export function useFile(path: string, options: cockpit.JsonObject) {
    const [content] = useFileWithError(path, options, { log_errors: true });
    return content;
}

/* - useObject(create, destroy, dependencies, comparators)
 *
 * function Component(param) {
 *   const obj = useObject(() => create_object(param),
 *                         obj => obj.close(),
 *                         [param] as const, [deep_equal])
 *
 *   ...
 * }
 *
 * This will call "create_object(param)" before the first render of
 * the component, and will call "obj.close()" after the last render.
 *
 * More precisely, create_object will be called as part of the first
 * call to useObject, i.e., at the very beginning of the first render.
 *
 * When "param" changes compared to the previous call to useObject
 * (according to the deep_equal function in the example above), the
 * object will also be destroyed and a new one will be created for the
 * new value of "param" (as part of the call to useObject).
 *
 * There is no time when the "obj" variable is null in the example
 * above; the first render already has a fully created object.  This
 * is an advantage that useObject has over useEffect, which you might
 * otherwise use to only create objects when dependencies have
 * changed.
 *
 * And unlike useMemo, useObject will run a cleanup function when a
 * component is removed.  Also unlike useMemo, useObject guarantees
 * that it will not ignore the dependencies.
 *
 * The dependencies are an array of values that are by default
 * compared with Object.is.  If you need to use a custom comparator
 * function instead of Object.is, you can provide a second
 * "comparators" array that parallels the "dependencies" array.  The
 * values at a given index in the old and new "dependencies" arrays
 * are compared with the function at the same index in "comparators".
 */

type Tuple = readonly [...unknown[]];
type Comparator<T> = (a: T, b: T) => boolean;
type Comparators<T extends Tuple> = {[ t in keyof T ]?: Comparator<T[t]>};

function deps_changed<T extends Tuple>(old_deps: T | null, new_deps: T, comps: Comparators<T>): boolean {
    return (!old_deps || old_deps.length != new_deps.length ||
            old_deps.findIndex((o, i) => !(comps[i] || Object.is)(o, new_deps[i])) >= 0);
}

export function useObject<T, D extends Tuple>(create: () => T, destroy: ((value: T) => void) | null, deps: D, comps?: Comparators<D>): T {
    const ref = useRef<T | null>(null);
    const deps_ref = useRef<D | null>(null);
    const destroy_ref = useRef<((value: T) => void) | null>(destroy);

    /* Since each item in Comparators<> is optional, `[]` should be valid here
     * but for some reason it doesn't work — but `{}` does.
     */
    if (deps_changed(deps_ref.current, deps, comps || {})) {
        if (ref.current && destroy)
            destroy(ref.current);
        ref.current = create();
        deps_ref.current = deps;
    }

    destroy_ref.current = destroy;
    useEffect(() => {
        return () => { destroy_ref.current?.(ref.current!) };
    }, []);

    return ref.current!;
}

/* - useEvent(obj, event, handler)
 *
 * function Component(proxy) {
 *   useEvent(proxy, "changed");
 *
 *   ...
 * }
 *
 * The component will be re-rendered whenever "proxy" emits the
 * "changed" signal.  The "proxy" parameter can be null.
 *
 * When the optional "handler" is given, it will be called with the
 * arguments of the event.
 */

export function useEvent<EM extends cockpit.EventMap, E extends keyof EM>(obj: cockpit.EventSource<EM>, event: E, handler?: cockpit.EventListener<EM[E]>) {
    // We increase a (otherwise unused) state variable whenever the event
    // happens.  That reliably triggers a re-render.

    const [, forceUpdate] = useReducer(x => x + 1, 0);

    useEffect(() => {
        function update(...args: Parameters<cockpit.EventListener<EM[E]>>) {
            if (handler)
                handler(...args);
            forceUpdate();
        }

        obj?.addEventListener(event, update);
        return () => obj?.removeEventListener(event, update);
    }, [obj, event, handler]);
}

/* - useInit(func, deps, comps)
 *
 * function Component(arg) {
 *   useInit(() => {
 *     cockpit.spawn([ ..., arg ]);
 *   }, [arg]);
 *
 *   ...
 * }
 *
 * The function will be called once during the first render, and
 * whenever "arg" changes.
 *
 * "useInit(func, deps, comps)" is the same as "useObject(func, null,
 * deps, comps)" but if you want to emphasize that you just want to
 * run a function (instead of creating a object), it is clearer to use
 * the "useInit" name for that.  Also, "deps" are optional for
 * "useInit" and default to "[]".
 */

export function useInit<T, D extends Tuple>(func: () => T, deps: D, comps?: Comparators<D>, destroy: ((value: T) => void) | null = null): T {
    return useObject(func, destroy, deps || [], comps);
}
07070100000038000081ED000000000000000000000001662A07780000195A000000000000000000000000000000000000002B00000000cockpit-docker-devel-16/pkg/lib/html2po.js#!/usr/bin/env node

/*
 * Extracts translatable strings from HTML files in the following forms:
 *
 * <tag translate>String</tag>
 * <tag translate context="value">String</tag>
 * <tag translate="...">String</tag>
 * <tag translate-attr attr="String"></tag>
 *
 * Supports the following angular-gettext compatible forms:
 *
 * <translate>String</translate>
 * <tag translate-plural="Plural">Singular</tag>
 *
 * Note that some of the use of the translated may not support all the strings
 * depending on the code actually using these strings to translate the HTML.
 */

import fs from 'fs';
import path from 'path';
import htmlparser from 'htmlparser';
import { ArgumentParser } from 'argparse';

function fatal(message, code) {
    console.log((filename || "html2po") + ": " + message);
    process.exit(code || 1);
}

const parser = new ArgumentParser();
parser.add_argument('-d', '--directory', { help: "Base directory for input files" });
parser.add_argument('-o', '--output', { help: 'Output file', required: true });
parser.add_argument('files', { nargs: '+', help: "One or more input files", metavar: "FILE" });
const args = parser.parse_args();

const input = args.files;
const entries = { };

/* Filename being parsed and offset of line number */
let filename = null;
let offsets = 0;

/* The HTML parser we're using */
const handler = new htmlparser.DefaultHandler(function(error, dom) {
    if (error)
        fatal(error);
    else
        walk(dom);
});

/* Now process each file in turn */
step();

function step() {
    filename = input.shift();
    if (filename === undefined) {
        finish();
        return;
    }

    /* Qualify the filename if necessary */
    let full = filename;
    if (args.directory)
        full = path.join(args.directory, filename);

    fs.readFile(full, { encoding: "utf-8" }, function(err, data) {
        if (err)
            fatal(err.message);

        const parser = new htmlparser.Parser(handler, { includeLocation: true });
        parser.parseComplete(data);
        step();
    });
}

/* Process an array of nodes */
function walk(children) {
    if (!children)
        return;

    children.forEach(function(child) {
        const line = (child.location || { }).line || 0;
        const offset = line - 1;

        /* Scripts get their text processed as HTML */
        if (child.type == 'script' && child.children) {
            const parser = new htmlparser.Parser(handler, { includeLocation: true });

            /* Make note of how far into the outer HTML file we are */
            offsets += offset;

            child.children.forEach(function(node) {
                parser.parseChunk(node.raw);
            });
            parser.done();

            offsets -= offset;

        /* Tags get extracted as usual */
        } else if (child.type == 'tag') {
            tag(child);
        }
    });
}

/* Process a single loaded tag */
function tag(node) {
    let tasks, line, entry;
    const attrs = node.attribs || { };
    let nest = true;

    /* Extract translate strings */
    if ("translate" in attrs || "translatable" in attrs) {
        tasks = (attrs.translate || attrs.translatable || "yes").split(" ");

        /* Calculate the line location taking into account nested parsing */
        line = (node.location || { }).line || 0;
        line += offsets;

        entry = {
            msgctxt: attrs['translate-context'] || attrs.context,
            msgid_plural: attrs['translate-plural'],
            locations: [filename + ":" + line]
        };

        /* For each thing listed */
        tasks.forEach(function(task) {
            const copy = Object.assign({}, entry);

            /* The element text itself */
            if (task == "yes" || task == "translate") {
                copy.msgid = extract(node.children);
                nest = false;

            /* An attribute */
            } else if (task) {
                copy.msgid = attrs[task];
            }

            if (copy.msgid)
                push(copy);
        });
    }

    /* Walk through all the children */
    if (nest)
        walk(node.children);
}

/* Push an entry onto the list */
function push(entry) {
    const key = entry.msgid + "\0" + entry.msgid_plural + "\0" + entry.msgctxt;
    const prev = entries[key];
    if (prev) {
        prev.locations = prev.locations.concat(entry.locations);
    } else {
        entries[key] = entry;
    }
}

/* Extract the given text */
function extract(children) {
    if (!children)
        return null;

    const str = [];
    children.forEach(function(node) {
        if (node.type == 'tag' && node.children)
            str.push(extract(node.children));
        else if (node.type == 'text' && node.data)
            str.push(node.data);
    });

    const msgid = str.join("");

    if (msgid != msgid.trim()) {
        console.error("FATAL: string contains leading or trailing whitespace:", msgid);
        process.exit(1);
    }

    return msgid;
}

/* Escape a string for inclusion in po file */
function escape(string) {
    const bs = string.split('\\')
            .join('\\\\')
            .split('"')
            .join('\\"');
    return bs.split("\n").map(function(line) {
        return '"' + line + '"';
    }).join("\n");
}

/* Finish by writing out the strings */
function finish() {
    const result = [
        'msgid ""',
        'msgstr ""',
        '"Project-Id-Version: PACKAGE_VERSION\\n"',
        '"MIME-Version: 1.0\\n"',
        '"Content-Type: text/plain; charset=UTF-8\\n"',
        '"Content-Transfer-Encoding: 8bit\\n"',
        '"X-Generator: Cockpit html2po\\n"',
        '',
    ];

    for (const msgid in entries) {
        const entry = entries[msgid];
        result.push('#: ' + entry.locations.join(" "));
        if (entry.msgctxt)
            result.push('msgctxt ' + escape(entry.msgctxt));
        result.push('msgid ' + escape(entry.msgid));
        if (entry.msgid_plural) {
            result.push('msgid_plural ' + escape(entry.msgid_plural));
            result.push('msgstr[0] ""');
            result.push('msgstr[1] ""');
        } else {
            result.push('msgstr ""');
        }
        result.push('');
    }

    const data = result.join('\n');
    if (!args.output) {
        process.stdout.write(data);
        process.exit(0);
    } else {
        fs.writeFile(args.output, data, function(err) {
            if (err)
                fatal(err.message);
            process.exit(0);
        });
    }
}
07070100000039000081A4000000000000000000000001662A07780000002D000000000000000000000000000000000000002B00000000cockpit-docker-devel-16/pkg/lib/index.d.tsinterface Window {
    debugging?: string;
}
0707010000003A000081A4000000000000000000000001662A077800000A1D000000000000000000000000000000000000002B00000000cockpit-docker-devel-16/pkg/lib/inotify.py#
# This file is part of Cockpit.
#
# Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.

import ctypes
import os
import struct
import sys

IN_CLOSE_WRITE = 0x00000008
IN_MOVED_FROM = 0x00000040
IN_MOVED_TO = 0x00000080
IN_CREATE = 0x00000100
IN_DELETE = 0x00000200
IN_DELETE_SELF = 0x00000400
IN_MOVE_SELF = 0x00000800
IN_IGNORED = 0x00008000


class Inotify:
    def __init__(self):
        self._libc = ctypes.CDLL(None, use_errno=True)
        self._get_errno_func = ctypes.get_errno

        self._libc.inotify_init.argtypes = []
        self._libc.inotify_init.restype = ctypes.c_int
        self._libc.inotify_add_watch.argtypes = [ctypes.c_int, ctypes.c_char_p,
                                                 ctypes.c_uint32]
        self._libc.inotify_add_watch.restype = ctypes.c_int
        self._libc.inotify_rm_watch.argtypes = [ctypes.c_int, ctypes.c_int]
        self._libc.inotify_rm_watch.restype = ctypes.c_int

        self.fd = self._libc.inotify_init()

    def add_watch(self, path, mask):
        path = ctypes.create_string_buffer(path.encode(sys.getfilesystemencoding()))
        wd = self._libc.inotify_add_watch(self.fd, path, mask)
        if wd < 0:
            sys.stderr.write("can't add watch for %s: %s\n" % (path, os.strerror(self._get_errno_func())))
        return wd

    def rem_watch(self, wd):
        if self._libc.inotify_rm_watch(self.fd, wd) < 0:
            sys.stderr.write("can't remove watch: %s\n" % (os.strerror(self._get_errno_func())))

    def process(self, callback):
        buf = os.read(self.fd, 4096)
        pos = 0
        while pos < len(buf):
            (wd, mask, _cookie, name_len) = struct.unpack('iIII', buf[pos:pos + 16])
            pos += 16
            (name,) = struct.unpack('%ds' % name_len, buf[pos:pos + name_len])
            pos += name_len
            callback(wd, mask, name.decode().rstrip('\0'))

    def run(self, callback):
        while True:
            self.process(callback)
0707010000003B000081A4000000000000000000000001662A077800000FDA000000000000000000000000000000000000002C00000000cockpit-docker-devel-16/pkg/lib/journal.css/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
 */

.cockpit-log-panel:empty {
  border: none;
}

.cockpit-log-panel {
  overflow-x: unset;
}

.cockpit-log-panel .panel-body {
  padding: 0;
}

.cockpit-log-panel .pf-v5-c-card__body .panel-heading,
.cockpit-log-panel .panel-body .panel-heading {
  border-block-start: 0;
  background-color: var(--ct-color-bg);
  font-weight: var(--pf-v5-global--FontWeight--normal);
  padding-block: var(--pf-v5-global--spacer--sm);
  inline-size: auto;
  color: var(--ct-color-list-text);
  display: flex;
}

.cockpit-log-panel .pf-v5-c-card__body .panel-heading {
  /* Align sub-heading within a PF4 card to the heading of the card */
  padding-inline-start: var(--pf-v5-global--spacer--lg);
}

.cockpit-log-panel .panel-body .panel-heading:not(:first-child)::after {
  content: "\a0";
  display: block;
  flex: auto;
  background: linear-gradient(var(--ct-color-bg) 50%, var(--ct-color-border) calc(50% + 1px), var(--ct-color-bg) calc(50% + 2px));
  margin-block: 0;
  margin-inline: 0.5rem 0;
}

.cockpit-logline {
  --log-icon: 24px;
  --log-time: 3rem;
  --log-message: 1fr;
  --log-service-min: 0;
  --log-service: minmax(var(--log-service-min), max-content);
  background-color: var(--ct-color-list-bg);
  font-size: var(--font-small);
  padding-block: 0.5rem;
  padding-inline: 1rem;
  display: grid;
  grid-template-columns: var(--log-icon) var(--log-time) var(--log-message) var(--log-service);
  grid-gap: var(--pf-v5-global--spacer--sm);
  align-items: baseline;
}

.cockpit-log-panel .cockpit-logline:hover {
  background-color: var(--ct-color-list-hover-bg);
  cursor: pointer;
}

.cockpit-log-panel .cockpit-logline:hover .cockpit-log-message:not(.cockpit-logmsg-reboot) {
  color: var(--ct-color-list-hover-text);
  text-decoration: underline;
}

.cockpit-log-panel .cockpit-logline + .panel-heading {
  border-block-start-width: 1px;
}

/* Don't show headers without content */
.cockpit-log-panel .panel-heading:last-child {
  display: none !important;
}

.cockpit-logmsg-reboot {
  font-style: italic;
}

.cockpit-log-warning {
  display: flex;
  align-self: center;
  justify-content: center;
}

.empty-message {
  inline-size: 100%;
  color: var(--pf-v5-global--Color--200);
  display: block;
  padding-block: 0.5rem;
  padding-inline: 1rem;
  text-align: center;
}

.cockpit-log-time,
.cockpit-log-service,
.cockpit-log-service-reduced {
  color: var(--pf-v5-global--Color--200);
}

.cockpit-log-time {
  color: var(--pf-v5-global--Color--200);
  font-family: monospace;
  font-size: var(--pf-v5-global--FontSize--xs);
  justify-self: end;
  white-space: nowrap;
}

.cockpit-log-message,
.cockpit-log-service,
.cockpit-log-service-reduced {
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
  flex: auto;
}

.cockpit-log-message,
.cockpit-log-service,
.cockpit-log-service-reduced {
  font-size: var(--pf-v5-global--FontSize--sm);
}

.cockpit-log-service-container > .pf-v5-c-badge {
  margin-inline-start: var(--pf-v5-global--spacer--xs);
}

.cockpit-log-service-container {
  display: flex;
  align-items: baseline;
}

@media screen and (max-width: 428px) {
  .cockpit-logline {
    /* Remove space for service */
    --log-service: 0;
  }

  .cockpit-log-service,
  .cockpit-log-service-reduced,
  .cockpit-log-service-container {
    /* Move service under message */
    grid-area: 2 / 3;
  }
}
0707010000003C000081A4000000000000000000000001662A077800003D98000000000000000000000000000000000000002B00000000cockpit-docker-devel-16/pkg/lib/journal.js/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2015 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 <http://www.gnu.org/licenses/>.
 */

import cockpit from "cockpit";
import * as timeformat from "timeformat";

const _ = cockpit.gettext;

export const journal = { };

/**
 * journalctl([match, ...], [options])
 * @match: any number of journal match strings
 * @options: an object containing further options
 *
 * Load and (by default) stream journal entries as
 * json objects. This function returns a jQuery deferred
 * object which delivers the various journal entries.
 *
 * The various @match strings are journalctl matches.
 * Zero, one or more can be specified. They must be in
 * string format, or arrays of strings.
 *
 * The optional @options object can contain the following:
 *  * "count": number of entries to load and/or pre-stream.
 *    Default is 10
 *  * "follow": if set to false just load entries and don't
 *    stream further journal data. Default is true.
 *  * "directory": optional directory to load journal files
 *  * "boot": when set only list entries from this specific
 *    boot id, or if null then the current boot.
 *  * "since": if specified list entries since the date/time
 *  * "until": if specified list entries until the date/time
 *  * "cursor": a cursor to start listing entries from
 *  * "after": a cursor to start listing entries after
 *  * "priority": if specified list entries below the specific priority, inclusive
 *
 * Returns a jQuery deferred promise. You can call these
 * functions on the deferred to handle the responses. Note that
 * there are additional non-jQuery methods.
 *
 *  .done(function(entries) { }): Called when done, @entries is
 *         an array of all journal entries loaded. If .stream()
 *         has been invoked then @entries will be empty.
 *  .fail(function(ex) { }): called if the operation fails
 *  .stream(function(entries) { }): called when we receive entries
 *         entries. Called once per batch of journal @entries,
 *         whether following or not.
 *  .stop(): stop following or retrieving entries.
 */

journal.build_cmd = function build_cmd(/* ... */) {
    const matches = [];
    const options = { follow: true };
    for (let i = 0; i < arguments.length; i++) {
        const arg = arguments[i];
        if (typeof arg == "string") {
            matches.push(arg);
        } else if (typeof arg == "object") {
            if (arg instanceof Array) {
                matches.push.apply(matches, arg);
            } else {
                Object.assign(options, arg);
                break;
            }
        } else {
            console.warn("journal.journalctl called with invalid argument:", arg);
        }
    }

    if (options.count === undefined) {
        if (options.follow)
            options.count = 10;
        else
            options.count = null;
    }

    const cmd = ["journalctl", "-q"];
    if (!options.count)
        cmd.push("--no-tail");
    else
        cmd.push("--lines=" + options.count);

    cmd.push("--output=" + (options.output || "json"));

    if (options.directory)
        cmd.push("--directory=" + options.directory);
    if (options.boot)
        cmd.push("--boot=" + options.boot);
    else if (options.boot !== undefined)
        cmd.push("--boot");
    if (options.since)
        cmd.push("--since=" + options.since);
    if (options.until)
        cmd.push("--until=" + options.until);
    if (options.cursor)
        cmd.push("--cursor=" + options.cursor);
    if (options.after)
        cmd.push("--after=" + options.after);
    if (options.priority)
        cmd.push("--priority=" + options.priority);
    if (options.grep)
        cmd.push("--grep=" + options.grep);

    /* journalctl doesn't allow reverse and follow together */
    if (options.reverse)
        cmd.push("--reverse");
    else if (options.follow)
        cmd.push("--follow");

    cmd.push("--");
    cmd.push.apply(cmd, matches);
    return cmd;
};

journal.journalctl = function journalctl(/* ... */) {
    const cmd = journal.build_cmd.apply(null, arguments);

    const dfd = cockpit.defer();
    const promise = dfd.promise();
    let buffer = "";
    let entries = [];
    let streamers = [];
    let interval = null;

    function fire_streamers() {
        let ents, i;
        if (streamers.length && entries.length > 0) {
            ents = entries;
            entries = [];
            for (i = 0; i < streamers.length; i++)
                streamers[i].apply(promise, [ents]);
        } else {
            window.clearInterval(interval);
            interval = null;
        }
    }

    const proc = cockpit.spawn(cmd, { batch: 8192, latency: 300, superuser: "try" })
            .stream(function(data) {
                if (buffer)
                    data = buffer + data;
                buffer = "";

                const lines = data.split("\n");
                const last = lines.length - 1;
                lines.forEach(function(line, i) {
                    if (i == last) {
                        buffer = line;
                    } else if (line && line.indexOf("-- ") !== 0) {
                        try {
                            entries.push(JSON.parse(line));
                        } catch (e) {
                            console.warn(e, line);
                        }
                    }
                });

                if (streamers.length && interval === null)
                    interval = window.setInterval(fire_streamers, 300);
            })
            .done(function() {
                fire_streamers();
                dfd.resolve(entries);
            })
            .fail(function(ex) {
            /* The journalctl command fails when no entries are matched
             * so we just ignore this status code */
                if (ex.problem == "cancelled" ||
                ex.exit_status === 1) {
                    fire_streamers();
                    dfd.resolve(entries);
                } else {
                    dfd.reject(ex);
                }
            })
            .always(function() {
                window.clearInterval(interval);
            });

    promise.stream = function stream(callback) {
        streamers.push(callback);
        return this;
    };
    promise.stop = function stop() {
        streamers = [];
        promise.stopped = true;
        proc.close("cancelled");
    };
    return promise;
};

journal.printable = function printable(value, key) {
    if (value === undefined || value === null)
        return _("[no data]");
    else if (typeof (value) == "string")
        return value;
    else if (value.length !== undefined && value.length <= 1000 && key == "MESSAGE")
        return new TextDecoder().decode(new Uint8Array(value));
    else {
        return _("[binary data]");
    }
};

/* Render the journal entries by passing suitable DOM elements back to
   the caller via the 'output_funcs'.

   Rendering is context aware.  It will insert 'reboot' markers, for
   example, and collapse repeated lines.  You can extend the output at
   the bottom and also at the top.

   A new renderer is created by calling 'journal.renderer' like
   so:

      const renderer = journal.renderer(funcs);

   You can feed new entries into the renderer by calling various
   methods on the returned object:

      - renderer.append(journal_entry)
      - renderer.append_flush()
      - renderer.prepend(journal_entry)
      - renderer.prepend_flush()

   A 'journal_entry' is one element of the result array returned by a
   call to 'Query' with the 'cockpit.journal_fields' as the fields to
   return.

   Calling 'append' will append the given entry to the end of the
   output, naturally, and 'prepend' will prepend it to the start.

   The output might lag behind what has been input via 'append' and
   'prepend', and you need to call 'append_flush' and 'prepend_flush'
   respectively to ensure that the output is up-to-date.  Flushing a
   renderer does not introduce discontinuities into the output.  You
   can continue to feed entries into the renderer after flushing and
   repeated lines will be correctly collapsed across the flush, for
   example.

   The renderer will call methods of the 'output_funcs' object to
   produce the desired output:

      - output_funcs.append(rendered)
      - output_funcs.remove_last()
      - output_funcs.prepend(rendered)
      - output_funcs.remove_first()

   The 'rendered' argument is the return value of one of the rendering
   functions described below.  The 'append' and 'prepend' methods
   should add this element to the output, naturally, and 'remove_last'
   and 'remove_first' should remove the indicated element.

   If you never call 'prepend' on the renderer, 'output_func.prepend'
   isn't called either.  If you never call 'renderer.prepend' after
   'renderer.prepend_flush', then 'output_func.remove_first' will
   never be called.  The same guarantees exist for the 'append' family
   of functions.

   The actual rendering is also done by calling methods on
   'output_funcs':

      - output_funcs.render_line(ident, prio, message, count, time, cursor)
      - output_funcs.render_day_header(day)
      - output_funcs.render_reboot_separator()
*/

journal.renderer = function renderer(output_funcs) {
    if (!output_funcs.render_line)
        console.error("Invalid renderer provided");

    function copy_object(o) {
        const c = { }; for (const p in o) c[p] = o[p]; return c;
    }

    // A 'entry' object describes a journal entry in formatted form.
    // It has fields 'bootid', 'ident', 'prio', 'message', 'time',
    // 'day', all of which are strings.

    function format_entry(journal_entry) {
        const d = journal_entry.__REALTIME_TIMESTAMP / 1000; // timestamps are in µs
        return {
            cursor: journal_entry.__CURSOR,
            full: journal_entry,
            day: timeformat.date(d),
            time: timeformat.time(d),
            bootid: journal_entry._BOOT_ID,
            ident: journal_entry.SYSLOG_IDENTIFIER || journal_entry._COMM,
            prio: journal_entry.PRIORITY,
            message: journal.printable(journal_entry.MESSAGE, "MESSAGE")
        };
    }

    function entry_is_equal(a, b) {
        return (a && b &&
                a.day == b.day &&
                a.bootid == b.bootid &&
                a.ident == b.ident &&
                a.prio == b.prio &&
                a.message == b.message);
    }

    // A state object describes a line that should be eventually
    // output.  It has an 'entry' field as per description above, and
    // also 'count', 'last_time', and 'first_time', which record
    // repeated entries.  Additionally:
    //
    // line_present: When true, the line has been output already with
    //     some preliminary data.  It needs to be removed before
    //     outputting more recent data.
    //
    // header_present: The day header has been output preliminarily
    //     before the actual log lines.  It needs to be removed before
    //     prepending more lines.  If both line_present and
    //     header_present are true, then the header comes first in the
    //     output, followed by the line.

    function render_state_line(state) {
        return output_funcs.render_line(state.entry.ident,
                                        state.entry.prio,
                                        state.entry.message,
                                        state.count,
                                        state.last_time,
                                        state.entry.full);
    }

    // We keep the state of the first and last journal lines,
    // respectively, in order to collapse repeated lines, and to
    // insert reboot markers and day headers.
    //
    // Normally, there are two state objects, but if only a single
    // line has been output so far, top_state and bottom_state point
    // to the same object.

    let top_state, bottom_state;

    top_state = bottom_state = { };

    function start_new_line() {
        // If we now have two lines, split the state
        if (top_state === bottom_state && top_state.entry) {
            top_state = copy_object(bottom_state);
        }
    }

    function top_output() {
        if (top_state.header_present) {
            output_funcs.remove_first();
            top_state.header_present = false;
        }
        if (top_state.line_present) {
            output_funcs.remove_first();
            top_state.line_present = false;
        }
        if (top_state.entry) {
            output_funcs.prepend(render_state_line(top_state));
            top_state.line_present = true;
        }
    }

    function prepend(journal_entry) {
        const entry = format_entry(journal_entry);

        if (entry_is_equal(top_state.entry, entry)) {
            top_state.count += 1;
            top_state.first_time = entry.time;
        } else {
            top_output();

            if (top_state.entry) {
                if (entry.bootid != top_state.entry.bootid)
                    output_funcs.prepend(output_funcs.render_reboot_separator());
                if (entry.day != top_state.entry.day)
                    output_funcs.prepend(output_funcs.render_day_header(top_state.entry.day));
            }

            start_new_line();
            top_state.entry = entry;
            top_state.count = 1;
            top_state.first_time = top_state.last_time = entry.time;
            top_state.line_present = false;
        }
    }

    function prepend_flush() {
        top_output();
        if (top_state.entry) {
            output_funcs.prepend(output_funcs.render_day_header(top_state.entry.day));
            top_state.header_present = true;
        }
    }

    function bottom_output() {
        if (bottom_state.line_present) {
            output_funcs.remove_last();
            bottom_state.line_present = false;
        }
        if (bottom_state.entry) {
            output_funcs.append(render_state_line(bottom_state));
            bottom_state.line_present = true;
        }
    }

    function append(journal_entry) {
        const entry = format_entry(journal_entry);

        if (entry_is_equal(bottom_state.entry, entry)) {
            bottom_state.count += 1;
            bottom_state.last_time = entry.time;
        } else {
            bottom_output();

            if (!bottom_state.entry || entry.day != bottom_state.entry.day) {
                output_funcs.append(output_funcs.render_day_header(entry.day));
                bottom_state.header_present = true;
            }
            if (bottom_state.entry && entry.bootid != bottom_state.entry.bootid)
                output_funcs.append(output_funcs.render_reboot_separator());

            start_new_line();
            bottom_state.entry = entry;
            bottom_state.count = 1;
            bottom_state.first_time = bottom_state.last_time = entry.time;
            bottom_state.line_present = false;
        }
    }

    function append_flush() {
        bottom_output();
    }

    return {
        prepend,
        prepend_flush,
        append,
        append_flush
    };
};
0707010000003D000081A4000000000000000000000001662A077800001D18000000000000000000000000000000000000003800000000cockpit-docker-devel-16/pkg/lib/long-running-process.js/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
 */

/* Manage a long-running precious process that runs independently from a Cockpit
 * session in a transient systemd service unit. See
 * examples/long-running-process/README.md for details.
 *
 * The unit will run as root, on the system systemd manager, so that every privileged
 * Cockpit session shares the same unit. The same approach works in principle on
 * the user's systemd instance, but the current code  does not support that as it
 * is not a common use case for Cockpit.
 */

/* global cockpit */

// systemd D-Bus API names
const O_SD_OBJ = "/org/freedesktop/systemd1";
const I_SD_MGR = "org.freedesktop.systemd1.Manager";
const I_SD_UNIT = "org.freedesktop.systemd1.Unit";
const I_DBUS_PROP = "org.freedesktop.DBus.Properties";

/* Possible LongRunningProcess.state values */
export const ProcessState = {
    INIT: 'init',
    STOPPED: 'stopped',
    RUNNING: 'running',
    FAILED: 'failed',
};

export class LongRunningProcess {
    /* serviceName: systemd unit name to start or reattach to
     * updateCallback: function that gets called whenever the state changed; first and only
     *                 argument is `this` LongRunningProcess instance.
     */
    constructor(serviceName, updateCallback) {
        this.systemdClient = cockpit.dbus("org.freedesktop.systemd1", { superuser: "require" });
        this.serviceName = serviceName;
        this.updateCallback = updateCallback;
        this._setState(ProcessState.INIT);
        this.startTimestamp = null; // µs since epoch
        this.terminated = false;

        // Watch for start event of the service
        this.systemdClient.subscribe({ interface: I_SD_MGR, member: "JobNew" }, (path, iface, signal, args) => {
            if (args[2] == this.serviceName)
                this._checkState();
        });

        // Check if it is already running
        this._checkState();
    }

    /* Start long-running process. Only call this in states STOPPED or FAILED.
     * This runs as root, thus will be shared with all privileged Cockpit sessions.
     * Return cockpit.spawn promise. You need to handle exceptions, but not success.
     */
    run(argv, options) {
        if (this.state !== ProcessState.STOPPED && this.state !== ProcessState.FAILED)
            throw new Error(`cannot start LongRunningProcess in state ${this.state}`);

        // no need to directly react to this -- JobNew and _checkState() will pick up when the unit runs
        return cockpit.spawn(["systemd-run", "--unit", this.serviceName, "--service-type=oneshot", "--no-block", "--"].concat(argv),
                             { superuser: "require", err: "message", ...options });
    }

    /*  Stop long-running process while it is RUNNING, or reset a FAILED one */
    terminate() {
        if (this.state !== ProcessState.RUNNING && this.state !== ProcessState.FAILED)
            throw new Error(`cannot terminate LongRunningProcess in state ${this.state}`);

        /* This sends a SIGTERM to the unit, causing it to go into "failed" state. This would not
         * happen with `systemd-run -p SuccessExitStatus=0`, but that does not yet work on older
         * OSes with systemd ≤ 241 So let checkState() know that a failure is due to termination. */
        this.terminated = true;
        return this.systemdClient.call(O_SD_OBJ, I_SD_MGR, "StopUnit", [this.serviceName, "replace"], { type: "ss" });
    }

    /*
     * below are internal private methods
     */

    _setState(state) {
        /* PropertiesChanged often gets fired multiple times with the same values, avoid UI flicker */
        if (state === this.state)
            return;
        this.state = state;
        this.terminated = false;
        if (this.updateCallback)
            this.updateCallback(this);
    }

    _setStateFromProperties(activeState, stateChangeTimestamp) {
        switch (activeState) {
        case 'activating':
            this.startTimestamp = stateChangeTimestamp;
            this._setState(ProcessState.RUNNING);
            break;
        case 'failed':
            this.startTimestamp = null; // TODO: can we derive this from InvocationID?
            if (this.terminated) {
                /* terminating causes failure; reset that and do not announce it as failed */
                this.systemdClient.call(O_SD_OBJ, I_SD_MGR, "ResetFailedUnit", [this.serviceName], { type: "s" });
            } else {
                this._setState(ProcessState.FAILED);
            }
            break;
        case 'inactive':
            this._setState(ProcessState.STOPPED);
            break;
        case 'deactivating':
            /* ignore these transitions */
            break;
        default:
            throw new Error(`unexpected state of unit ${this.serviceName}: ${activeState}`);
        }
    }

    // check if the transient unit for our command is running
    _checkState() {
        this.systemdClient.call(O_SD_OBJ, I_SD_MGR, "GetUnit", [this.serviceName], { type: "s" })
                .then(([unitObj]) => {
                    /* Some time may pass between getting JobNew and the unit actually getting activated;
                     * we may get an inactive unit here; watch for state changes. This will also update
                     * the UI if the unit stops. */
                    this.subscription = this.systemdClient.subscribe(
                        { interface: I_DBUS_PROP, member: "PropertiesChanged" },
                        (path, iface, signal, args) => {
                            if (path === unitObj && args[1].ActiveState && args[1].StateChangeTimestamp)
                                this._setStateFromProperties(args[1].ActiveState.v, args[1].StateChangeTimestamp.v);
                        });

                    this.systemdClient.call(unitObj, I_DBUS_PROP, "GetAll", [I_SD_UNIT], { type: "s" })
                            .then(([props]) => this._setStateFromProperties(props.ActiveState.v, props.StateChangeTimestamp.v))
                            .catch(ex => {
                                throw new Error(`unexpected failure of GetAll(${unitObj}): ${ex.toString()}`);
                            });
                })
                .catch(ex => {
                    if (ex.name === "org.freedesktop.systemd1.NoSuchUnit") {
                        if (this.subscription) {
                            this.subscription.remove();
                            this.subscription = null;
                        }
                        this._setState(ProcessState.STOPPED);
                    } else {
                        throw new Error(`unexpected failure of GetUnit(${this.serviceName}): ${ex.toString()}`);
                    }
                });
    }
}
0707010000003E000081A4000000000000000000000001662A0778000020B8000000000000000000000000000000000000003000000000cockpit-docker-devel-16/pkg/lib/machine-info.js/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2018 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 <http://www.gnu.org/licenses/>.
 */

import cockpit from "cockpit";
const _ = cockpit.gettext;

export const cpu_ram_info = () =>
    cockpit.spawn(["cat", "/proc/meminfo", "/proc/cpuinfo"])
            .then(text => {
                const info = { };
                const memtotal_match = text.match(/MemTotal:[^0-9]*([0-9]+) [kK]B/);
                const total_kb = memtotal_match && parseInt(memtotal_match[1], 10);
                if (total_kb)
                    info.memory = total_kb * 1024;

                const available_match = text.match(/MemAvailable:[^0-9]*([0-9]+) [kK]B/);
                const available_kb = available_match && parseInt(available_match[1], 10);
                if (available_kb)
                    info.available_memory = available_kb * 1024;

                const swap_match = text.match(/SwapTotal:[^0-9]*([0-9]+) [kK]B/);
                const swap_total_kb = swap_match && parseInt(swap_match[1], 10);
                if (swap_total_kb)
                    info.swap = swap_total_kb * 1024;

                let model_match = text.match(/^model name\s*:\s*(.*)$/m);
                if (!model_match)
                    model_match = text.match(/^cpu\s*:\s*(.*)$/m); // PowerPC
                if (!model_match)
                    model_match = text.match(/^vendor_id\s*:\s*(.*)$/m); // s390x
                if (model_match)
                    info.cpu_model = model_match[1];

                info.cpus = 0;
                const re = /^(processor|cpu number)\s*:/gm;
                while (re.test(text))
                    info.cpus += 1;
                return info;
            });

// https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
const chassis_types = [
    undefined,
    _("Other"),
    _("Unknown"),
    _("Desktop"),
    _("Low profile desktop"),
    _("Pizza box"),
    _("Mini tower"),
    _("Tower"),
    _("Portable"),
    _("Laptop"),
    _("Notebook"),
    _("Handheld"),
    _("Docking station"),
    _("All-in-one"),
    _("Sub-Notebook"),
    _("Space-saving computer"),
    _("Lunch box"), /* 0x10 */
    _("Main server chassis"),
    _("Expansion chassis"),
    _("Sub-Chassis"),
    _("Bus expansion chassis"),
    _("Peripheral chassis"),
    _("RAID chassis"),
    _("Rack mount chassis"),
    _("Sealed-case PC"),
    _("Multi-system chassis"),
    _("Compact PCI"), /* 0x1A */
    _("Advanced TCA"),
    _("Blade"),
    _("Blade enclosure"),
    _("Tablet"),
    _("Convertible"),
    _("Detachable"), /* 0x20 */
    _("IoT gateway"),
    _("Embedded PC"),
    _("Mini PC"),
    _("Stick PC"),
];

function parseDMIFields(text) {
    const info = {};
    text.split("\n").forEach(line => {
        const sep = line.indexOf(':');
        if (sep <= 0)
            return;
        const file = line.slice(0, sep);
        const key = file.slice(file.lastIndexOf('/') + 1);
        let value = line.slice(sep + 1);

        // clean up after lazy OEMs
        if (value.match(/to be filled by o\.?e\.?m\.?/i))
            value = "";

        info[key] = value;

        if (key === "chassis_type")
            info[key + "_str"] = chassis_types[parseInt(value)] || chassis_types[2]; // fall back to "Unknown"
    });
    return info;
}

export function dmi_info() {
    // the grep often/usually exits with 2, that's okay as long as we find *some* information
    return cockpit.script("grep -r . /sys/class/dmi/id || true", null,
                          { err: "message", superuser: "try" })
            .then((output) => parseDMIFields(output));
}

// decode a binary Uint8Array with a trailing null byte
function decode_proc_str(s) {
    return cockpit.utf8_decoder().decode(s.slice(0, -1));
}

export function devicetree_info() {
    let model, serial;

    return Promise.all([
        // these succeed with content === null if files are absent
        cockpit.file("/proc/device-tree/model", { binary: true }).read()
                .then(content => { model = content ? decode_proc_str(content) : null }),
        cockpit.file("/proc/device-tree/serial-number", { binary: true }).read()
                .then(content => { serial = content ? decode_proc_str(content) : null }),
    ])
            .then(() => ({ model, serial }));
}

/* we expect udev db paragraphs like this:
 *
   P: /devices/virtual/mem/null
   N: null
   E: DEVMODE=0666
   E: DEVNAME=/dev/null
   E: SUBSYSTEM=mem
*/

const udevPathRE = /^P: (.*)$/;
const udevPropertyRE = /^E: (\w+)=(.*)$/;

function parseUdevDB(text) {
    const info = {};
    text.split("\n\n").forEach(paragraph => {
        let syspath = null;
        const props = {};

        paragraph = paragraph.trim();
        if (!paragraph)
            return;

        paragraph.split("\n").forEach(line => {
            let match = line.match(udevPathRE);
            if (match) {
                syspath = match[1];
            } else {
                match = line.match(udevPropertyRE);
                if (match)
                    props[match[1]] = match[2];
            }
        });

        if (syspath)
            info[syspath] = props;
        else
            console.log("udev database paragraph is missing P:", paragraph);
    });
    return info;
}

export function udev_info() {
    return cockpit.spawn(["udevadm", "info", "--export-db"], { err: "message" })
            .then(output => parseUdevDB(output));
}

const memoryRE = /^([ \w]+): (.*)/;

// Process the dmidecode output and create a mapping of locator to DIMM properties
function parseMemoryInfo(text) {
    const info = {};
    text.split("\n\n").forEach(paragraph => {
        let locator = null;
        let bankLocator = null;
        const props = {};
        paragraph = paragraph.trim();
        if (!paragraph)
            return;

        paragraph.split("\n").forEach(line => {
            line = line.trim();
            const match = line.match(memoryRE);
            if (match)
                props[match[1]] = match[2];
        });

        locator = props.Locator;
        bankLocator = props['Bank Locator'];
        if (locator)
            info[bankLocator + locator] = props;
    });
    return processMemory(info);
}

// Select the useful properties to display
function processMemory(info) {
    const memoryArray = [];

    for (const dimm in info) {
        const memoryProperty = info[dimm];

        let memorySize = memoryProperty.Size || _("Unknown");
        if (memorySize.includes("MB")) {
            const memorySizeValue = parseInt(memorySize, 10);
            memorySize = cockpit.format(_("$0 GiB"), memorySizeValue / 1024);
        }

        let memoryTechnology = memoryProperty["Memory technology"];
        if (!memoryTechnology || memoryTechnology == "<OUT OF SPEC>")
            memoryTechnology = _("Unknown");

        let memoryRank = memoryProperty.Rank || _("Unknown");
        if (memoryRank == 1)
            memoryRank = _("Single rank");
        if (memoryRank == 2)
            memoryRank = _("Dual rank");

        memoryArray.push({
            locator: (memoryProperty['Bank Locator'] + ': ' + memoryProperty.Locator) || _("Unknown"),
            technology: memoryTechnology,
            type: memoryProperty.Type || _("Unknown"),
            size: memorySize,
            state: memoryProperty["Total Width"] == "Unknown" ? _("Absent") : _("Present"),
            rank: memoryRank,
            speed: memoryProperty.Speed || _("Unknown")
        });
    }

    return memoryArray;
}

export function memory_info() {
    return cockpit.spawn(["dmidecode", "-t", "memory"], { environ: ["LC_ALL=C"], err: "message", superuser: "try" })
            .then(output => parseMemoryInfo(output));
}
0707010000003F000081ED000000000000000000000001662A07780000123B000000000000000000000000000000000000002F00000000cockpit-docker-devel-16/pkg/lib/manifest2po.js#!/usr/bin/env node

/*
 * Extracts translatable strings from manifest.json files.
 *
 */

import fs from 'fs';
import path from 'path';
import { ArgumentParser } from 'argparse';

function fatal(message, code) {
    console.log((filename || "manifest2po") + ": " + message);
    process.exit(code || 1);
}

const parser = new ArgumentParser();
parser.add_argument('-d', '--directory', { help: "Base directory for input files" });
parser.add_argument('-o', '--output', { help: 'Output file', required: true });
parser.add_argument('files', { nargs: '+', help: "One or more input files", metavar: "FILE" });
const args = parser.parse_args();

const input = args.files;
const entries = { };

/* Filename being parsed */
let filename = null;

/* Now process each file in turn */
step();

function step() {
    filename = input.shift();
    if (filename === undefined) {
        finish();
        return;
    }

    if (path.basename(filename) != "manifest.json")
        return step();

    /* Qualify the filename if necessary */
    let full = filename;
    if (args.directory)
        full = path.join(args.directory, filename);

    fs.readFile(full, { encoding: "utf-8" }, function(err, data) {
        if (err)
            fatal(err.message);

        // There are variables which when not substituted can cause JSON.parse to fail
        // Dummy replace them. None variable is going to be translated anyway
        const safe_data = data.replace(/@.+?@/gi, 1);
        process_manifest(JSON.parse(safe_data));

        return step();
    });
}

function process_manifest(manifest) {
    if (manifest.menu)
        process_menu(manifest.menu);
    if (manifest.tools)
        process_menu(manifest.tools);
    if (manifest.bridges)
        process_bridges(manifest.bridges);
    if (manifest.docs)
        process_docs(manifest.docs);
}

function process_keywords(keywords) {
    keywords.forEach(v => {
        v.matches.forEach(keyword =>
            push({
                msgid: keyword,
                locations: [filename + ":0"]
            })
        );
    });
}

function process_docs(docs) {
    docs.forEach(doc => {
        push({
            msgid: doc.label,
            locations: [filename + ":0"]
        });
    });
}

function process_menu(menu) {
    for (const m in menu) {
        if (menu[m].label) {
            push({
                msgid: menu[m].label,
                locations: [filename + ":0"]
            });
        }
        if (menu[m].keywords)
            process_keywords(menu[m].keywords);
        if (menu[m].docs)
            process_docs(menu[m].docs);
    }
}

function process_bridges(bridges) {
    for (const b in bridges) {
        if (bridges[b].label) {
            push({
                msgid: bridges[b].label,
                locations: [filename + ":0"]
            });
        }
    }
}

/* Push an entry onto the list */
function push(entry) {
    const key = entry.msgid + "\0" + entry.msgid_plural + "\0" + entry.msgctxt;
    const prev = entries[key];
    if (prev) {
        prev.locations = prev.locations.concat(entry.locations);
    } else {
        entries[key] = entry;
    }
}

/* Escape a string for inclusion in po file */
function escape(string) {
    const bs = string.split('\\')
            .join('\\\\')
            .split('"')
            .join('\\"');
    return bs.split("\n").map(function(line) {
        return '"' + line + '"';
    }).join("\n");
}

/* Finish by writing out the strings */
function finish() {
    const result = [
        'msgid ""',
        'msgstr ""',
        '"Project-Id-Version: PACKAGE_VERSION\\n"',
        '"MIME-Version: 1.0\\n"',
        '"Content-Type: text/plain; charset=UTF-8\\n"',
        '"Content-Transfer-Encoding: 8bit\\n"',
        '"X-Generator: Cockpit manifest2po\\n"',
        '',
    ];

    for (const msgid in entries) {
        const entry = entries[msgid];
        result.push('#: ' + entry.locations.join(" "));
        if (entry.msgctxt)
            result.push('msgctxt ' + escape(entry.msgctxt));
        result.push('msgid ' + escape(entry.msgid));
        if (entry.msgid_plural) {
            result.push('msgid_plural ' + escape(entry.msgid_plural));
            result.push('msgstr[0] ""');
            result.push('msgstr[1] ""');
        } else {
            result.push('msgstr ""');
        }
        result.push('');
    }

    const data = result.join('\n');
    if (!args.output) {
        process.stdout.write(data);
        process.exit(0);
    } else {
        fs.writeFile(args.output, data, function(err) {
            if (err)
                fatal(err.message);
            process.exit(0);
        });
    }
}
07070100000040000081A4000000000000000000000001662A0778000005A4000000000000000000000000000000000000003800000000cockpit-docker-devel-16/pkg/lib/menu-select-widget.scss// FIXME: Remove this custom implementation once a component exists upstream.
// PF overrides to fake a multiselect widget (as one does not currently exist in PF4).
// A menu gives us the interaction we want, but the styling is a bit off.
// Therefore, we're changing the visuals here locally.
// PF4 upstream request for multi-select @ https://github.com/patternfly/patternfly/issues/4027
.ct-menu-select-widget.pf-v5-c-menu {
  // Divider is silly between the widgets in this context
  .pf-v5-c-divider {
    display: none;

    + .pf-v5-c-menu__content {
      // There should be minimal space between the widgets (replacing the divider)
      margin-block-start: var(--pf-v5-global--spacer--sm);
    }
  }

  .pf-v5-c-menu__content {
    // An overflow multi-select widget needs an outline
    border: 1px solid var(--pf-v5-global--BorderColor--100);
  }

  // Search should not be inset when there's no border containing it
  .pf-v5-c-menu__search {
    padding: 0;
  }

  // Keep the background on a selected item even when it doesn't have
  // focus, allowing keyboard control to have the only background color
  // when active but also keep the background color when the list loses
  // focus (such as when the keyboard or mouse navigates outside,
  // including initial rendering of the list.
  .pf-v5-c-menu__list:not(:focus-within) .pf-m-selected {
    background-color: var(--pf-v5-c-menu__list-item--hover--BackgroundColor);
  }
}
07070100000041000081A4000000000000000000000001662A077800001420000000000000000000000000000000000000003100000000cockpit-docker-devel-16/pkg/lib/notifications.js/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
 */

/* NOTIFICATIONS

A page can broadcast notifications to the rest of Cockpit.  For
example, the "Software updates" page can send out a notification when
it detects that software updates are available.  The shell will then
highlight the menu entry for "Software updates" and the "System"
overview page will also mention it in its "Operating system" section.

The details are all still experimental and subject to change.

As a first step, there are only simple "page status" notifications.
When we address "event" style notifications, page status notifications
might become a special case of them.  Or not.

A page status is either null, or a JSON value with the following
fields:

 - type (string, optional)

 If specified, one of "info", "warning", "error".  The shell will put
 an appropriate icon next to the navigation entry for this page, for
 example.

 Omitting 'type' means that the page has no special status and is the
 same as using "null" as the whole status value.  This can be used to
 broadcast values in the 'details' field to other pages without
 forcing an icon into the navigation menu.

 - title (string, optional)

 A short, human readable, localized description of the status,
 suitable for a tooltip.

 - details (JSON value, optional)

 An arbitrary value.  The "System" overview page might monitor a
 couple of pages for their status and it will use 'details' to display
 a richer version of the status than possible with just type and
 title. The recognized properties are:

   * icon: custom icon name (defaults to standard icon corresponding to type)
   * pficon: PatternFly icon name; e.g. "enhancement", "bug", "security", "spinner", "check";
     see get_pficon() in pkg/systemd/page-status.jsx
   * link: custom link target (defaults to page name); if false, the
     notification will not be a link

Usage:

 import { page_status } from "notifications";

 - page_status.set_own(STATUS)

 Sets the status of the page making the call, completely overwriting
 the current status.  For example,

    page_status.set_own({
      type: "info",
      title: _("Software updates available"),
      details: {
        num_updates: 10,
        num_security_updates: 5
      }
    });

    page_status.set_own({
      type: null
      title: _("System is up to date"),
      details: {
        last_check: 81236457
      }
    });

 Calling this function with the same STATUS value multiple times is
 cheap: only the first call will actually broadcast the new status.

 - page_status.get(PAGE, [HOST])

 Retrieves the current status of page PAGE of HOST.  When HOST is
 omitted, it defaults to the default host of the calling page.

 PAGE is the same string that Cockpit uses in its URLs to identify a
 page, such as "system/terminal" or "storage".

 Until the page_status object is fully initialized (see 'valid'
 below), this function will return 'undefined'.

 - page_status.addEventListener("changed", event => { ... })

 The "changed" event is emitted whenever any page status changes.

 - page_status.valid

 The page_status objects needs to initialize itself asynchronously and
 'valid' is false until this is done.  When 'valid' changes to true, a
 "changed" event is emitted.

*/

import cockpit from "cockpit";
import deep_equal from "deep-equal";

class PageStatus {
    constructor() {
        cockpit.event_target(this);
        window.addEventListener("storage", event => {
            if (event.key == "cockpit:page_status") {
                this.dispatchEvent("changed");
            }
        });

        this.cur_own = null;

        this.valid = false;
        cockpit.transport.wait(() => {
            this.valid = true;
            this.dispatchEvent("changed");
        });
    }

    get(page, host) {
        let page_status;

        if (!this.valid)
            return undefined;

        if (host === undefined)
            host = cockpit.transport.host;

        try {
            page_status = JSON.parse(sessionStorage.getItem("cockpit:page_status"));
        } catch {
            return null;
        }

        if (page_status?.[host])
            return page_status[host][page] || null;
        return null;
    }

    set_own(status) {
        if (!deep_equal(status, this.cur_own)) {
            this.cur_own = status;
            cockpit.transport.control("notify", { page_status: status });
        }
    }
}

export const page_status = new PageStatus();
07070100000042000081A4000000000000000000000001662A077800000547000000000000000000000000000000000000002E00000000cockpit-docker-devel-16/pkg/lib/os-release.js/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2022 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 <http://www.gnu.org/licenses/>.
 */

import cockpit from "cockpit";

function parse_simple_vars(text) {
    const res = { };
    for (const l of text.split('\n')) {
        const pos = l.indexOf('=');
        if (pos > 0) {
            const name = l.substring(0, pos);
            let val = l.substring(pos + 1);
            if (val[0] == '"' && val[val.length - 1] == '"')
                val = val.substring(1, val.length - 1);
            res[name] = val;
        }
    }
    return res;
}

/* Return /etc/os-release as object */
export const read_os_release = () => cockpit.file("/etc/os-release", { syntax: { parse: parse_simple_vars } }).read();
07070100000043000081A4000000000000000000000001662A077800005088000000000000000000000000000000000000002E00000000cockpit-docker-devel-16/pkg/lib/packagekit.js/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2017, 2018 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 <http://www.gnu.org/licenses/>.
 */

import cockpit from "cockpit";
import { superuser } from 'superuser';

const _ = cockpit.gettext;

// see https://github.com/PackageKit/PackageKit/blob/main/lib/packagekit-glib2/pk-enum.h
export const Enum = {
    EXIT_SUCCESS: 1,
    EXIT_FAILED: 2,
    EXIT_CANCELLED: 3,
    ROLE_REFRESH_CACHE: 13,
    ROLE_UPDATE_PACKAGES: 22,
    INFO_UNKNOWN: -1,
    INFO_LOW: 3,
    INFO_ENHANCEMENT: 4,
    INFO_NORMAL: 5,
    INFO_BUGFIX: 6,
    INFO_IMPORTANT: 7,
    INFO_SECURITY: 8,
    INFO_DOWNLOADING: 10,
    INFO_UPDATING: 11,
    INFO_INSTALLING: 12,
    INFO_REMOVING: 13,
    INFO_REINSTALLING: 19,
    INFO_DOWNGRADING: 20,
    STATUS_WAIT: 1,
    STATUS_DOWNLOAD: 8,
    STATUS_INSTALL: 9,
    STATUS_UPDATE: 10,
    STATUS_CLEANUP: 11,
    STATUS_SIGCHECK: 14,
    STATUS_WAITING_FOR_LOCK: 30,
    FILTER_INSTALLED: (1 << 2),
    FILTER_NOT_INSTALLED: (1 << 3),
    FILTER_NEWEST: (1 << 16),
    FILTER_ARCH: (1 << 18),
    FILTER_NOT_SOURCE: (1 << 21),
    ERROR_ALREADY_INSTALLED: 9,
    TRANSACTION_FLAG_SIMULATE: (1 << 2),
};

export const transactionInterface = "org.freedesktop.PackageKit.Transaction";

let _dbus_client = null;

/**
 * Get PackageKit D-Bus client
 *
 * This will get lazily initialized and re-initialized after PackageKit
 * disconnects (due to a crash or idle timeout).
 */
function dbus_client() {
    if (_dbus_client === null) {
        _dbus_client = cockpit.dbus("org.freedesktop.PackageKit", { superuser: "try", track: true });
        _dbus_client.addEventListener("close", () => {
            console.log("PackageKit went away from D-Bus");
            _dbus_client = null;
        });
    }

    return _dbus_client;
}

// Reconnect when privileges change
superuser.addEventListener("changed", () => { _dbus_client = null });

/**
 * Call a PackageKit method
 */
export function call(objectPath, iface, method, args, opts) {
    return dbus_client().call(objectPath, iface, method, args, opts);
}

/**
 * Figure out whether PackageKit is available and usable
 */
export function detect() {
    function dbus_detect() {
        return call("/org/freedesktop/PackageKit", "org.freedesktop.DBus.Properties",
                    "Get", ["org.freedesktop.PackageKit", "VersionMajor"])
                .then(() => true,
                      () => false);
    }

    return cockpit.spawn(["findmnt", "-T", "/usr", "-n", "-o", "VFS-OPTIONS"])
            .then(options => {
                if (options.split(",").indexOf("ro") >= 0)
                    return false;
                else
                    return dbus_detect();
            })
            .catch(dbus_detect);
}

/**
 * Watch a running PackageKit transaction
 *
 * transactionPath (string): D-Bus object path of the PackageKit transaction
 * signalHandlers, notifyHandler: As in method #transaction
 * Returns: If notifyHandler is set, Cockpit promise that resolves when the watch got set up
 */
export function watchTransaction(transactionPath, signalHandlers, notifyHandler) {
    const subscriptions = [];
    let notifyReturn;
    const client = dbus_client();

    // Listen for PackageKit crashes while the transaction runs
    function onClose(event, ex) {
        console.warn("PackageKit went away during transaction", transactionPath, ":", JSON.stringify(ex));
        if (signalHandlers.ErrorCode)
            signalHandlers.ErrorCode("close", _("PackageKit crashed"));
        if (signalHandlers.Finished)
            signalHandlers.Finished(Enum.EXIT_FAILED);
    }
    client.addEventListener("close", onClose);

    if (signalHandlers) {
        Object.keys(signalHandlers).forEach(handler => subscriptions.push(
            client.subscribe({ interface: transactionInterface, path: transactionPath, member: handler },
                             (path, iface, signal, args) => signalHandlers[handler](...args)))
        );
    }

    if (notifyHandler) {
        notifyReturn = client.watch(transactionPath);
        subscriptions.push(notifyReturn);
        client.addEventListener("notify", reply => {
            const iface = reply?.detail?.[transactionPath]?.[transactionInterface];
            if (iface)
                notifyHandler(iface, transactionPath);
        });
    }

    // unsubscribe when transaction finished
    subscriptions.push(client.subscribe(
        { interface: transactionInterface, path: transactionPath, member: "Finished" },
        () => {
            subscriptions.map(s => s.remove());
            client.removeEventListener("close", onClose);
        })
    );

    return notifyReturn;
}

/**
 * Run a PackageKit transaction
 *
 * method (string): D-Bus method name on the https://www.freedesktop.org/software/PackageKit/gtk-doc/Transaction.html interface
 *                  If undefined, only a transaction will be created without calling a method on it
 * arglist (array): "in" arguments of @method
 * signalHandlers (object): maps PackageKit.Transaction signal names to handlers
 * notifyHandler (function): handler for https://cockpit-project.org/guide/latest/cockpit-dbus.html#cockpit-dbus-onnotify
 *                           signals, called on property changes with (changed_properties, transaction_path)
 * Returns: Promise that resolves with transaction path on success, or rejects on an error
 *
 * Note that most often you don't really need the transaction path, but want to
 * listen to the "Finished" signal.
 *
 * Example:
 *     transaction("GetUpdates", [0], {
 *             Package: (info, packageId, _summary) => { ... },
 *             ErrorCode: (code, details) => { ... },
 *         },
 *         changedProps => { ... }  // notify handler
 *     )
 *        .then(transactionPath => { ... })
 *        .catch(ex => { handle exception });
 */
export function transaction(method, arglist, signalHandlers, notifyHandler) {
    return call("/org/freedesktop/PackageKit", "org.freedesktop.PackageKit", "CreateTransaction", [])
            .then(([transactionPath]) => {
                if (!signalHandlers && !notifyHandler)
                    return transactionPath;

                const watchPromise = watchTransaction(transactionPath, signalHandlers, notifyHandler) || Promise.resolve();
                return watchPromise.then(() => {
                    if (method) {
                        return call(transactionPath, transactionInterface, method, arglist)
                                .then(() => transactionPath);
                    } else {
                        return transactionPath;
                    }
                });
            });
}

export class TransactionError extends Error {
    constructor(code, detail) {
        super(detail);
        this.detail = detail;
        this.code = code;
    }
}

/**
 * Run a long cancellable PackageKit transaction
 *
 * method (string): D-Bus method name on the https://www.freedesktop.org/software/PackageKit/gtk-doc/Transaction.html interface
 * arglist (array): "in" arguments of @method
 * progress_cb: Callback that receives a {waiting, percentage, cancel} object regularly; if cancel is not null, it can
 *              be called to cancel the current transaction. if wait is true, PackageKit is waiting for its lock (i. e.
 *              on another package operation)
 * signalHandlers, notifyHandler: As in method #transaction, but ErrorCode and Finished are handled internally
 * Returns: Promise that resolves when the transaction finished successfully, or rejects with TransactionError
 *          on failure.
 */
export function cancellableTransaction(method, arglist, progress_cb, signalHandlers) {
    if (signalHandlers?.ErrorCode || signalHandlers?.Finished)
        throw Error("cancellableTransaction handles ErrorCode and Finished signals internally");

    return new Promise((resolve, reject) => {
        let cancelled = false;
        let status;
        let allow_wait_status = false;
        const progress_data = {
            waiting: false,
            absolute_percentage: 0,
            cancel: null
        };

        function changed(props, transaction_path) {
            function cancel() {
                call(transaction_path, transactionInterface, "Cancel", []);
                cancelled = true;
            }

            if (progress_cb) {
                if ("Status" in props)
                    status = props.Status;
                progress_data.waiting = allow_wait_status && (status === Enum.STATUS_WAIT || status === Enum.STATUS_WAITING_FOR_LOCK);
                if ("AllowCancel" in props)
                    progress_data.cancel = props.AllowCancel ? cancel : null;
                if ("Percentage" in props && props.Percentage <= 100)
                    progress_data.absolute_percentage = props.Percentage;

                progress_cb(progress_data);
            }
        }

        // We ignore STATUS_WAIT and friends during the first second of a transaction.  They
        // are always reported briefly even when a transaction doesn't really need to wait.
        window.setTimeout(() => {
            allow_wait_status = true;
            changed({});
        }, 1000);

        transaction(method, arglist,
                    Object.assign({
                        // avoid calling progress_cb after ending the transaction, to avoid flickering cancel buttons
                        ErrorCode: (code, detail) => {
                            progress_cb = null;
                            reject(new TransactionError(cancelled ? "cancelled" : code, detail));
                        },
                        Finished: exit => {
                            progress_cb = null;
                            resolve(exit);
                        },
                    }, signalHandlers || {}),
                    changed)
                .catch(ex => {
                    progress_cb = null;
                    reject(ex);
                });
    });
}

/**
 * Check Red Hat subscription-manager if if this is an unregistered RHEL
 * system. If subscription-manager is not installed or required (not a
 * Red Hat product), nothing happens.
 *
 * callback: Called with a boolean (true: registered, false: not registered)
 *           after querying subscription-manager once, and whenever the value
 *           changes.
 */
export function watchRedHatSubscription(callback) {
    const sm = cockpit.dbus("com.redhat.RHSM1");

    function check() {
        sm.call(
            "/com/redhat/RHSM1/Entitlement", "com.redhat.RHSM1.Entitlement", "GetStatus", ["", ""])
                .then(result => {
                    const status = JSON.parse(result[0]);
                    callback(status.valid);
                })
                .catch(ex => console.warn("Failed to query RHEL subscription status:", JSON.stringify(ex)));
    }

    // check if subscription is required on this system, i.e. whether there are any installed products
    sm.call("/com/redhat/RHSM1/Products", "com.redhat.RHSM1.Products", "ListInstalledProducts", ["", {}, ""])
            .then(result => {
                const products = JSON.parse(result[0]);
                if (products.length === 0)
                    return;

                // check if this is an unregistered RHEL system
                sm.subscribe(
                    {
                        path: "/com/redhat/RHSM1/Entitlement",
                        interface: "com.redhat.RHSM1.Entitlement",
                        member: "EntitlementChanged"
                    },
                    () => check()
                );

                check();
            })
            .catch(ex => {
                if (ex.problem != "not-found")
                    console.warn("Failed to query RHSM products:", JSON.stringify(ex));
            });
}

/* Support for installing missing packages.
 *
 * First call check_missing_packages to determine whether something
 * needs to be installed, then call install_missing_packages to
 * actually install them.
 *
 * check_missing_packages resolves to an object that can be passed to
 * install_missing_packages.  It contains these fields:
 *
 * - missing_names:     Packages that were requested, are currently not installed,
 *                      and can be installed.
 *
 * - missing_ids:       The full PackageKit IDs corresponding to missing_names
 *
 * - unavailable_names: Packages that were requested, are currently not installed,
 *                      but can't be found in any repository.
 *
 * If unavailable_names is empty, a simulated installation of the missing packages
 * is done and the result also contains these fields:
 *
 * - extra_names:       Packages that need to be installed as dependencies of
 *                      missing_names.
 *
 * - remove_names:      Packages that need to be removed.
 *
 * - download_size:     Bytes that need to be downloaded.
 */

export function check_missing_packages(names, progress_cb) {
    const data = {
        missing_ids: [],
        missing_names: [],
        unavailable_names: [],
    };

    if (names.length === 0)
        return Promise.resolve(data);

    function refresh() {
        return cancellableTransaction("RefreshCache", [false], progress_cb);
    }

    function resolve() {
        const installed_names = { };

        return cancellableTransaction("Resolve",
                                      [Enum.FILTER_ARCH | Enum.FILTER_NOT_SOURCE | Enum.FILTER_NEWEST, names],
                                      progress_cb,
                                      {
                                          Package: (info, package_id) => {
                                              const parts = package_id.split(";");
                                              const repos = parts[3].split(":");
                                              if (repos.indexOf("installed") >= 0) {
                                                  installed_names[parts[0]] = true;
                                              } else {
                                                  data.missing_ids.push(package_id);
                                                  data.missing_names.push(parts[0]);
                                              }
                                          },
                                      })
                .then(() => {
                    names.forEach(name => {
                        if (!installed_names[name] && data.missing_names.indexOf(name) == -1)
                            data.unavailable_names.push(name);
                    });
                    return data;
                });
    }

    function simulate(data) {
        data.install_ids = [];
        data.remove_ids = [];
        data.extra_names = [];
        data.remove_names = [];

        if (data.missing_ids.length > 0 && data.unavailable_names.length === 0) {
            return cancellableTransaction("InstallPackages",
                                          [Enum.TRANSACTION_FLAG_SIMULATE, data.missing_ids],
                                          progress_cb,
                                          {
                                              Package: (info, package_id) => {
                                                  const name = package_id.split(";")[0];
                                                  if (info == Enum.INFO_REMOVING) {
                                                      data.remove_ids.push(package_id);
                                                      data.remove_names.push(name);
                                                  } else if (info == Enum.INFO_INSTALLING ||
                                                             info == Enum.INFO_UPDATING) {
                                                      data.install_ids.push(package_id);
                                                      if (data.missing_names.indexOf(name) == -1)
                                                          data.extra_names.push(name);
                                                  }
                                              }
                                          })
                    .then(() => {
                        data.missing_names.sort();
                        data.extra_names.sort();
                        data.remove_names.sort();
                        return data;
                    });
        } else {
            return data;
        }
    }

    function get_details(data) {
        data.download_size = 0;
        if (data.install_ids.length > 0) {
            return cancellableTransaction("GetDetails",
                                          [data.install_ids],
                                          progress_cb,
                                          {
                                              Details: details => {
                                                  if (details.size)
                                                      data.download_size += details.size.v;
                                              }
                                          })
                    .then(() => data);
        } else {
            return data;
        }
    }

    return refresh().then(resolve)
            .then(simulate)
            .then(get_details);
}

/* Check a list of packages whether they are installed.
 *
 * This is a lightweight version of check_missing_packages() which does not
 * refresh, simulates, or retrieves details. It just checks which of the given package
 * names are already installed, and returns a Set of the missing ones.
 */
export function check_uninstalled_packages(names) {
    const uninstalled = new Set(names);

    if (names.length === 0)
        return Promise.resolve(uninstalled);

    return cancellableTransaction("Resolve",
                                  [Enum.FILTER_ARCH | Enum.FILTER_NOT_SOURCE | Enum.FILTER_INSTALLED, names],
                                  null, // don't need progress, this is fast
                                  {
                                      Package: (info, package_id) => {
                                          const parts = package_id.split(";");
                                          uninstalled.delete(parts[0]);
                                      },
                                  })
            .then(() => uninstalled);
}

/* Carry out what check_missing_packages has planned.
 *
 * In addition to the usual "waiting", "percentage", and "cancel"
 * fields, the object reported by progress_cb also includes "info" and
 * "package" from the "Package" signal.
 */

export function install_missing_packages(data, progress_cb) {
    if (!data || data.missing_ids.length === 0)
        return Promise.resolve();

    let last_progress, last_info, last_name;

    function report_progess() {
        progress_cb({
            waiting: last_progress.waiting,
            percentage: last_progress.percentage,
            cancel: last_progress.cancel,
            info: last_info,
            package: last_name
        });
    }

    return cancellableTransaction("InstallPackages", [0, data.missing_ids],
                                  p => {
                                      last_progress = p;
                                      report_progess();
                                  },
                                  {
                                      Package: (info, id) => {
                                          last_info = info;
                                          last_name = id.split(";")[0];
                                          report_progess();
                                      }
                                  });
}

/**
 * Get the used backendName in PackageKit.
 */
export function getBackendName() {
    return call("/org/freedesktop/PackageKit", "org.freedesktop.DBus.Properties",
                "Get", ["org.freedesktop.PackageKit", "BackendName"]);
}
07070100000044000081A4000000000000000000000001662A077800001570000000000000000000000000000000000000002A00000000cockpit-docker-devel-16/pkg/lib/page.scss@use "@patternfly/patternfly/base/patternfly-themes.scss";
@use "@patternfly/patternfly/patternfly-theme-dark.scss";
@use "./patternfly/patternfly-5-overrides.scss";
@import "global-variables";

/* Globally resize headings */
h1 {
  --ct-heading-font-size: var(--pf-v5-global--FontSize--4xl);
}

h2 {
  --ct-heading-font-size: var(--pf-v5-global--FontSize--3xl);
}

h3 {
  --ct-heading-font-size: var(--pf-v5-global--FontSize--2xl);
}

h4 {
  --ct-heading-font-size: var(--pf-v5-global--FontSize--lg);
}

// Only apply a custom font size when a heading does NOT have a PF4 class
h1, h2, h3, h4 {
  &:not([class*="pf-"]):not([data-pf-content="true"]) {
    font-size: var(--ct-heading-font-size);
  }
}

/* End of headings resize */

a {
  cursor: pointer;
}

.disabled {
  pointer-events: auto;
}

.btn {
  min-block-size: 26px;
  min-inline-size: 26px;
}

.btn.disabled, .pf-v5-c-button.disabled {
  pointer-events: auto;
}

.btn.disabled:hover, .pf-v5-c-button.disabled:hover {
  z-index: auto;
}

.btn-group {
  /* Fix button groups from wrapping in narrow widths */
  display: inline-flex;
}

a.disabled {
  cursor: not-allowed !important;
  text-decoration: none;
  pointer-events: none;
  color: #8b8d8f;
}

a.disabled:hover {
  text-decoration: none;
}

.highlight-ct {
  background-color: var(--ct-color-link-hover-bg);
}

.curtains-ct {
  inline-size: 100%;
}

/* Animation of new items */
.ct-new-item {
  animation: ctNewRow 4s ease-in;
}

:root {
  --ct-animation-new-background: #fdf4dd;
}

.pf-v5-theme-dark {
  --ct-animation-new-background: #353428;
}

/* Animation background is instantly yellow and fades out halfway through */
@keyframes ctNewRow {
  0% {
    background-color: var(--ct-animation-new-background);
  }

  50% {
    background-color: var(--ct-animation-new-background);
  }
}

/* Dialog patterns */

.dialog-wait-ct {
  /* Right align footer idle messages after the buttons */
  margin-inline-start: auto;
  display: flex;
  column-gap: var(--pf-v5-global--spacer--sm);
  align-items: center;
}

:root {
  /* Cockpit custom colors */
  --ct-color-light-red: #f8cccc;
  --ct-color-red-hat-red : #e00;

  // Blend between --pf-v5-global--palette--black-200 and 300
  --ct-global--palette--black-250: #e6e6e6;

  /* Semantic colors */
  --ct-color-fg: var(--pf-v5-global--color--100);
  --ct-color-bg: var(--pf-v5-global--BackgroundColor--100);
  --ct-color-text: var(--ct-color-fg);

  --ct-color-link        : var(--pf-v5-global--active-color--100);
  --ct-color-link-visited: var(--pf-v5-global--active-color--100);

  --ct-color-subtle-copy: var(--pf-v5-global--disabled-color--100);

  // General border color (semantic shortcut, instead of specifying the color directly)
  --ct-color-border: var(--pf-v5-global--BorderColor--100);

  // Used for highlighting link blocks (with a light background blue)
  --ct-color-link-hover-bg  : var(--pf-v5-global--palette--light-blue-100);

  /* Colors used for custom lists */
  // as seen in Journal, Listing, Table widgets and pages like Machines, Updates, Services
  --ct-color-list-text               : var(--ct-color-text);
  --ct-color-list-link               : var(--ct-color-link);
  --ct-color-list-bg                 : var(--ct-color-bg);
  --ct-color-list-border             : var(--ct-color-border);
  --ct-color-list-hover-text         : var(--ct-color-link);
  --ct-color-list-hover-bg           : var(--pf-v5-global--BackgroundColor--150);
  --ct-color-list-hover-border       : var(--pf-v5-global--BackgroundColor--150);
  --ct-color-list-hover-icon         : var(--pf-v5-global--palette--light-blue-400);
  --ct-color-list-selected-text      : var(--ct-color-link);
  --ct-color-list-selected-bg        : var(--pf-v5-global--BackgroundColor--150);
  --ct-color-list-selected-border    : var(--pf-v5-global--BackgroundColor--150);
  --ct-color-list-active-text        : var(--pf-v5-global--palette--blue-500);
  --ct-color-list-active-bg          : var(--ct-color-bg);
  --ct-color-list-active-border      : var(--ct-color-list-border);
  --ct-color-list-critical-bg        : var(--pf-v5-global--palette--red-50);
  --ct-color-list-critical-border    : #e6bcbc; // red-500 mixed with white @ 50%
  --ct-color-list-critical-alert-text: var(--pf-v5-global--palette--red-200);
}

.pf-v5-theme-dark {
  --ct-color-list-critical-bg        : #261213; // red-100 mixed with black-850 @ 20%
  --ct-color-list-critical-border    : var(--pf-v5-global--danger-color--200);
  --ct-color-list-critical-alert-text: var(--pf-v5-global--palette--red-8888);
}

[hidden] { display: none !important; }

// Let PF4 handle the scrolling through page component otherwise we might get double scrollbar
html:not(.index-page) body {
  overflow-block: hidden;

  // Ensure UI fills the entire page (and does not run over)
  .ct-page-fill {
    block-size: 100% !important;
  }
}

.ct-icon-info-circle {
  color: var(--pf-v5-global--info-color--100);
}

.ct-icon-exclamation-triangle {
  color: var(--pf-v5-global--warning-color--100);
}

.ct-icon-times-circle {
  color: var(--pf-v5-global--danger-color--100);
}

// Action buttons in headers add extra space. Offset that with a negative margin
// to compensate, so headings are always the same height regardless of action
// buttons or not.
.pf-v5-c-page__main-breadcrumb .pf-v5-c-button {
  --offset: calc(-1 * var(--pf-v5-global--spacer--sm));
  margin-block: var(--offset);
}

// To be used only from testlib.py for pixel-tests
main.pixel-test {
  overflow-y: clip;
}
07070100000045000081A4000000000000000000000001662A077800000A16000000000000000000000000000000000000003300000000cockpit-docker-devel-16/pkg/lib/pam_user_parser.js/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2013 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 <http://www.gnu.org/licenses/>.
 */

function parse_passwd_content(content) {
    if (!content) {
        console.warn("Couldn't read /etc/passwd");
        return [];
    }

    const ret = [];
    const lines = content.split('\n');

    for (let i = 0; i < lines.length; i++) {
        if (!lines[i])
            continue;
        const column = lines[i].split(':');
        ret.push({
            name: column[0],
            password: column[1],
            uid: parseInt(column[2], 10),
            gid: parseInt(column[3], 10),
            gecos: (column[4] || '').replace(/,*$/, ''),
            home: column[5] || '',
            shell: column[6] || '',
        });
    }

    return ret;
}

export const etc_passwd_syntax = {
    parse: parse_passwd_content
};

function parse_group_content(content) {
    // /etc/group file is used to set only secondary groups of users. The primary group is saved in /etc/passwd-
    content = (content || "").trim();
    if (!content) {
        console.warn("Couldn't read /etc/group");
        return [];
    }

    const ret = [];
    const lines = content.split('\n');

    for (let i = 0; i < lines.length; i++) {
        if (!lines[i])
            continue;
        const column = lines[i].split(':');
        ret.push({
            name: column[0],
            password: column[1],
            gid: parseInt(column[2], 10),
            userlist: column[3].split(','),
        });
    }

    return ret;
}

export const etc_group_syntax = {
    parse: parse_group_content
};

function parse_shells_content(content) {
    content = (content || "").trim();
    if (!content) {
        console.warn("Couldn't read /etc/shells");
        return [];
    }

    const lines = content.split('\n');

    return lines.filter(line => !line.includes("#") && line.trim());
}

export const etc_shells_syntax = {
    parse: parse_shells_content
};
07070100000046000041ED000000000000000000000002662A077800000000000000000000000000000000000000000000002B00000000cockpit-docker-devel-16/pkg/lib/patternfly07070100000047000081A4000000000000000000000001662A07780000073D000000000000000000000000000000000000003700000000cockpit-docker-devel-16/pkg/lib/patternfly/_fonts.scss@mixin printRedHatFont(
  $weightValue: 400,
  $weightName: "Regular",
  $familyName: "RedHatText",
  $style: "normal",
  $relative: true
) {
  $filePath: "../../static/fonts" + "/" + $familyName + "-" + $weightName;

  @font-face {
    font-family: $familyName;
    src: url("#{$filePath}.woff2") format("woff2");
    font-style: #{$style};
    font-weight: $weightValue;
    text-rendering: optimizelegibility;
  }
}

@include printRedHatFont(700, "Bold", $familyName: "RedHatDisplay");
@include printRedHatFont(700, "BoldItalic", $style: "italic", $familyName: "RedHatDisplay");
@include printRedHatFont(900, "Black", $familyName: "RedHatDisplay");
@include printRedHatFont(900, "BlackItalic", $style: "italic", $familyName: "RedHatDisplay");
@include printRedHatFont(300, "Italic", $style: "italic", $familyName: "RedHatDisplay");
@include printRedHatFont(400, "Medium", $familyName: "RedHatDisplay");
@include printRedHatFont(400, "MediumItalic", $style: "italic", $familyName: "RedHatDisplay");
@include printRedHatFont(300, "Regular", $familyName: "RedHatDisplay");
@include printRedHatFont(700, "Bold");
@include printRedHatFont(700, "BoldItalic", $style: "italic");
@include printRedHatFont(400, "Italic", $style: "italic");
@include printRedHatFont(700, "Medium");
@include printRedHatFont(700, "MediumItalic", $style: "italic");
@include printRedHatFont(400, "Regular");
@include printRedHatFont(700, "Bold", $familyName: "RedHatMono");
@include printRedHatFont(700, "BoldItalic", $style: "italic", $familyName: "RedHatMono");
@include printRedHatFont(400, "Italic", $style: "italic", $familyName: "RedHatMono");
@include printRedHatFont(500, "Medium", $familyName: "RedHatMono");
@include printRedHatFont(500, "MediumItalic", $style: "italic", $familyName: "RedHatMono");
@include printRedHatFont(400, "Regular", $familyName: "RedHatMono");
07070100000048000081A4000000000000000000000001662A077800000194000000000000000000000000000000000000004500000000cockpit-docker-devel-16/pkg/lib/patternfly/patternfly-5-cockpit.scss/* Set fake font and icon path variables */
$pf-v5-global--font-path: "patternfly-fonts-fake-path";
$pf-v5-global--fonticon-path: "patternfly-icons-fake-path";
$pf-v5-global--disable-fontawesome: true !default; // Disable Font Awesome 5 Free

@import "@patternfly/patternfly/patternfly-base.scss";

/* Import our own fonts since the PF4 font-face rules are filtered out in build.js */
@import "./fonts";
07070100000049000081A4000000000000000000000001662A077800004DE3000000000000000000000000000000000000004700000000cockpit-docker-devel-16/pkg/lib/patternfly/patternfly-5-overrides.scss/*** PF5 overrides ***/
// Pull in variables (for breakpoints)
@use "global-variables" as *;

// PF Select is deprecated - no issue reported upstream - this needs to be removed from our codebase
// Make select have the expected width
.pf-v5-c-select[data-popper-reference-hidden="false"] {
  inline-size: auto;
}

/* WORKAROUND: Navigation problems with Tertiary Nav widget on mobile */
/* See: https://github.com/patternfly/patternfly-design/issues/840 */
/* Helper mod to wrap pf-v5-c-nav__tertiary */
.ct-m-nav__tertiary-wrap {
  .pf-v5-c-nav__list {
    flex-wrap: wrap;
  }

  .pf-v5-c-nav__scroll-button {
    display: none;
  }
}

/* Helper mod to center pf-v5-c-nav__tertiary when it wraps */
.ct-m-nav__tertiary-center {
  .pf-v5-c-nav__list {
    justify-content: center;
  }
}

/* Fix overflow issue with tabs, especially seen in small sizes, like mobile
seen in:
- https://github.com/cockpit-project/cockpit-podman/pull/897#issuecomment-1127637202
- https://github.com/patternfly/patternfly/issues/1625
- https://github.com/patternfly/patternfly/pull/2757
- https://github.com/patternfly/patternfly/issues/4800
- https://github.com/patternfly/patternfly-design/issues/840
- https://github.com/patternfly/patternfly-design/issues/1034
- https://github.com/cockpit-project/cockpit-podman/issues/845

This disables the large and halfway useless overflow buttons and causes the tabs
to wrap around when there isn't enough space.
*/
.pf-v5-c-tabs__list {
  flex-wrap: wrap;
}

/* Fix select menu rendering */
ul.pf-v5-c-select__menu {
  /* Don't get too tall */
  max-block-size: min(20rem, 50vh);
  /* Don't have a horizontal scrollbar */
  overflow-y: auto;
}

/* Adjust padding on form selects to resemble PF non-form selects */
/* (This can be seen when the longest text is selected on a non-stretched select) */
/* Upstream: https://github.com/patternfly/patternfly/issues/4387 */
/* Cockpit-Podman: https://github.com/cockpit-project/cockpit-podman/issues/755 */
select.pf-v5-c-form-control {
  --pf-v5-c-form-control--PaddingRight: 41px;
  --pf-v5-c-form-control--PaddingLeft: 8px;

  // Firefox's select text has additional padding (4px)
  @-moz-document url-prefix() {
    --pf-v5-c-form-control--PaddingRight: 37px;
    --pf-v5-c-form-control--PaddingLeft: 4px;
  }
}

// Workaround the transparent background for HTML select options
// https://github.com/patternfly/patternfly/issues/5695
.pf-v5-c-form-control option {
  background-color: var(--pf-v5-c-form-control--BackgroundColor);
  color: var(--pf-v5-c-form-control--Color);
}

// Fix the double-spacing issue in the non-deprecated split buttons
// https://github.com/patternfly/patternfly-react/issues/10302
.pf-m-split-button .pf-v5-c-menu-toggle__controls {
  margin: 0;
}

// Fix the double-spacing issue for other dropdowns
// https://github.com/patternfly/patternfly-react/issues/10302
.pf-v5-c-menu-toggle__controls:last-child > .pf-v5-c-menu-toggle__toggle-icon {
  margin-inline-end: 0;
}

// The default gap between the rows in horizontal lists is too large
.pf-v5-c-description-list.pf-m-horizontal-on-sm,
.pf-v5-c-description-list.pf-m-horizontal {
  --pf-v5-c-description-list--RowGap: 1rem;
}

.pf-v5-c-description-list {
  // When using horizontal ruler inside description list it's just for the spacing - don't show it
  > hr {
    border-block-start: none;
  }
}

.pf-v5-c-modal-box.pf-m-align-top {
  // We utilize custom footers in dialogs
  // Make sure that the buttons always appear in the next line from the inline alerts
  .pf-v5-c-modal-box__footer {
    flex-wrap: wrap;
    row-gap: var(--pf-v5-global--spacer--sm);

    > div:not(.pf-v5-c-button):not(.dialog-wait-ct) {
      flex: 0 0 100%;
    }
  }
}

// don't hide long dialog titles
.pf-v5-c-modal-box__title-text {
    white-space: normal;
}

.pf-v5-c-card {
  // https://github.com/patternfly/patternfly/issues/3959
  --pf-v5-c-card__header-toggle--MarginTop: 0;

  .pf-v5-c-card__header:not(.ct-card-expandable-header),
  .pf-v5-c-card__header:not(.ct-card-expandable-header) .pf-v5-c-card__header-main {
    // upstream fix (pending): https://github.com/patternfly/patternfly/pull/3714
    display: flex;
    flex-wrap: wrap;
    row-gap: var(--pf-v5-global--spacer--sm);
    justify-content: space-between;
  }

  .pf-v5-c-card__header:not(.ct-card-expandable-header) {
    > .pf-v5-c-card__actions {
      flex-wrap: wrap;
      row-gap: var(--pf-v5-global--spacer--sm);

      // PF4 CardActions act up when using buttons while the title is large of font
      // https://github.com/patternfly/patternfly/issues/3713
      // https://github.com/patternfly/patternfly/issues/4362
      margin: unset;
      padding-inline: var(--pf-v5-c-card__actions--PaddingLeft) unset;
    }
  }
}

// Add some spacing to nested form groups - PF4 does not support these yet
// https://github.com/patternfly/patternfly-design/issues/1012
.pf-v5-c-form__group-control {
  .pf-v5-c-form__group, .pf-v5-c-form__section {
    padding-block-start: var(--pf-v5-global--spacer--md);
  }
}

// Alerts use elements that have fonts set in other frameworks (including PF3);
// generally, this is an H4 that often has a font size and sometimes family set.
// Therefore, it should inherit from the alert font set at the pf-v5-c-alert level.
// https://github.com/patternfly/patternfly/issues/4206
.pf-v5-c-alert__title {
  font-size: inherit;
  font-family: inherit;
}

.pf-v5-c-toolbar {
  // Make summary content use the same vertical space as the filter toggle,
  // when possible.
  // https://github.com/patternfly/patternfly-design/issues/1055
  &.ct-compact {
    @media screen and (max-width: $pf-v5-global--breakpoint--lg - 1) {
      display: flex;
      flex-wrap: wrap;

      > .pf-v5-c-toolbar__content:first-child {
        flex: auto;
      }

      .pf-v5-c-toolbar__content-section {
        inline-size: auto;
      }
    }
  }
}

// When there is an Alert above the Form add some spacing
.pf-v5-c-modal-box .pf-v5-c-alert + .pf-v5-c-form {
  padding-block-start: var(--pf-v5-global--FontSize--sm);
}

// HACK: Not possible to specify text, so needs some hacks, see https://github.com/patternfly/patternfly-react/issues/6140
.pf-v5-c-toolbar__toggle {
  .pf-v5-c-button.pf-m-plain {
    color: var(--pf-v5-c-button--m-link--Color);

    .pf-v5-c-button__icon {
      margin-inline-end: var(--pf-v5-global--spacer--sm);
    }
  }
}

// Flex should use gap, not a margin hack
// https://github.com/patternfly/patternfly/issues/4523
// Override default spacing from lg -> md
.pf-v5-l-flex {
  gap: var(--pf-v5-l-flex--spacer-base);

  &:not([class*="pf-m-space-items-"]) {
    > * {
      --pf-v5-l-flex--spacer--column: 0;
    }
    gap: var(--pf-v5-l-flex--spacer--md);
  }

  // Negate the margin hack used by immediate flex children
  // (except for nested flex, as we want to mind the gap)
  > :not(.pf-v5-l-flex) {
    --pf-v5-l-flex--spacer-base: 0;
  }

  // Undo all spacer modification adjustments
  &[class*="pf-m-space-items-"] {
    > * {
      --pf-v5-l-flex--spacer--column: 0;
    }
  }

  // Re-add spacer modification adjustments on the flex layout widget
  // (using class attribute matching for handling breakpoint -on- also)
  @each $size in (none, xs, sm, md, lg, xl, 2xl, 3xl, 4xl) {
    &[class*="pf-m-space-items-#{$size}"] {
      --pf-v5-l-flex--spacer-base: var(--pf-v5-l-flex--spacer--#{$size});
    }
  }
}

// Realign the radio and checks: https://github.com/patternfly/patternfly/issues/5802
.pf-v5-c-radio,
.pf-v5-c-check {
  // `baseline` is different in Firefox than Chrome & WebKit; use `normal`
  align-items: normal;

  // Remove incorrect PF settings on the children
  &__label,
  &__input {
    margin-block: auto;
    align-self: unset;
    transform: unset;
  }

  // Slightly shift the radio and check widgets
  &__input {
    // Shift up the checks/radios for (most) browsers
    transform: translateY(-1px);
    // Mozilla doesn't need the translation, so undo it
    -moz-transform: none;  // stylelint-disable property-no-vendor-prefix
    // If the size is not specified, browsers may size it between 12px - 16px; so let's set it to the font size (which winds up 16px)
    block-size: var(--pf-v5-global--FontSize--md);
    // Use the height for width too
    aspect-ratio: 1;
  }
}

// InputMenus now use the PF Panel component which mistakenly uses position:
// relative, when it needs to be set to absolute.
// Additionally, it needs to be full width to properly align to the widget the
// popover panel describes.
// https://github.com/patternfly/patternfly-react/issues/7592
.pf-v5-c-search-input__menu.pf-v5-c-panel {
  position: absolute;
  inline-size: 100%;
}

// Breadcrumb links should have the correct pointing hand cursor.
//
// PatternFly requires a "to" attribute for an actual link, but we use some
// funky onClick JS for navigating and override it with a className.
//
// Therefore, instead of having a proper <a href="..."> being rendered, we need
// to override the link. This is a problem with a (correct) assumption in PF
// and our (incorrect) way of not using links (but using JavaScript) for
// linking.
//
// Nevertheless, Cockpit needs to be adapted for this to work as expected.
.pf-v5-c-breadcrumb__link {
  cursor: pointer;
}

//Page headers are inconsistent with shadows and borders
// https://github.com/patternfly/patternfly/issues/5184
.pf-v5-c-page__main-group,
.pf-v5-c-page__main-nav,
.pf-v5-c-page__main-section.pf-m-light:not(:last-child) {
  z-index: var(--pf-v5-c-page--section--m-shadow-bottom--ZIndex);
  box-shadow: var(--pf-v5-c-page--section--m-shadow-bottom--BoxShadow);
}

// Dark mode fixes for several PF components
.pf-v5-theme-dark {
  // Change background color behind cards
  // (matches PF surge website; PF doesn't specify otherwise)
  .pf-v5-c-page__main-section {
    --pf-v5-c-page__main-section--BackgroundColor: var(--pf-v5-global--BackgroundColor--dark-300);
  }

  // Adapt breadcrumb bar to be similar color as PF website
  // (We use header bars in slightly different ways from PF)
  // https://github.com/patternfly/patternfly/issues/5301
  .pf-v5-c-page__main-breadcrumb {
    --pf-v5-c-page__main-breadcrumb--BackgroundColor: var(--pf-v5-global--BackgroundColor--dark-100);
    background-color: var(--pf-v5-global--BackgroundColor--dark-100);
  }

  // Fix input group background and borders
  // (Looks fixed in PF5, but not in PF4)
  .pf-v5-c-text-input-group {
    background-color: var(--pf-v5-global--BackgroundColor--400);

    .pf-v5-c-text-input-group__text {
      &::before {
        border-block-start-color: transparent;
        border-inline-end-color: transparent;
        border-inline-start-color: transparent;
      }

      &:is(:focus,:hover)::after {
        border-block-end-color: var(--pf-v5-global--active-color--100);
      }

      &:not(:focus):not(:hover)::after {
        border-block-end-color: var(--pf-v5-global--BorderColor--400);
      }
    }
  }

  // FIXME: https://github.com/patternfly/patternfly/issues/5278
  .pf-v5-c-modal-box .pf-v5-c-table {
    background-color: inherit;
  }
}

// Fix icons in buttons
.pf-v5-c-button__icon.pf-m-start {
  margin-inline: 0 var(--pf-v5-c-button__icon--m-start--MarginRight);
}

// Drop side padding in mobile mode,
// intended mainly for PF PageSection elements (pf-v5-c-page__main-section).
// It's similar to adding padding={{ default: 'noPadding', sm: 'padding' }},
// except this only affects the sides, not the top and bottom.
@media screen and (max-width: $pf-v5-global--breakpoint--sm) {
  .pf-v5-c-page__main > section.pf-v5-c-page__main-section:not(.pf-m-padding) {
    padding-inline: 0;
  }
}

// Patch tabular number 0s to not have the slash inside
// https://github.com/RedHatOfficial/RedHatFont/issues/53
// https://github.com/patternfly/patternfly/issues/5308
@font-face {
  /* red-hat-text-regular */
  unicode-range: U+0030;
  font-family: RedHatText;
  font-style: normal;
  font-weight: 400;
  src: url(data:font/woff2;base64,d09GMgABAAAAAAe8ABAAAAAAHEQAAAdfAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGVA/RkZUTRwaGBuaeBxuBmAAgkIRCAqBAIEICwwAATYCJAMUBCAFk0wHNRsqG1FUjgoEfz9ujIGNa3r1k8ghpztyImrCBYeonxUhtHdiJ+VxOsW1XvMb+MfL1IXbjodirvftJrlLmXk8oXAIGtz3ZWMrDLGtwvP7dO1vUK18MEuOuwr1HGbdxvxtJkjRURZ0AACm/+Q57n/s4iCgJJL8gTb8spMpWXyTfaB1cheGpDVP+q9RXC7EbPF87t79W+ptEWcSRJxLBcrqiyc9mJ/A5OTLl8rkeDKpTCaVSqRSeRKJRCKRJ6lV11TVDu9HyuJ0/Y9TL5b/hy5+R0aejKwkK8mXBOLAMYRbAP/3rn1aYAcsaY7cXWsUktB1QLOX5Gj3FzgHqMqYN5+yBWRFikmVJYERwEKRA5YVBoWr1JXdhjkRb+jfXuUqsphizGjMnud5RoEapc7yJhDeW1SeZtBWnpvP6D8mOcO8dhBSraNojaD2yybgbQPwhjbA2xMCHdEVJEokAu2t9Yp/EWNaoYXAQK3LPs8IEysenyvh2++a4Tsfb0To2HHUVRk68cYrZfpQy05dn0A66VSbbF6hjflJMm9Q4RWVFEBMaI5CFkyaph/NGFGHKzOpeYPeeusrrfoL30CHAjIyjDLJn/GXjQJSMhSG16nl6boWG2QPw4vDk5q39d2BXlhvqrHx5IoCRAZcxvNOMgGQdGa3It3wh0L1cq0yZS93LQasSKAXZKHF5T/jpNed7iewLTa3BLO3HQ1chV4/PqWW5xsv9scjxfLEvg6W66ymBWQLnF4HwG05Qpjl+0Mt3yccNMDFALN63HnhmwhUAJgBUEJ01QXSVgLLZUjnGeAUbel0vddcbCr2SsAlQulrmmtzEqdHsVAOfeWleo/JPLQOamTU66LjeNhzp6KqAKB1MLTQBx8Uae18DIfaBXcA43mTUQeLmrBAwtF8Fy1NAslad5ibzH4kuMkgqErYAITmyx40CQlJ7dTem8p1V1IvnRFDsnzeUQe+aPuhHgG95VfxBtGxZoZJIF8r8p9XmYjEGxId7kNBPkX6ZksLGbHULnNBv5t2vKjt1nwQy2RXecnhRbVxpSMPi83HqMSQdRjE6NC0oEOdCTlVp3RmQebQbDTfd+E4OkYMPkagMB7MMPPbJzLYBXAVDQJYK3WrCOgXKXJ5hgdPgi+g90EfCHrjAAyGAxYFWZXHcGXhJ9Tg7OiH21m2gmfWFKi20Wo8Fn1TaG9iJiMNhT01isE6Qvcm9RoGQS/YLXVkx2XaQv4maOZD2wWPr8rbmZXzhscBbT4r2qJHotccXba3jCDkSZftjYHIKk5GPyXQfSgKNpUYOdkzRxW9rW7DaFEgoHJdZoaGC2BoFF4xMOqN5kS0/PNMJBgD6B4HQxayhVWEIHBcS5MOpQdgb4JQPebjAXMo3pabQwRzohBGV30Go/tBZr+aP5dVaWTQLzQE6snLfFBLG3ktDbDB3oQ4jHosgoyqKDMaRfEYTk7gKBgLbFo3Jg7V1Irfb3Wuo8B9gPiWAENpKFBgIAEWaqFSZiiY6kuBqS2vuH+AaBCASCSFGoe/icLdLQrRYgBUoD1KDAXTAZQASFkXTvv+yB20DbvmmL/ndTqfMVDiT7SITwPFfydBBs3A8LD73aoGzdv55V+pTP9sAhCIvP2w2u7IgN+p+LZo4exWW8+1pJYgkAtG5QiyRQ4LqoD/PmITkA9s5V6sEqh0pYFECCwFwNC4RAD2+YcAoWOMJUBqxioIUBgUVwlQ6hmvAZVOmQI0dM/ZBGjalwNqXYqLEKCdLkUbAdq723oBHfStHhGgo97VF0AndzuEAJ0NaP4EdDdaT9MiynZ4Avgg9I3OkDrHLCjMj01QGhttUOkfX6BhZHaFpidagdrgYo9vu8DL0D6G90voYGp1HDqaWL2CTjF8/IPOZjef/gu6W66bVsgVfikhX4CRupq6TeRZwEUeuRzkksTIPSUXcdkYOVJhgZxSIssCjBVK2wwGX4gFKhadLZcy4LM7L+qOxxOyhaQk6Ejnchm2Atvk4XJp9Wykhn90HbqUvDyHbUGlLZ8X/RB+yEFlzV/sENPhgW+dyD7HbRtnvgK64z9z/WD4IdzyL/vrCb5KUqRgL+V9gGn0v/D+GnY0nbhh2vk9osu2G1CDTZtcqzd2+tEFO4khorWB4FwD7QhObbCD7uVubobyQza9yZpYiWo0ZM6BRlOJ/k62KJ+tiHYGkk+RUutIGj2xdQeb8gqJOq9Tr3dzHPI6XKhEaJ0NeZLzsG+S4qLA/IQTxDHLLORUvlCOrzI5QwlPrpjyThGjIGgmECZItiwss4xT+CKps5O+SKGEZEm4yHduMQitPPLg2zjbRkKQJVtsYxZYSVeC5uvSOZDPcOecCUs5sLfwTQescCutiZZg9H66MvflaXyLncfLRP+Q9s7YYoJBaHRhkz5y+KMHkrxNcmEXqS7LMFm5svK7cxOE8r9LB9albLEE+80Cl7eGlTrlqR0nS1dQgkQ3bYuWJLrmZTiraE+GjP8IPrtDF85Ce3Y081ATaMVR2G+FkHpE1ksdy3WWFgAA);
}

@font-face {
  /* red-hat-text-italic */
  unicode-range: U+0030;
  font-family: RedHatText;
  font-style: italic;
  font-weight: 400;
  src: url(data:font/woff2;base64,d09GMgABAAAAAARQAAsAAAAADYwAAAQEAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAABmAAgkIRCAqBKIE/CwwAATYCJAMUBCAFkz0HIBvTDBGVnFUBfhzYbVYrgpXBW2mcjbMQzKj2CJ7Xi/85N8mCj8I31EGoJLfybs1VZl6ZyBK4Vh2jT3nLs5sj4dUngEuZ1e+dluoRtq0zKIlQcJ/3KFXzHQpsqVVIHAiJKjWtCRXZx1eZiYvUsXGRKq/wZqa/mnANebooLlGQzkvbYH3zQmMn02IhKYosT9HMsus4eAn7YwxsuHz9BsIUU7K//3+D7O+YisshfkyPYohAhgwDBFggExggNwS5XGBKp5Ns77wjgCgJZxAJSUd/+EtkvGcTotPRBgWoIcdUbAJoACBJzValtf/57znl3Xdr570fp36y5XnFPKe8Vz//gw/i1Pe/mp7yyQfnv/fhbu++FwW2/uD992tAS899ft8Trzjx6K1fv+LYrcLjHn6ydsziJQfvs/T+vXbd9uCNvNW7bXOQc2rfHfXM6M2tz2QH3HJDdeOhZ6/bcNr60+5qHVurzlm/8V2ceq8KTOj8lrZLLwhEuvjgRyZds3N7wW+VevYjfH7H+xfAF1/9ejG89NDNSUUCgYkJtRFiwh566jiLuf8msSHpIqvTNiYIFPpLICEElgAYEmcJwLYeEiA0YowASSmWC5AZEGcIkOseLwlQaMZfApR0TeMFKNs2/SRARTvbX4DquPJK1Fyav4G6PsXlaOhVfIymS8s90dK//MXJ3XSvDJeJvIoH8BD6RAtJI2YiMyfWITcqrkehX3yGkmGpjLIH0kJUDMrWS6vK41GLYdmdqJtS7IqGCcVdaMaw4ie0zCrfelfdjCoT/mNmMPqRhFKFoYAn4EN3lRy6ymXQGkHdzZk0qOVSDE28sMpAUpCmwthIibhcJeFtfkk4UoOOS9ucfNVJoSCkBNJCfGRp0GM6WHHSAca2e5XjNc9wTbxfzu6Ea9L+bzesFFy9ku2ECd4U8btM2SS6R3W0Dm7Nvct9MWBCd3CGAGwwMUlgN3wZ49tTAJ/DEwjFDmZ29mJ3cIrNNpFQ8POCMF6hO2OBiJHTJl2hWM1IqAmEdGPw2aecpAiDHtYuOAoWBOgaRC0j30/SgKTns3uMlCTSsaA5LIXzPVMpqbNQ3zXGyb+rCAq66SboZlBgH0TKIZRZb8TDpTTgfRmXZlLpYu2obzb24skIX0S9eU9FapWzKOozbZflzSW6GXkjQoskWjn00StBUChOXManswhawW0pKUkYMcWhCC3HQCq5Tpb2YA+VVMJP/LjFHXYFe5++22b/HhYBgX2DvWDJMKYOJMjwm/C3tCQUO7kgcYS2wn4xVlSi3TUef1ruwTWOT/dsRyfVaLGfUW7bTbBL7u/Spt0zJvsrQ671aEScUIObukdTouZX0C1qsUxUSreOW5fgH/+/9g2dIwMA);
}

@font-face {
  /* red-hat-text-medium */
  unicode-range: U+0030;
  font-family: RedHatText;
  font-style: normal;
  font-weight: 700;
  src: url(data:font/woff2;base64,d09GMgABAAAAAARYAAsAAAAADewAAAQLAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAABmAAgkIRCAqBBIEUCwwAATYCJAMUBCAFlEEHIBssDRHF5AH4ceCUZY9pfajQk3kIirq5CjGXcNU3dzw879+/rX1uIKlLfhd0AMukgzVCNamGesTI7ZlyoyVrc1NnSh8mwlFTCbdmKtP+Dit8tlI4uJx+ywNd5wGHgUayB46pjw3cEzAMA9obaA2wrQFmmmYhpw1jFzajZpAvnYxflbQxTcLQpXLM0Ca6k6JAlq87j+w4K3uxFI1geN+hw/TSSqvs7/9fkv0drbEL4rd0DbUEMmSoJkA3mUA1+dRCPvkEWuVy9Wvk3gogChCASEg2jMM3kQFbCpHLeQX5QRHkQwN0AuQDQJKKgBaFvt38Uaz7eMt7a7/Z9PEZ8aON78yfr31ftGdVrYts/HLjRxs+3vR+/avCjj48fmJf0pGHJ47HC07O3LomD7Q8+b81b41tT7+YcqBX76kHP6toeve6uOr7HbdHcPpTA4FI2z6r8ajDzNduv61o9gG+OvXRHvj62x9Z3dxvaeV5iEISCMRN8R8greRLWf67/xgxnHRSmzRJXYH8ykkgIQR6AagXFwRgsnMChLLRQYCkYEwRIFMtDguQT9n4VoD8iqfSAhRQOvUVoKDJWWEBCimZbRCgsJLZWQGK2JHvOwGK0qrU0zRQTIX+TAPF7ShYAw2UUPX+QQOlPK2B0h43qH8Zn1vtTryFcQacQ2iQEpCUyBDIdMocyKdBLkN+VfIbFFDn1ISCzqQTFFLjmYspTNgFRVLn7i4U1ap5UEyz3oPiqfNYGErocG9CKX8rC6U9rcsvKCO/kQYSqRuJuDwMtTW1taAjjw3t2SxoSmBoS0r4bCaGejLMk5AUVORhLKV0NTS4CPNkDHWmRKTxbV7zrA2Hg5iIED5xz1gixkpA9eTYMhodPRv3toVTwYZzfnC4qGIhd21cYi8zNAv28+xKENSUf4kTUDV3CeiKHtmuZ5iVq4VkIvBLjqMuL1k33vskVyY0SJAOHQrQWp1NH+/QrQwsLOn0E/yJTk3V+ls1HV21l+kE5fySUAsfcTET7pUUkoghMeVgReYSIGkgqh/yvZMCCzNqeIwJLkmIVKFwqAij/piFK0VURf4O8Tvu43iIgnSaBx0kHOxCkGz4FBoi5j/kigfE2TnGwkg58w7pYGap20if9/p69PIrmKo/EcXZ83nb778FJJpPOBNISDCEbOjCr5wnOFjXszsvzLpQFCxKMUkkxZQ6hYTqEpKrYWNsCYyvHC4RuAopKPidh9FUe5gtE2nhvsDPoN1DZrBablAY/vtRWGYtRDj3dii4i2KFX/5YnaLQ3Jx/M14CbaSX7fC6iCkQYjcpW2xHgXndGplBaGzmiD8/n4udCiLBhyIarSIVSpzlI9gYQiEnAv1uYLpLNP0kBmgE4daZBPYHprlOLAA=);
}

@font-face {
  /* red-hat-text-medium-italic */
  unicode-range: U+0030;
  font-family: RedHatText;
  font-style: italic;
  font-weight: 700;
  src: url(data:font/woff2;base64,d09GMgABAAAAAASEAAsAAAAADlwAAAQ2AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAABmAAgkIRCAqBFIEsCwwAATYCJAMUBCAFlSEHIBukDRGVnM0Bfh7YtpMt1pMMwRCUu0ZjuTUEq7QbeQVD9K0cz/Ot8btvZnf/YKZJNFnHE01D/RnzSKWKhUyUChVONznY33vvR2dGwQ2aI5GoNmehLcEOiWwABJeyW3OVmVcm8ohUq47RZ7FbwPRAePVny2WpHmHbKIFFSYTiZgv0eNbhCK663oXEgZAouis1niAM8d0RrIJ/++6lfvsW4tlZz+XCcgddeYS86ClXUGEcuZ9pztoaHSVD7Uom1PoYXN4HzmM1HDvo8UJstrl+/duh/ooIdyFfyySwWFBRMV/ALlUwX2cxOp1g8////ez/7wTIYNOHFBRPpuM3UT2dduT/fwTQg7HosBaHAOMBUJSJ41s7f/UIRt8fohuPkYqTk0DjIYx+wDSdIBXHF4dHTScnx41XjWvbCeXg09bl7Kq2spLoZndx7KxpLys+bE+xPzlSvtEU9Kdaw+XrzSFfCjx7S7xYdVVkdxPPNt0V6Tn3pXNM35O5+5Lj8TVY8f+1pJQVQcrNGpprY9Ik7deGqZ/h49BxJ+DT3euFXl45vWgKCHjCmw1FF5wzw5Or//U/Ro5RHsGeErZC0JurgIII9gFYngkI4ITnAmJa5AKKIccFVEvTCwGdufkT0JtQdggYzCr5AkacqAYBzaT6BgLGmFS/CBjrdk8VMM7s/k7AeDMHpoAJbo+kCZhoXuMJmGx828MUE9q1+VPNbbuqdGPwlM8QK4OHYnwOQ3UoSdDZnlXozS1EGGwsFhjxtDRBs7B2yceo12BsltZPGGdzPw7jre+/YEKWDgaYaOfIK0w2u7lgirlNLucvmGpSt7lM7qfkWAGGRgZGhtBDQEM3moI2CEMXpUxIkxiaqrFAplRBDQHGchVPX5/lsEBN6JEyib5sun/VmWE4kkNiintWMinWBKkq1j5mPQKNN61wQ1xoZ4PJlTQWfjfEOg5qkYlI9NKWD4LKvsIBWNs3wdfAg/b9JD/GK04tASqxsZdO7i0AsMW2oxcemJdGiAQY6v2hj4/5jub2DnznuBqjbimBQcMNRmM/O3D5FqNuVZcqy4v1JSg8sy8eXD/7opUqTiaFNzdUr4ltdQWPsnLjOwlH+QU1zSZWiSQ6EEejweUpky0p6SA48swT/xBwKuiT9El+l5vYD1LSkIqGHupwVyFgqz9rKPLSzaGjvds6iLOcPkV66UGKvJMJY/hKQzuzbm7RfuSNODEixDT0CVekwI2Yun7uzTyIAlGRSAvNsUrPEhHr4Wac1Xe2cgCFYQwS3sbkcQPzJRV0vfftYZIvhIHN7j0prlgOThTI+EtwCbhkFD29IrGHWGFLMysqF5fCO59XB/DG+Xle3pOQIjH2k9PYLoHSvy+RUVwIR/xlEFc+BxEXVNQ/eHIo4f1HsH/OR5zIVm52mVCpjKZqyJHY+j1zNf8f/y+v5iopAAAAAA==);
}

// Fix for https://github.com/patternfly/patternfly/issues/5855
.pf-v5-u-font-weight-light {
  font-weight: 300;
}

// Workaround for https://github.com/patternfly/patternfly/issues/5865
.pf-v5-c-card.pf-m-selectable, .pf-v5-c-card.pf-m-clickable {
  isolation: auto;
}
0707010000004A000081A4000000000000000000000001662A0778000045E5000000000000000000000000000000000000002800000000cockpit-docker-devel-16/pkg/lib/plot.js/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
 */

import cockpit from 'cockpit';

/* The public API here is not general and a bit weird.  It can only do
 * what Cockpit itself needs right now, and what can be easily
 * implemented without touching the older Metrics_series classes.
 *
 * The basic idea is that you create a global PlotState object and
 * keep it alive for the lifetime of the page.  Then you instantiate
 * ZoomControl and SvgPlot components as needed.
 *
 * To control what to plot, you call some methods on the PlotState
 * object:
 *
 * - plot_state.plot_single(id, metric_options)
 *
 * - plot_state.plot_instances(id, metric_options, instances, reset)
 *
 * You need to figure out the rest of the details from the existing
 * users, unfortunately.
 */

class Metrics_series {
    constructor(desc, opts, grid, plot_data, interval) {
        cockpit.event_target(this);
        this.desc = desc;
        this.options = opts;
        this.grid = grid;
        this.plot_data = plot_data;
        this.interval = interval;
        this.channel = null;
        this.chanopts_list = [];
    }

    stop() {
        if (this.channel)
            this.channel.close();
    }

    remove_series() {
        const pos = this.plot_data.indexOf(this.options);
        if (pos >= 0)
            this.plot_data.splice(pos, 1);
    }

    remove() {
        this.stop();
        this.remove_series();
        this.dispatchEvent("removed");
    }

    build_metric(n) {
        return { name: n, units: this.desc.units, derive: this.desc.derive };
    }

    check_archives() {
        if (this.channel.archives)
            this.dispatchEvent("changed");
    }
}

class Metrics_sum_series extends Metrics_series {
    constructor(desc, opts, grid, plot_data, interval) {
        super(desc, opts, grid, plot_data, interval);
        if (this.desc.direct) {
            this.chanopts_list.push({
                source: 'direct',
                archive_source: 'pcp-archive',
                metrics: this.desc.direct.map(this.build_metric, this),
                instances: this.desc.instances,
                'omit-instances': this.desc['omit-instances'],
                host: this.desc.host
            });
        }
        if (this.desc.pmcd) {
            this.chanopts_list.push({
                source: 'pmcd',
                metrics: this.desc.pmcd.map(this.build_metric, this),
                instances: this.desc.instances,
                'omit-instances': this.desc['omit-instances'],
                host: this.desc.host
            });
        }
        if (this.desc.internal) {
            this.chanopts_list.push({
                source: 'internal',
                metrics: this.desc.internal.map(this.build_metric, this),
                instances: this.desc.instances,
                'omit-instances': this.desc['omit-instances'],
                host: this.desc.host
            });
        }
    }

    flat_sum(val) {
        if (!val)
            return 0;
        if (val.length !== undefined) {
            let sum = 0;
            for (let i = 0; i < val.length; i++)
                sum += this.flat_sum(val[i]);
            return sum;
        }
        return val;
    }

    reset_series() {
        if (this.channel)
            this.channel.close();

        this.channel = cockpit.metrics(this.interval, this.chanopts_list);

        const metrics_row = this.grid.add(this.channel, []);
        const factor = this.desc.factor || 1;
        const threshold = this.desc.threshold || null;
        const offset = this.desc.offset || 0;
        this.options.data = this.grid.add((row, x, n) => {
            for (let i = 0; i < n; i++) {
                const value = offset + this.flat_sum(metrics_row[x + i]) * factor;
                if (threshold !== null)
                    row[x + i] = [(this.grid.beg + x + i) * this.interval, Math.abs(value) > threshold ? value : null, threshold];
                else
                    row[x + i] = [(this.grid.beg + x + i) * this.interval, value];
            }
        });

        this.channel.addEventListener("changed", this.check_archives.bind(this));
        this.check_archives();
    }
}

class Metrics_stacked_instances_series extends Metrics_series {
    constructor(desc, opts, grid, plot_data, interval) {
        super(desc, opts, grid, plot_data, interval);
        this.instances = { };
        this.last_instance = null;
        if (this.desc.direct) {
            this.chanopts_list.push({
                source: 'direct',
                archive_source: 'pcp-archive',
                metrics: [this.build_metric(this.desc.direct)],
                metrics_path_names: ['a'],
                instances: this.desc.instances,
                'omit-instances': this.desc['omit-instances'],
                host: this.desc.host
            });
        }
        if (this.desc.pmcd) {
            this.chanopts_list.push({
                source: 'pmcd',
                metrics: this.desc.pmcd.map(this.build_metric, this),
                metrics_path_names: ['a'],
                instances: this.desc.instances,
                'omit-instances': this.desc['omit-instances'],
                host: this.desc.host
            });
        }

        if (this.desc.internal) {
            this.chanopts_list.push({
                source: 'internal',
                metrics: [this.build_metric(this.desc.internal)],
                metrics_path_names: ['a'],
                instances: this.desc.instances,
                'omit-instances': this.desc['omit-instances'],
                host: this.desc.host
            });
        }
    }

    reset_series() {
        if (this.channel)
            this.channel.close();
        this.channel = cockpit.metrics(this.interval, this.chanopts_list);
        this.channel.addEventListener("changed", this.check_archives.bind(this));
        this.check_archives();
        for (const name in this.instances)
            this.instances[name].reset();
    }

    add_instance(name, selector) {
        if (this.instances[name])
            return;

        const instance_data = Object.assign({ selector }, this.options);
        const factor = this.desc.factor || 1;
        const threshold = this.desc.threshold || 0;
        const last = this.last_instance;
        let metrics_row;

        function reset() {
            metrics_row = this.grid.add(this.channel, ['a', name]);
            instance_data.data = this.grid.add((row, x, n) => {
                for (let i = 0; i < n; i++) {
                    const value = (metrics_row[x + i] || 0) * factor;
                    const ts = (this.grid.beg + x + i) * this.interval;
                    let floor = 0;

                    if (last) {
                        if (last.data[x + i][1])
                            floor = last.data[x + i][1];
                        else
                            floor = last.data[x + i][2];
                    }

                    if (Math.abs(value) > threshold) {
                        row[x + i] = [ts, floor + value, floor];
                        if (row[x + i - 1] && row[x + i - 1][1] === null)
                            row[x + i - 1][1] = row[x + i - 1][2];
                    } else {
                        row[x + i] = [ts, null, floor];
                        if (row[x + i - 1] && row[x + i - 1][1] !== null)
                            row[x + i - 1][1] = row[x + i - 1][2];
                    }
                }
            });
        }

        function remove() {
            this.grid.remove(metrics_row);
            this.grid.remove(instance_data.data);
            const pos = this.plot_data.indexOf(instance_data);
            if (pos >= 0)
                this.plot_data.splice(pos, 1);
        }

        instance_data.reset = reset.bind(this);
        instance_data.remove = remove.bind(this);
        instance_data.name = name;
        this.last_instance = instance_data;
        this.instances[name] = instance_data;
        instance_data.reset();
        this.plot_data.push(instance_data);
        this.grid.sync();
    }

    clear_instances() {
        for (const i in this.instances)
            this.instances[i].remove();
        this.instances = { };
        this.last_instance = null;
    }
}

class Plot {
    constructor(element, x_range_seconds, x_stop_seconds) {
        cockpit.event_target(this);

        this.series = [];
        this.plot_data = [];

        this.interval = Math.ceil(x_range_seconds / 1000) * 1000;
        this.grid = null;

        this.sync_suppressed = 0;
        this.archives = false;

        this.reset(x_range_seconds, x_stop_seconds);
    }

    refresh() {
        this.dispatchEvent("plot", this.plot_data);
    }

    start_walking() {
        this.grid.walk();
    }

    stop_walking() {
        this.grid.move(this.grid.beg, this.grid.end);
    }

    reset(x_range_seconds, x_stop_seconds) {
        // Fill the plot with about 1000 samples, but don't sample
        // faster than once per second.
        //
        // TODO - do this based on the actual size of the plot.
        this.interval = Math.ceil(x_range_seconds / 1000) * 1000;

        const x_offset = (x_stop_seconds !== undefined)
            ? (new Date().getTime()) - x_stop_seconds * 1000
            : 0;

        const beg = -Math.ceil((x_range_seconds * 1000 + x_offset) / this.interval);
        const end = -Math.floor(x_offset / this.interval);

        if (this.grid && this.grid.interval == this.interval) {
            this.grid.move(beg, end);
        } else {
            if (this.grid)
                this.grid.close();
            this.grid = cockpit.grid(this.interval, beg, end);
            this.sync_suppressed++;
            for (let i = 0; i < this.series.length; i++) {
                this.series[i].stop();
                this.series[i].interval = this.interval;
                this.series[i].grid = this.grid;
                this.series[i].reset_series();
            }
            this.sync_suppressed--;
            this.sync();

            this.grid.addEventListener("notify", (event, index, count) => {
                this.refresh();
            });
        }
    }

    sync() {
        if (this.sync_suppressed === 0)
            this.grid.sync();
    }

    destroy() {
        this.grid.close();
        for (let i = 0; i < this.series.length; i++)
            this.series[i].stop();

        this.options = { };
        this.series = [];
        this.plot_data = [];
    }

    check_archives() {
        if (!this.archives) {
            this.archives = true;
            this.dispatchEvent('changed');
        }
    }

    add_metrics_sum_series(desc, opts) {
        const sum_series = new Metrics_sum_series(desc, opts, this.grid, this.plot_data, this.interval);

        sum_series.addEventListener("removed", this.refresh.bind(this));
        sum_series.addEventListener("changed", this.check_archives.bind(this));
        sum_series.reset_series();
        sum_series.check_archives();

        this.series.push(sum_series);
        this.sync();
        this.plot_data.push(opts);

        return sum_series;
    }

    add_metrics_stacked_instances_series(desc, opts) {
        const stacked_series = new Metrics_stacked_instances_series(desc, opts, this.grid, this.plot_data, this.interval);

        stacked_series.addEventListener("removed", this.refresh.bind(this));
        stacked_series.addEventListener("changed", this.check_archives.bind(this));
        stacked_series.reset_series();
        stacked_series.check_archives();

        this.series.push(stacked_series);
        this.sync_suppressed++;
        for (const name in stacked_series.instances)
            stacked_series.instances[name].reset();
        this.sync_suppressed--;
        this.sync();

        return stacked_series;
    }
}

class ZoomState {
    constructor(reset_callback) {
        cockpit.event_target(this);

        this.reset_callback = reset_callback;

        this.x_range = 5 * 60;
        this.x_stop = undefined;
        this.history = [];

        this.enable_zoom_in = false;
        this.enable_zoom_out = true;
        this.enable_scroll_left = true;
        this.enable_scroll_right = false;
    }

    reset() {
        const plot_min_x_range = 5 * 60;

        if (this.x_range < plot_min_x_range) {
            this.x_stop += (plot_min_x_range - this.x_range) / 2;
            this.x_range = plot_min_x_range;
        }
        if (this.x_stop >= (new Date()).getTime() / 1000 - 10)
            this.x_stop = undefined;

        this.reset_callback(this.x_range, this.x_stop);

        this.enable_zoom_in = (this.x_range > plot_min_x_range);
        this.enable_scroll_right = (this.x_stop !== undefined);

        this.dispatchEvent("changed");
    }

    set_range(x_range) {
        this.history = [];
        this.x_range = x_range;
        this.reset();
    }

    zoom_in(x_range, x_stop) {
        this.history.push(this.x_range);
        this.x_range = x_range;
        this.x_stop = x_stop;
        this.reset();
    }

    zoom_out() {
        const plot_zoom_steps = [
            5 * 60,
            60 * 60,
            6 * 60 * 60,
            24 * 60 * 60,
            7 * 24 * 60 * 60,
            30 * 24 * 60 * 60,
            365 * 24 * 60 * 60
        ];

        let r = this.history.pop();
        if (r === undefined) {
            let i;
            for (i = 0; i < plot_zoom_steps.length - 1; i++) {
                if (plot_zoom_steps[i] > this.x_range)
                    break;
            }
            r = plot_zoom_steps[i];
        }
        if (this.x_stop !== undefined)
            this.x_stop += (r - this.x_range) / 2;
        this.x_range = r;
        this.reset();
    }

    goto_now() {
        this.x_stop = undefined;
        this.reset();
    }

    scroll_left() {
        const step = this.x_range / 10;
        if (this.x_stop === undefined)
            this.x_stop = (new Date()).getTime() / 1000;
        this.x_stop -= step;
        this.reset();
    }

    scroll_right() {
        const step = this.x_range / 10;
        if (this.x_stop !== undefined) {
            this.x_stop += step;
            this.reset();
        }
    }
}

class SinglePlotState {
    constructor() {
        this._plot = new Plot(null, 300);
        this._plot.start_walking();
    }

    plot_single(metric) {
        if (this._stacked_instances_series) {
            this._stacked_instances_series.clear_instances();
            this._stacked_instances_series.remove();
            this._stacked_instances_series = null;
        }
        if (!this._sum_series) {
            this._sum_series = this._plot.add_metrics_sum_series(metric, { });
        }
    }

    plot_instances(metric, insts, reset) {
        if (this._sum_series) {
            this._sum_series.remove();
            this._sum_series = null;
        }
        if (!this._stacked_instances_series) {
            this._stacked_instances_series = this._plot.add_metrics_stacked_instances_series(metric, { });
        } else if (reset) {
            // We can't remove individual instances, only clear the
            // whole thing (because of limitations of Metrics_stacked_instances_series above).
            // So we do that, but only when there is at least one instance
            // that needs to be removed.  That avoids a lot of events and React warnings.
            if (Object.keys(this._stacked_instances_series.instances).some(old => insts.indexOf(old) == -1))
                this._stacked_instances_series.clear_instances();
        }

        for (let i = 0; i < insts.length; i++) {
            this._stacked_instances_series.add_instance(insts[i]);
        }
    }

    destroy() {
        this._plot.destroy();
    }
}

export class PlotState {
    constructor() {
        cockpit.event_target(this);
        this.plots = { };
        this.zoom_state = null;
    }

    _reset_plots(x_range, x_stop) {
        for (const id in this.plots) {
            const p = this.plots[id]._plot;
            p.stop_walking();
            p.reset(x_range, x_stop);
            p.refresh();
            if (x_stop === undefined)
                p.start_walking();
        }
    }

    _check_archives(plot) {
        if (!this.zoom_state && plot.archives) {
            this.zoom_state = new ZoomState(this._reset_plots.bind(this));
            this.dispatchEvent("changed");
        }
    }

    _get(id) {
        if (this.plots[id])
            return this.plots[id];

        const ps = new SinglePlotState();
        ps._plot.addEventListener("changed", () => this._check_archives(ps._plot));
        this._check_archives(ps._plot);
        ps._plot.addEventListener("plot", (event, data) => {
            ps.data = data;
            this.dispatchEvent("plot:" + id);
        });

        this.plots[id] = ps;
        return ps;
    }

    plot_single(id, metric) {
        const ps = this._get(id);
        ps.plot_single(metric);
    }

    plot_instances(id, metric, insts, reset) {
        this._get(id).plot_instances(metric, insts, reset);
    }

    data(id) {
        return this.plots[id] && this.plots[id].data;
    }
}
0707010000004B000081A4000000000000000000000001662A0778000003A2000000000000000000000000000000000000002D00000000cockpit-docker-devel-16/pkg/lib/polyfills.js/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
 */

/**
 This file contains various polyfills and other compatibility hacks
 */

// Don't complain about extending native data types -- that's what polyfills do
/* eslint-disable no-extend-native */
0707010000004C000081A4000000000000000000000001662A077800000470000000000000000000000000000000000000002A00000000cockpit-docker-devel-16/pkg/lib/python.js/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
 */

import cockpit from "cockpit";

const pyinvoke = ["sh", "-ec", "exec $(command -v /usr/libexec/platform-python || command -v python3) -c \"$@\"", "--"];

export function spawn (script_pieces, args, options) {
    const script = (typeof script_pieces == "string")
        ? script_pieces
        : script_pieces.join("\n");

    return cockpit.spawn(pyinvoke.concat([script]).concat(args), options);
}
0707010000004D000081A4000000000000000000000001662A0778000000B4000000000000000000000000000000000000002F00000000cockpit-docker-devel-16/pkg/lib/qunit-tap.d.tsdeclare module 'qunit-tap' {
    export default function qunitTap(qunitObject: QUnit, printLikeFunction: (message: string, ...args: unknown[]) => void, options?: unknown): void;
}
0707010000004E000081A4000000000000000000000001662A07780000053E000000000000000000000000000000000000003700000000cockpit-docker-devel-16/pkg/lib/qunit-template.html.in<!DOCTYPE html>
<!--
  This file is part of Cockpit.

  Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
-->
<html>
<head>
    <meta charset="utf-8" />
    <title><%= title %></title>
    <link rel="stylesheet" href="<%= title %>.css" type="text/css" />
    <script src="<%= builddir %>dist/base1/cockpit.js"></script>
    <script src="<%= script %>"></script>
</head>
<body class="pf-v5-m-tabular-nums">
    <h1 id="qunit-header"><%= title %></h1>
    <h2 id="qunit-banner"></h2><div id="qunit-testrunner-toolbar"></div>
    <h2 id="qunit-userAgent"></h2><ol id="qunit-tests"></ol>
    <div id="qunit-fixture">test markup, will be hidden</div>
    <div id="done-flag" style="display: none;">Done</div>
</body>
</html>
0707010000004F000081A4000000000000000000000001662A077800000D07000000000000000000000000000000000000002F00000000cockpit-docker-devel-16/pkg/lib/qunit-tests.ts/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
 */

"use strict";

import QUnit from "qunit";
import qunitTap from "qunit-tap";
import "qunit/qunit/qunit.css";

export const mock_info = async (key: string) => {
    const response = await fetch(`http://${window.location.hostname}:${window.location.port}/mock/info`);
    return (await response.json())[key];
};

// Convenience for skipping tests that the python bridge can't yet
// handle.

let is_pybridge: boolean | null = null;

export const skipWithPybridge = async (name: string, callback: (assert: unknown) => void | Promise<void>) => {
    if (is_pybridge === null)
        is_pybridge = await mock_info("pybridge");

    if (is_pybridge)
        QUnit.skip(name, callback);
    else
        QUnit.test(name, callback);
};

/* Always use explicit start */
QUnit.config.autostart = false;

let qunit_started = false;

QUnit.moduleStart(() => {
    qunit_started = true;
});

window.setTimeout(() => {
    if (!qunit_started) {
        console.log("QUnit not started by test");
        console.log("cockpittest-tap-error");
    }
}, 20000);

/* QUnit-Tap writes the summary line right after this function returns.
* Delay printing the end marker until after that summary is out.
*/
QUnit.done(() => { window.setTimeout(() => console.log("cockpittest-tap-done"), 0) });

/* Now initialize qunit-tap
 *
 * When not running under a tap driver this stuff will just show up in
 * the console. We print out a special canary at the end of the tests
 * so that the tap driver can know when the testing is done.
 *
 * In addition double check for a test file that doesn't properly call
 * QUnit.start() after its done setting up its tests.
 *
 * We also want to insert the current test name into all tap lines.
 */
const tap_regex = /^((not )?ok [0-9]+ (- )?)(.*)$/;
qunitTap(QUnit, function(message: string, ...args: unknown[]) {
    if (args.length == 0 && QUnit.config.current) {
        const match = tap_regex.exec(message);
        if (match) {
            console.log(match[1] + QUnit.config.current.testName + ": " + match[4]);
            return;
        }
    }
    console.log(message, args);
});

export function f(format: TemplateStringsArray, ...args: unknown[]) {
    const strings = [...format].reverse();
    args.reverse();

    const parts = [strings.pop()];
    if (strings.length !== args.length) {
        throw new Error('unequal strings and args in f-string');
    }

    while (args.length !== 0) {
        const arg = args.pop();
        parts.push(JSON.stringify(arg) || String(arg));
        parts.push(strings.pop());
    }

    return parts.join('');
}

export default QUnit;
07070100000050000081A4000000000000000000000001662A077800007583000000000000000000000000000000000000002E00000000cockpit-docker-devel-16/pkg/lib/serverTime.js/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
 */
import cockpit from "cockpit";
import React, { useState } from "react";
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { DatePicker } from "@patternfly/react-core/dist/esm/components/DatePicker/index.js";
import { Flex, FlexItem } from "@patternfly/react-core/dist/esm/layouts/Flex/index.js";
import { Form, FormGroup } from "@patternfly/react-core/dist/esm/components/Form/index.js";
import { Popover } from "@patternfly/react-core/dist/esm/components/Popover/index.js";
import { Select, SelectOption } from "@patternfly/react-core/dist/esm/deprecated/components/Select/index.js";
import { Spinner } from "@patternfly/react-core/dist/esm/components/Spinner/index.js";
import { TimePicker } from "@patternfly/react-core/dist/esm/components/TimePicker/index.js";
import { TextInput } from "@patternfly/react-core/dist/esm/components/TextInput/index.js";
import { CloseIcon, ExclamationCircleIcon, InfoCircleIcon, PlusIcon } from "@patternfly/react-icons";
import { show_modal_dialog } from "cockpit-components-dialog.jsx";
import { useObject, useEvent } from "hooks.js";

import * as service from "service.js";
import * as timeformat from "timeformat.js";
import * as python from "python.js";
import get_timesync_backend_py from "./get-timesync-backend.py";

import { superuser } from "superuser.js";

import "serverTime.scss";

const _ = cockpit.gettext;

export function ServerTime() {
    const self = this;
    cockpit.event_target(self);

    function emit_changed() {
        self.dispatchEvent("changed");
    }

    let time_offset = null;
    let remote_offset = null;

    let client = null;
    let timedate = null;

    function connect() {
        if (client) {
            timedate.removeEventListener("changed", emit_changed);
            client.close();
        }
        client = cockpit.dbus('org.freedesktop.timedate1', { superuser: "try" });
        timedate = client.proxy();
        timedate.addEventListener("changed", emit_changed);
        client.subscribe({
            interface: "org.freedesktop.DBus.Properties",
            member: "PropertiesChanged"
        }, ntp_updated);
    }

    const timedate1_service = service.proxy("dbus-org.freedesktop.timedate1.service");
    const timesyncd_service = service.proxy("systemd-timesyncd.service");
    const chronyd_service = service.proxy("chronyd.service");

    timesyncd_service.addEventListener("changed", emit_changed);
    chronyd_service.addEventListener("changed", emit_changed);

    /*
     * The time we return from here as its UTC time set to the
     * server time. This is the only way to get predictable
     * behavior.
     */
    Object.defineProperty(self, 'utc_fake_now', {
        enumerable: true,
        get: function get() {
            const offset = time_offset + remote_offset;
            return new Date(offset + (new Date()).valueOf());
        }
    });

    Object.defineProperty(self, 'now', {
        enumerable: true,
        get: function get() {
            return new Date(time_offset + (new Date()).valueOf());
        }
    });

    self.format = function format(and_time) {
        const options = { dateStyle: "medium", timeStyle: and_time ? "short" : undefined, timeZone: "UTC" };
        return timeformat.formatter(options).format(self.utc_fake_now);
    };

    const updateInterval = window.setInterval(emit_changed, 30000);

    self.wait = function wait() {
        if (remote_offset === null)
            return self.update();
        return Promise.resolve();
    };

    self.update = function update() {
        return cockpit.spawn(["date", "+%s:%z"], { err: "message" })
                .then(data => {
                    const parts = data.trim().split(":");
                    const timems = parseInt(parts[0], 10) * 1000;
                    let tzmin = parseInt(parts[1].slice(-2), 10);
                    const tzhour = parseInt(parts[1].slice(0, -2));
                    if (tzhour < 0)
                        tzmin = -tzmin;
                    const offsetms = (tzhour * 3600000) + tzmin * 60000;
                    const now = new Date();
                    time_offset = (timems - now.valueOf());
                    remote_offset = offsetms;
                    emit_changed();
                })
                .catch(ex => console.log("Couldn't calculate server time offset: " + cockpit.message(ex)));
    };

    /* There is no way to make sense of this date without a round trip to the
     * server, as the timezone is really server specific. */
    self.change_time = (datestr, timestr) => cockpit.spawn(["date", "--date=" + datestr + " " + timestr, "+%s"])
            .then(data => {
                const seconds = parseInt(data.trim(), 10);
                return timedate.call('SetTime', [seconds * 1000 * 1000, false, true])
                        .then(self.update);
            });

    self.bump_time = function (millis) {
        return timedate.call('SetTime', [millis, true, true]);
    };

    self.get_time_zone = function () {
        return timedate.Timezone;
    };

    self.set_time_zone = function (tz) {
        return timedate.call('SetTimezone', [tz, true]);
    };

    self.poll_ntp_synchronized = () => client.call(
        timedate.path, "org.freedesktop.DBus.Properties", "Get", ["org.freedesktop.timedate1", "NTPSynchronized"])
            .then(result => {
                const ifaces = { "org.freedesktop.timedate1": { NTPSynchronized: result[0].v } };
                const data = { };
                data[timedate.path] = ifaces;
                client.notify(data);
            })
            .catch(error => {
                if (error.name != "org.freedesktop.DBus.Error.UnknownProperty" &&
                        error.problem != "not-found")
                    console.log("can't get NTPSynchronized property", error);
            });

    let ntp_waiting_value = null;
    let ntp_waiting_resolve = null;

    function ntp_updated(path, iface, member, args) {
        if (!ntp_waiting_resolve || !args[1].NTP)
            return;
        if (ntp_waiting_value !== args[1].NTP.v)
            console.warn("Unexpected value of NTP");
        ntp_waiting_resolve();
        ntp_waiting_resolve = null;
    }

    self.set_ntp = function set_ntp(val) {
        const promise = new Promise((resolve, reject) => {
            ntp_waiting_resolve = resolve;
        });
        ntp_waiting_value = val;
        client.call(timedate.path,
                    "org.freedesktop.DBus.Properties", "Get", ["org.freedesktop.timedate1", "NTP"])
                .then(result => {
                // Check if don't want to enable enabled or disable disabled
                    if (result[0].v === val) {
                        ntp_waiting_resolve();
                        ntp_waiting_resolve = null;
                        return;
                    }
                    timedate.call('SetNTP', [val, true])
                            .catch(e => {
                                ntp_waiting_resolve();
                                ntp_waiting_resolve = null;
                                console.error("Failed to call SetNTP:", e.message); // not-covered: OS error
                            });
                });
        return promise;
    };

    self.get_ntp_active = function () {
        return timedate.NTP;
    };

    self.get_ntp_supported = function () {
        return timedate.CanNTP;
    };

    self.get_ntp_status = function () {
        const status = {
            initialized: false,
            active: false,
            synch: false,
            service: null,
            server: null,
            sub_status: null
        };

        // flag for tests that timedated/timesyncd proxies got initialized
        if (timedate.CanNTP !== undefined &&
            timedate1_service.unit && timedate1_service.unit.Id &&
            timesyncd_service.exists !== null &&
            chronyd_service.exists !== null)
            status.initialized = true;

        status.active = timedate.NTP;
        status.synch = timedate.NTPSynchronized;

        const timesyncd_server_regex = /.*time server (.*)\./i;

        const timesyncd_status = (timesyncd_service.state == "running" &&
                                timesyncd_service.service?.StatusText);

        if (timesyncd_service.state == "running")
            status.service = "systemd-timesyncd.service";
        else if (chronyd_service.state == "running")
            status.service = "chronyd.service";

        if (timesyncd_status) {
            const match = timesyncd_status.match(timesyncd_server_regex);
            if (match)
                status.server = match[1];
            else if (timesyncd_status != "Idle." && timesyncd_status !== "")
                status.sub_status = timesyncd_status;
        }

        return status;
    };

    function get_timesync_backend() {
        return python.spawn(get_timesync_backend_py, [], { superuser: "try", err: "message" })
                .then(data => {
                    const unit = data.trim();
                    if (unit == "systemd-timesyncd.service")
                        return "timesyncd";
                    else if (unit == "chrony.service" || unit == "chronyd.service")
                        return "chronyd";
                    else
                        return null;
                });
    }

    function get_custom_ntp_timesyncd() {
        const custom_ntp_config_file = cockpit.file("/etc/systemd/timesyncd.conf.d/50-cockpit.conf",
                                                    { superuser: "try" });

        const result = {
            backend: "timesyncd",
            enabled: false,
            servers: []
        };

        return custom_ntp_config_file.read()
                .then(function(text) {
                    let ntp_line = "";
                    if (text) {
                        result.enabled = true;
                        text.split("\n").forEach(function(line) {
                            if (line.indexOf("NTP=") === 0) {
                                ntp_line = line.slice(4);
                                result.enabled = true;
                            } else if (line.indexOf("#NTP=") === 0) {
                                ntp_line = line.slice(5);
                                result.enabled = false;
                            }
                        });

                        result.servers = ntp_line.split(" ").filter(function(val) {
                            return val !== "";
                        });
                        if (result.servers.length === 0)
                            result.enabled = false;
                    }
                    return result;
                })
                .catch(function(error) {
                    console.warn("failed to load time servers", error);
                    return result;
                });
    }

    function set_custom_ntp_timesyncd(config) {
        const custom_ntp_config_file = cockpit.file("/etc/systemd/timesyncd.conf.d/50-cockpit.conf",
                                                    { superuser: "require" });

        const text = `# This file is automatically generated by Cockpit\n\n[Time]\n${config.enabled ? "" : "#"}NTP=${config.servers.join(" ")}\n`;

        return cockpit.spawn(["mkdir", "-p", "/etc/systemd/timesyncd.conf.d"], { superuser: "require" })
                .then(() => custom_ntp_config_file.replace(text));
    }

    const chronyd_sourcedir = "/etc/chrony/sources.d";
    const chronyd_sources_enabled = chronyd_sourcedir + "/cockpit.sources";
    const chronyd_sources_disabled = chronyd_sourcedir + "/cockpit.disabled";

    function get_custom_ntp_chronyd() {
        const enabled_file = cockpit.file(chronyd_sources_enabled, { superuser: "try" });
        const disabled_file = cockpit.file(chronyd_sources_disabled, { superuser: "try" });

        function parse_servers(data) {
            if (!data)
                return [];
            const servers = [];
            data.split("\n").forEach(function(line) {
                const parts = line.split(" ");
                if (parts[0] == "server")
                    servers.push(parts[1]);
            });
            return servers;
        }

        return enabled_file.read()
                .then(data => {
                    if (data) {
                        return {
                            backend: "chronyd",
                            enabled: true,
                            servers: parse_servers(data)
                        };
                    } else {
                        return disabled_file.read()
                                .then(data => {
                                    return {
                                        backend: "chronyd",
                                        enabled: false,
                                        servers: parse_servers(data)
                                    };
                                });
                    }
                });
    }

    function set_custom_ntp_chronyd(config) {
        const enabled_file = cockpit.file(chronyd_sources_enabled, { superuser: "require" });
        const disabled_file = cockpit.file(chronyd_sources_disabled, { superuser: "require" });

        const text = "# This file is automatically generated by Cockpit\n\n" + config.servers.map(s => `server ${s}\n`).join("");

        // HACK - https://bugzilla.redhat.com/show_bug.cgi?id=2168863
        function ensure_sourcedir() {
            function add_sourcedir(data) {
                const line = "sourcedir " + chronyd_sourcedir;
                if (data && data.indexOf(line) == -1)
                    data += "\n# Added by Cockpit\n" + line + "\n";
                return data;
            }
            return cockpit.file("/etc/chrony.conf", { superuser: "require" }).modify(add_sourcedir);
        }

        return cockpit.spawn(["mkdir", "-p", chronyd_sourcedir], { superuser: "require" })
                .then(() => {
                    if (config.enabled)
                        return enabled_file.replace(text).then(() => disabled_file.replace(null)).then(ensure_sourcedir);
                    else
                        return disabled_file.replace(text).then(() => enabled_file.replace(null));
                });
    }

    self.get_custom_ntp = function () {
        return get_timesync_backend().then(backend => {
            if (backend == "timesyncd") {
                return get_custom_ntp_timesyncd();
            } else if (backend == "chronyd") {
                return get_custom_ntp_chronyd();
            } else {
                return Promise.resolve({ backend: null, servers: [], enabled: false });
            }
        });
    };

    self.set_custom_ntp = function (config) {
        if (config.backend == "timesyncd") {
            return set_custom_ntp_timesyncd(config);
        } else if (config.backend == "chronyd") {
            return set_custom_ntp_chronyd(config);
        } else {
            return Promise.resolve();
        }
    };

    self.get_timezones = function() {
        return cockpit.spawn(["/usr/bin/timedatectl", "list-timezones"])
                .then(content => content.split('\n').filter(tz => tz != ""));
    };

    /* NTPSynchronized needs to be polled so we just do that
     * always.
     */

    const ntp_poll_interval = window.setInterval(function() {
        self.poll_ntp_synchronized();
    }, 5000);

    self.close = function close() {
        window.clearInterval(updateInterval);
        window.clearInterval(ntp_poll_interval);
        client.close();
    };

    connect();
    superuser.addEventListener("reconnect", connect);
    self.update();
}

export function ServerTimeConfig() {
    const server_time = useObject(() => new ServerTime(),
                                  st => st.close(),
                                  []);
    useEvent(server_time, "changed");

    const ntp = server_time.get_ntp_status();

    const tz = server_time.get_time_zone();
    const systime_button = (
        <Button variant="link" id="system_information_systime_button"
                onClick={ () => change_systime_dialog(server_time, tz) }
                data-timedated-initialized={ntp?.initialized}
                isInline isDisabled={!superuser.allowed || !tz}>
            { server_time.format(true) }
        </Button>);

    let ntp_status = null;
    if (ntp?.active) {
        let icon; let header; let body = ""; let footer = null;
        if (ntp.synch) {
            icon = <InfoCircleIcon className="ct-info-circle" />;
            header = _("Synchronized");
            if (ntp.server)
                body = <div>{cockpit.format(_("Synchronized with $0"), ntp.server)}</div>;
        } else {
            if (ntp.server) {
                icon = <Spinner size="md" />;
                header = _("Synchronizing");
                body = <div>{cockpit.format(_("Trying to synchronize with $0"), ntp.server)}</div>;
            } else {
                icon = <ExclamationCircleIcon className="ct-exclamation-circle" />;
                header = _("Not synchronized");
                if (ntp.service) {
                    footer = (
                        <Button variant="link"
                                onClick={() => cockpit.jump("/system/services#/" +
                                                            encodeURIComponent(ntp.service))}>
                            {_("Log messages")}
                        </Button>);
                }
            }
        }

        if (ntp.sub_status) {
            body = <>{body}<div>{ntp.sub_status}</div></>;
        }

        ntp_status = (
            <Popover headerContent={header} bodyContent={body} footerContent={footer}>
                {icon}
            </Popover>);
    }

    return (
        <Flex spaceItems={{ default: 'spaceItemsSm' }} alignItems={{ default: 'alignItemsCenter' }}>
            {systime_button}
            {ntp_status}
        </Flex>
    );
}

function Validated({ errors, error_key, children }) {
    const error = errors?.[error_key];
    // We need to always render the <div> for the has-error
    // class so that the input field keeps the focus when
    // errors are cleared.  Otherwise the DOM changes enough
    // for the Browser to remove focus.
    return (
        <div className={error ? "ct-validation-wrapper has-error" : "ct-validation-wrapper"}>
            { children }
            { error ? <span className="help-block dialog-error">{error}</span> : null }
        </div>
    );
}

function ValidatedInput({ errors, error_key, children }) {
    const error = errors?.[error_key];
    return (
        <span className={error ? "ct-validation-wrapper has-error" : "ct-validation-wrapper"}>
            { children }
        </span>
    );
}

function ChangeSystimeBody({ state, errors, change }) {
    const [zonesOpen, setZonesOpen] = useState(false);
    const [modeOpen, setModeOpen] = useState(false);

    const {
        time_zone, time_zones,
        mode,
        manual_date, manual_time,
        ntp_supported, custom_ntp
    } = state;

    function add_server(event, index) {
        custom_ntp.servers.splice(index + 1, 0, "");
        change("custom_ntp", custom_ntp);
        event.stopPropagation();
        event.preventDefault();
        return false;
    }

    function remove_server(event, index) {
        custom_ntp.servers.splice(index, 1);
        change("custom_ntp", custom_ntp);
        event.stopPropagation();
        event.preventDefault();
        return false;
    }

    function change_server(event, index, value) {
        custom_ntp.servers[index] = value;
        change("custom_ntp", custom_ntp);
        event.stopPropagation();
        event.preventDefault();
        return false;
    }

    const ntp_servers = (
        custom_ntp.servers.map((s, i) => (
            <Flex className="ntp-server-input-group" spaceItems={{ default: 'spaceItemsSm' }} key={i}>
                <FlexItem grow={{ default: 'grow' }}>
                    <TextInput value={s} placeholder={_("NTP server")} aria-label={_("NTP server")}
                               onChange={(event, value) => change_server(event, i, value)} />
                </FlexItem>
                <Button variant="secondary" onClick={event => add_server(event, i)}
                        icon={ <PlusIcon /> } />
                <Button variant="secondary" onClick={event => remove_server(event, i)}
                        icon={ <CloseIcon /> } isDisabled={i === custom_ntp.servers.length - 1} />
            </Flex>
        ))
    );

    const mode_options = [
        <SelectOption key="manual_time" value="manual_time">{_("Manually")}</SelectOption>,
        <SelectOption key="ntp_time" value="ntp_time" isDisabled={!ntp_supported}>{_("Automatically using NTP")}</SelectOption>
    ];

    if (custom_ntp.backend)
        mode_options.push(
            <SelectOption key="ntp_time_custom" value="ntp_time_custom" isDisabled={!ntp_supported}>
                { custom_ntp.backend == "chronyd"
                    ? _("Automatically using additional NTP servers")
                    : _("Automatically using specific NTP servers")
                }
            </SelectOption>);

    return (
        <Form isHorizontal>
            <FormGroup fieldId="systime-timezones" label={_("Time zone")}>
                <Validated errors={errors} error_key="time_zone">
                    <Select id="systime-timezones" variant="typeahead"
                            isOpen={zonesOpen} onToggle={(_, isOpen) => setZonesOpen(isOpen)}
                            selections={time_zone}
                            onSelect={(event, value) => { setZonesOpen(false); change("time_zone", value) }}
                            menuAppendTo="parent">
                        { time_zones.map(tz => <SelectOption key={tz} value={tz}>{tz.replaceAll("_", " ")}</SelectOption>) }
                    </Select>
                </Validated>
            </FormGroup>
            <FormGroup fieldId="change_systime" label={_("Set time")} isStack>
                <Select id="change_systime"
                        isOpen={modeOpen} onToggle={(_, isOpen) => setModeOpen(isOpen)}
                        selections={mode} onSelect={(event, value) => { setModeOpen(false); change("mode", value) }}
                        menuAppendTo="parent">
                    { mode_options }
                </Select>
                { mode == "manual_time" &&
                    <Flex spaceItems={{ default: 'spaceItemsSm' }} id="systime-manual-row">
                        <ValidatedInput errors={errors} error_key="manual_date">
                            <DatePicker id="systime-date-input"
                                        aria-label={_("Pick date")}
                                        buttonAriaLabel={_("Toggle date picker")}
                                        dateFormat={timeformat.dateShort}
                                        dateParse={timeformat.parseShortDate}
                                        invalidFormatText=""
                                        locale={timeformat.dateFormatLang()}
                                        weekStart={timeformat.firstDayOfWeek()}
                                        placeholder={timeformat.dateShortFormat()}
                                        onChange={(_, d) => change("manual_date", d)}
                                        value={manual_date}
                                        appendTo={() => document.body} />
                        </ValidatedInput>
                        <ValidatedInput errors={errors} error_key="manual_time">
                            <TimePicker id="systime-time-input"
                                        className="ct-serverTime-time-picker"
                                        time={manual_time}
                                        is24Hour
                                        menuAppendTo={() => document.body}
                                        invalidFormatErrorMessage=""
                                        onChange={(e, time, h, m, s, valid) => change("manual_time", time, valid) } />
                        </ValidatedInput>
                        <Validated errors={errors} error_key="manual_date" />
                        <Validated errors={errors} error_key="manual_time" />
                    </Flex>
                }
                { mode == "ntp_time_custom" &&
                    <Validated errors={errors} error_key="ntp_servers">
                        <div id="systime-ntp-servers">
                            { ntp_servers }
                        </div>
                    </Validated>
                }
            </FormGroup>
        </Form>
    );
}

function has_errors(errors) {
    for (const field in errors) {
        if (errors[field])
            return true;
    }
    return false;
}

function change_systime_dialog(server_time, timezone) {
    let dlg = null;
    const state = {
        time_zone: timezone,
        time_zones: null,
        mode: null,
        ntp_supported: server_time.get_ntp_supported(),
        custom_ntp: null,
        manual_time_valid: true,
    };
    let errors = { };

    function get_current_time() {
        state.manual_date = server_time.format();

        const minutes = server_time.utc_fake_now.getUTCMinutes();
        // normalize to two digits
        const minutes_str = (minutes < 10) ? "0" + minutes.toString() : minutes.toString();
        state.manual_time = `${server_time.utc_fake_now.getUTCHours()}:${minutes_str}`;
    }

    function change(field, value, isValid) {
        state[field] = value;
        errors = { };

        if (field == "mode" && value == "manual_time")
            get_current_time();

        if (field == "manual_time")
            state.manual_time_valid = value && isValid;

        update();
    }

    function validate() {
        errors = { };

        if (state.time_zone == "")
            errors.time_zone = _("Invalid timezone");

        if (state.mode == "manual_time") {
            const new_date = new Date(state.manual_date);
            if (isNaN(new_date.getTime()) || new_date.getTime() < 0)
                errors.manual_date = _("Invalid date format");

            if (!state.manual_time_valid)
                errors.manual_time = _("Invalid time format");
        }

        if (state.mode == "ntp_time_custom") {
            if (state.custom_ntp.servers.filter(s => !!s).length == 0)
                errors.ntp_servers = _("Need at least one NTP server");
        }

        return !has_errors(errors);
    }

    function apply() {
        return server_time.set_time_zone(state.time_zone)
                .then(() => {
                    if (state.mode == "manual_time") {
                        return server_time.set_ntp(false)
                                .then(() => server_time.change_time(state.manual_date,
                                                                    state.manual_time));
                    } else {
                        // Switch off NTP, write the config file, and switch NTP back on
                        state.custom_ntp.enabled = (state.mode == "ntp_time_custom");
                        state.custom_ntp.servers = state.custom_ntp.servers.filter(s => !!s);
                        return server_time.set_ntp(false)
                                .then(() => server_time.set_custom_ntp(state.custom_ntp))
                                .then(() => server_time.set_ntp(true));
                    }
                });
    }

    function update() {
        const props = {
            id: "system_information_change_systime",
            title: _("Change system time"),
            body: <ChangeSystimeBody state={state} errors={errors} change={change} />
        };

        const footer = {
            actions: [
                {
                    caption: _("Change"),
                    style: "primary",
                    clicked: () => {
                        if (validate()) {
                            return apply();
                        } else {
                            update();
                            return Promise.reject();
                        }
                    }
                }
            ]
        };

        if (!dlg)
            dlg = show_modal_dialog(props, footer);
        else {
            dlg.setProps(props);
            dlg.setFooterProps(footer);
        }
    }

    Promise.all([server_time.get_custom_ntp(), server_time.get_timezones()])
            .then(([custom_ntp, time_zones]) => {
                if (custom_ntp.servers.length == 0)
                    custom_ntp.servers = [""];
                state.custom_ntp = custom_ntp;
                state.time_zones = time_zones;
                if (server_time.get_ntp_active()) {
                    if (custom_ntp.enabled)
                        state.mode = "ntp_time_custom";
                    else
                        state.mode = "ntp_time";
                } else {
                    state.mode = "manual_time";
                    get_current_time();
                }
                update();
            });
}
07070100000051000081A4000000000000000000000001662A07780000009B000000000000000000000000000000000000003000000000cockpit-docker-devel-16/pkg/lib/serverTime.scss.ct-serverTime-time-picker {
  max-inline-size: 7rem;
}

.ntp-server-input-group:not(:last-child) {
  margin-block-end: var(--pf-v5-global--spacer--sm);
}
07070100000052000081A4000000000000000000000001662A077800002C25000000000000000000000000000000000000002B00000000cockpit-docker-devel-16/pkg/lib/service.jsimport cockpit from "cockpit";

/* SERVICE MANAGEMENT API
 *
 * The "service" module lets you monitor and manage a
 * system service on localhost in a simple way.
 *
 * It mainly exists because talking to the systemd D-Bus API is
 * not trivial enough to do it directly.
 *
 * - proxy = service.proxy(name)
 *
 * Create a proxy that represents the service named NAME.
 *
 * The proxy has properties and methods (described below) that
 * allow you to monitor the state of the service, and perform
 * simple actions on it.
 *
 * Initially, any of the properties can be "null" until their
 * actual values have been retrieved in the background.
 *
 * - proxy.addEventListener('changed', event => { ... })
 *
 * The 'changed' event is emitted whenever one of the properties
 * of the proxy changes.
 *
 * - proxy.exists
 *
 * A boolean that tells whether the service is known or not.  A
 * proxy with 'exists == false' will have 'state == undefined' and
 * 'enabled == undefined'.
 *
 * - proxy.state
 *
 * Either 'undefined' when the state can't be retrieved, or a
 * string that has one of the values "starting", "running",
 * "stopping", "stopped", or "failed".
 *
 * - proxy.enabled
 *
 * Either 'undefined' when the value can't be retrieved, or a
 * boolean that tells whether the service is started 'enabled'.
 * What it means exactly for a service to be enabled depends on
 * the service, but an enabled service is usually started on boot,
 * no matter whether other services need it or not.  A disabled
 * service is usually only started when it is needed by some other
 * service.
 *
 * - proxy.unit
 * - proxy.details
 *
 * The raw org.freedesktop.systemd1.Unit and type-specific D-Bus
 * interface proxies for the service.
 *
 * - proxy.service
 *
 * The deprecated name for proxy.details
 *
 * - promise = proxy.start()
 *
 * Start the service.  The return value is a standard jQuery
 * promise as returned from DBusClient.call.
 *
 * - promise =  proxy.restart()
 *
 * Restart the service.
 *
 * - promise = proxy.tryRestart()
 *
 * Try to restart the service if it's running or starting
 *
 * - promise = proxy.stop()
 *
 * Stop the service.
 *
 * - promise = proxy.enable()
 *
 * Enable the service.
 *
 * - promise = proxy.disable()
 *
 * Disable the service.
 *
 * - journal = proxy.getRunJournal(options)
 *
 * Return the journal of the current (if running) or recent (if failed/stopped) service run,
 * similar to `systemctl status`. `options` is an optional array that gets appended to the `journalctl` call.
 */

let systemd_client;
let systemd_manager;

function wait_valid(proxy, callback) {
    proxy.wait(() => {
        if (proxy.valid)
            callback();
    });
}

function with_systemd_manager(done) {
    if (!systemd_manager) {
        // cached forever, only used for reading/watching; no superuser
        systemd_client = cockpit.dbus("org.freedesktop.systemd1");
        systemd_manager = systemd_client.proxy("org.freedesktop.systemd1.Manager",
                                               "/org/freedesktop/systemd1");
        wait_valid(systemd_manager, () => {
            systemd_manager.Subscribe()
                    .catch(error => {
                        if (error.name != "org.freedesktop.systemd1.AlreadySubscribed" &&
                        error.name != "org.freedesktop.DBus.Error.FileExists")
                            console.warn("Subscribing to systemd signals failed", error);
                    });
        });
    }
    wait_valid(systemd_manager, done);
}

export function proxy(name, kind) {
    const self = {
        exists: null,
        state: null,
        enabled: null,

        wait,

        start,
        stop,
        restart,
        tryRestart,

        enable,
        disable,

        getRunJournal,
    };

    cockpit.event_target(self);

    let unit, details;
    let wait_promise_resolve;
    const wait_promise = new Promise(resolve => { wait_promise_resolve = resolve });

    if (name.indexOf(".") == -1)
        name = name + ".service";
    if (kind === undefined)
        kind = "Service";

    function update_from_unit() {
        self.exists = (unit.LoadState != "not-found" || unit.ActiveState != "inactive");

        if (unit.ActiveState == "activating")
            self.state = "starting";
        else if (unit.ActiveState == "deactivating")
            self.state = "stopping";
        else if (unit.ActiveState == "active" || unit.ActiveState == "reloading")
            self.state = "running";
        else if (unit.ActiveState == "failed")
            self.state = "failed";
        else if (unit.ActiveState == "inactive" && self.exists)
            self.state = "stopped";
        else
            self.state = undefined;

        if (unit.UnitFileState == "enabled" || unit.UnitFileState == "linked")
            self.enabled = true;
        else if (unit.UnitFileState == "disabled" || unit.UnitFileState == "masked")
            self.enabled = false;
        else
            self.enabled = undefined;

        self.unit = unit;

        self.dispatchEvent("changed");
        wait_promise_resolve();
    }

    function update_from_details() {
        self.details = details;
        self.service = details;
        self.dispatchEvent("changed");
    }

    with_systemd_manager(function () {
        systemd_manager.LoadUnit(name)
                .then(path => {
                    unit = systemd_client.proxy('org.freedesktop.systemd1.Unit', path);
                    unit.addEventListener('changed', update_from_unit);
                    wait_valid(unit, update_from_unit);

                    details = systemd_client.proxy('org.freedesktop.systemd1.' + kind, path);
                    details.addEventListener('changed', update_from_details);
                    wait_valid(details, update_from_details);
                })
                .catch(() => {
                    self.exists = false;
                    self.dispatchEvent('changed');
                });
    });

    function refresh() {
        if (!unit || !details)
            return Promise.resolve();

        function refresh_interface(path, iface) {
            return systemd_client.call(path, "org.freedesktop.DBus.Properties", "GetAll", [iface])
                    .then(([result]) => {
                        const props = { };
                        for (const p in result)
                            props[p] = result[p].v;
                        systemd_client.notify({ [unit.path]: { [iface]: props } });
                    })
                    .catch(error => console.log(error));
        }

        return Promise.allSettled([
            refresh_interface(unit.path, "org.freedesktop.systemd1.Unit"),
            refresh_interface(details.path, "org.freedesktop.systemd1." + kind),
        ]);
    }

    function on_job_new_removed_refresh(event, number, path, unit_id, result) {
        if (unit_id == name)
            refresh();
    }

    /* HACK - https://bugs.freedesktop.org/show_bug.cgi?id=69575
     *
     * We need to explicitly get new property values when getting
     * a UnitNew signal since UnitNew doesn't carry them.
     * However, reacting to UnitNew with GetAll could lead to an
     * infinite loop since systemd emits a UnitNew in reaction to
     * GetAll for units that it doesn't want to keep loaded, such
     * as units without unit files.
     *
     * So we ignore UnitNew and instead assume that the unit state
     * only changes in interesting ways when there is a job for it
     * or when the daemon is reloaded (or when we get a property
     * change notification, of course).
     */

    // This is what we want to do:
    // systemd_manager.addEventListener("UnitNew", function (event, unit_id, path) {
    //     if (unit_id == name)
    //         refresh();
    // });

    // This is what we have to do:
    systemd_manager.addEventListener("Reloading", (event, reloading) => {
        if (!reloading)
            refresh();
    });

    systemd_manager.addEventListener("JobNew", on_job_new_removed_refresh);
    systemd_manager.addEventListener("JobRemoved", on_job_new_removed_refresh);

    function wait(callback) {
        wait_promise.then(callback);
    }

    /* Actions
     *
     * We don't call methods on the persistent systemd_client, as that does not have superuser
     */

    function call_manager(dbus, method, args) {
        return dbus.call("/org/freedesktop/systemd1",
                         "org.freedesktop.systemd1.Manager",
                         method, args);
    }

    function call_manager_with_job(method, args) {
        return new Promise((resolve, reject) => {
            const dbus = cockpit.dbus("org.freedesktop.systemd1", { superuser: "try" });
            let pending_job_path;

            const subscription = dbus.subscribe(
                { interface: "org.freedesktop.systemd1.Manager", member: "JobRemoved" },
                (_path, _iface, _signal, [_number, path, _unit_id, result]) => {
                    if (path == pending_job_path) {
                        subscription.remove();
                        dbus.close();
                        refresh().then(() => {
                            if (result === "done")
                                resolve();
                            else
                                reject(new Error(`systemd job ${method} ${JSON.stringify(args)} failed with result ${result}`));
                        });
                    }
                });

            call_manager(dbus, method, args)
                    .then(([path]) => { pending_job_path = path })
                    .catch(() => {
                        dbus.close();
                        reject();
                    });
        });
    }

    function call_manager_with_reload(method, args) {
        const dbus = cockpit.dbus("org.freedesktop.systemd1", { superuser: "try" });
        return call_manager(dbus, method, args)
                .then(() => call_manager(dbus, "Reload", []))
                .then(refresh)
                .finally(dbus.close);
    }

    function start() {
        return call_manager_with_job("StartUnit", [name, "replace"]);
    }

    function stop() {
        return call_manager_with_job("StopUnit", [name, "replace"]);
    }

    function restart() {
        return call_manager_with_job("RestartUnit", [name, "replace"]);
    }

    function tryRestart() {
        return call_manager_with_job("TryRestartUnit", [name, "replace"]);
    }

    function enable() {
        return call_manager_with_reload("EnableUnitFiles", [[name], false, false]);
    }

    function disable() {
        return call_manager_with_reload("DisableUnitFiles", [[name], false]);
    }

    function getRunJournal(options) {
        if (!details || !details.ExecMainStartTimestamp)
            return Promise.reject(new Error("getRunJournal(): unit is not known"));

        // collect the service journal since start time; property is μs, journal wants s
        const startTime = Math.floor(details.ExecMainStartTimestamp / 1000000);
        return cockpit.spawn(
            ["journalctl", "--unit", name, "--since=@" + startTime.toString()].concat(options || []),
            { superuser: "try", error: "message" });
    }

    return self;
}
07070100000053000081A4000000000000000000000001662A0778000010B5000000000000000000000000000000000000002D00000000cockpit-docker-devel-16/pkg/lib/superuser.js/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
 */

import cockpit from "cockpit";

/* import { superuser } from "superuser";
 *
 * The "superuser" object indicates whether or not the current page
 * can open superuser channels.
 *
 * - superuser.allowed
 *
 * This is true when the page can open superuser channels, and false
 * otherwise. This field might be "null" while the page or the Cockpit
 * session itself is still initializing.
 *
 * UI elements that trigger actions that need administrative access
 * should be hidden when the "allowed" field is false or null.  (If
 * those elements also show information, such as with checkboxes or
 * toggle buttons, disable them instead of hiding.)
 *
 * UI elements that alert the user that they don't have administrative
 * access should be shown when the "allowed" field is exactly false,
 * but not when it is null.
 *
 * - superuser.addEventListener("changed", () => ...)
 *
 * The event handler is called whenever superuser.allowed has changed.
 * A page should update its appearance according to superuser.allowed.
 *
 * - superuser.addEventListener("reconnect", () => ...)
 *
 * The event handler is called whenever channels should be re-opened
 * that use the "superuser" option.
 *
 * The difference between "reconnect" and "connect" is that the
 * "reconnect" signal does not trigger when superuser.allowed goes
 * from "null" to its first real value.  You don't need to re-open
 * channels in this case, and it happens on every page load, so this
 * is important to avoid.
 *
 * - superuser.reload_page_on_change()
 *
 * Calling this function instructs the "superuser" object to reload
 * the page whenever "superuser.allowed" changes. This is a (bad)
 * alternative to re-initializing the page and intended to be used
 * only to help with the transition.
 *
 * Even if you are using "superuser.reload_page_on_change" to avoid having
 * to re-initialize your page dynamically, you should still use the
 * "changed" event to update the page appearance since
 * "superuser.allowed" might still change a couple of times right
 * after page reload.
 */

function Superuser() {
    const proxy = cockpit.dbus(null, { bus: "internal" }).proxy("cockpit.Superuser", "/superuser");
    let reload_on_change = false;

    const compute_allowed = () => {
        if (!proxy.valid || proxy.Current == "init")
            return null;
        return proxy.Current != "none";
    };

    const self = {
        allowed: compute_allowed(),
        reload_page_on_change
    };

    cockpit.event_target(self);

    function changed(allowed) {
        if (self.allowed != allowed) {
            if (self.allowed != null && reload_on_change) {
                window.location.reload(true);
            } else {
                const prev = self.allowed;
                self.allowed = allowed;
                self.dispatchEvent("changed");
                if (prev != null)
                    self.dispatchEvent("reconnect");
            }
        }
    }

    proxy.wait(() => {
        if (!proxy.valid) {
            // Fall back to cockpit.permissions
            const permission = cockpit.permission({ admin: true });
            const update = () => {
                changed(permission.allowed);
            };
            permission.addEventListener("changed", update);
            update();
        }
    });

    proxy.addEventListener("changed", () => {
        changed(compute_allowed());
    });

    function reload_page_on_change() {
        reload_on_change = true;
    }

    return self;
}

export const superuser = Superuser();
07070100000054000081A4000000000000000000000001662A077800000E0A000000000000000000000000000000000000002A00000000cockpit-docker-devel-16/pkg/lib/table.css.panel .table {
  font-size: var(--pf-v5-global--FontSize-s);
}

/* Panels don't draw borders between them */
.panel > .table > tbody:first-child td {
  border-block-start: 1px solid rgb(221 221 221);
}

/* Table headers should not generate a double border */
.panel .table thead tr th {
  border-block-end: none;
}

/* Fix panel heading alignment & mobile layout */

.panel-heading {
  align-items: center;
  background: #f5f5f5;
  display: flex;
  flex-wrap: wrap;
  /* (28px small size widget height) + (0.5rem * 2) */
  min-block-size: calc(28px + 1rem);
  padding-block: 0.5rem;
  padding-inline: 1rem;
  position: relative;
  z-index: 100;
}

.panel-title {
  font: inherit;
  margin: 0;
  padding: 0;
}

.panel-title > a {
  color: var(--ct-color-link);
  display: inline-block;
}

.panel-title > a:hover,
.panel-title > a:focus {
  color: var(--alert-info-text);
}

/* Allow children in the title to wrap */
.panel-title > h3,
.panel-title > a,
.panel-title > div,
.panel-title > span {
  flex-shrink: 1;
  word-break: break-all;
}

.panel-heading > :last-child:not(:first-child),
.panel-heading > .panel-heading-actions {
  flex: auto;
  text-align: end;
}

@media screen and (max-width: 640px) {
  /* Remove _most_ of the gaps on the sides of small screens */
  /* to maximize space, but still keep the boxy panel look */
  .col-md-12 > .panel {
    margin-inline: -10px;
  }

  .panel {
    /* Background fade */
    --hi-color: #d1d1d1;
    --hi-color2: var(--ct-global--palette--black-250);
    --bg-color: var(--ct-color-bg);
    --hi-width: 20px;
    --hi-width3: calc(var(--hi-width) * 3);
    --transparent: rgb(255 255 255 / 0%); /* WebKit needs white transparent */
    max-inline-size: 100vw;
    overflow-x: auto;
    position: relative;
    background-image:
      linear-gradient(to left, var(--bg-color) var(--hi-width), var(--transparent) var(--hi-width3)),
      linear-gradient(to left, var(--hi-color) 1px, var(--transparent) 2px, var(--hi-color2) 4px, var(--bg-color) var(--hi-width)),
      linear-gradient(to right, var(--bg-color) var(--hi-width), var(--transparent) var(--hi-width3)),
      linear-gradient(to right, var(--hi-color) 1px, var(--transparent) 2px, var(--hi-color2) 4px, var(--bg-color) var(--hi-width));
    background-attachment: local, scroll, local, scroll;
    background-position: right, right, left, left;
    background-repeat: no-repeat;
    background-size: var(--hi-width3) 100%;
  }

  .panel > .panel-heading {
    position: sticky;
    inset-inline-start: 0;
    inset-block-start: 0;
  }

  .panel .table thead th {
    white-space: nowrap;
  }

  .panel .table:not(:hover):not(:focus):not(:active) {
    background: transparent;
  }

  .panel .table thead:not(:hover):not(:focus):not(:active) {
    background: transparent;
  }
}

.pf-v5-c-table__tr.pf-m-clickable:hover > td,
.pf-v5-c-table__tr.pf-m-clickable:hover > th {
  /* PF5 has no hover background color; we have to force the override for hover colors */
  background-color: var(--ct-color-list-hover-bg) !important;
  color: var(--ct-color-list-hover-text) !important;
}

/* Override patternfly to fit buttons and such */
.table > thead > tr > th,
.table > tbody > tr > td {
  padding: 0.5rem;
  vertical-align: baseline;
}

/* Override the heavy patternfly headers */
.table > thead {
  background-image: none;
  background-color: var(--ct-color-bg);
}

/* Make things line up */
.table tbody tr > :first-child,
.table thead tr > :first-child {
  padding-inline-start: 1rem;
}

.table tbody tr > :last-child,
.table thead tr > :last-child {
  padding-inline-end: 1rem;
}
07070100000055000081A4000000000000000000000001662A077800000BEB000000000000000000000000000000000000002E00000000cockpit-docker-devel-16/pkg/lib/timeformat.js/* Wrappers around Intl.DateTimeFormat and date-fns which use Cockpit's current locale, and define a few standard formats.
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat
 *
 * Time stamps are given in milliseconds since the epoch.
 */
import cockpit from "cockpit";
import { parse, formatDistanceToNow } from 'date-fns';
import * as locales from 'date-fns/locale';

// this needs to be dynamic, as some pages don't initialize cockpit.language right away
export const dateFormatLang = () => cockpit.language.replace('_', '-');

const dateFormatLangDateFns = () => {
    if (cockpit.language == "en") return "enUS";
    else return cockpit.language.replace('_', '');
};

// general Intl.DateTimeFormat formatter object
export const formatter = options => new Intl.DateTimeFormat(dateFormatLang(), options);

// common formatters; try to use these as much as possible, for UI consistency
// 07:41 AM
export const time = t => formatter({ timeStyle: "short" }).format(t);
// 7:41:26 AM
export const timeSeconds = t => formatter({ timeStyle: "medium" }).format(t);
// June 30, 2021
export const date = t => formatter({ dateStyle: "long" }).format(t);
// 06/30/2021
export const dateShort = t => formatter().format(t);
// Jun 30, 2021, 7:41 AM
export const dateTime = t => formatter({ dateStyle: "medium", timeStyle: "short" }).format(t);
// Jun 30, 2021, 7:41:23 AM
export const dateTimeSeconds = t => formatter({ dateStyle: "medium", timeStyle: "medium" }).format(t);
// Jun 30, 7:41 AM
export const dateTimeNoYear = t => formatter({ month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }).format(t);
// Wednesday, June 30, 2021
export const weekdayDate = t => formatter({ dateStyle: "full" }).format(t);

// The following options are helpful for placeholders
// yyyy/mm/dd
export const dateShortFormat = () => locales[dateFormatLangDateFns()].formatLong.date({ width: 'short' });
// about 1 hour [ago]
export const distanceToNow = (t, addSuffix) => formatDistanceToNow(t, { locale: locales[dateFormatLangDateFns()], addSuffix });

// Parse a string localized date like 30.06.21 to a Date Object
export function parseShortDate(dateStr) {
    const parsed = parse(dateStr, dateShortFormat(), new Date());

    // Strip time which may cause bugs in calendar
    const timePortion = parsed.getTime() % (3600 * 1000 * 24);
    return new Date(parsed - timePortion);
}

/***
 * sorely missing from Intl: https://github.com/tc39/ecma402/issues/6
 * based on https://github.com/unicode-cldr/cldr-core/blob/master/supplemental/weekData.json#L59
 * However, we don't have translations for most locales, and cockpit.language does not even contain
 * the country in most cases, so this is just an approximation.
 * Most locales start the week on Monday (day 1), so default to that and enumerate the others.
 */

const first_dow_sun = ['en', 'ja', 'ko', 'pt', 'pt_BR', 'sv', 'zh_CN', 'zh_TW'];

export function firstDayOfWeek() {
    return first_dow_sun.indexOf(cockpit.language) >= 0 ? 0 : 1;
}
07070100000056000081A4000000000000000000000001662A07780000046E000000000000000000000000000000000000002A00000000cockpit-docker-devel-16/pkg/lib/utils.jsx/*
 * This file is part of Cockpit.
 *
 * Copyright (C) 2018 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 <http://www.gnu.org/licenses/>.
 */

import React from "react";

export function fmt_to_fragments(fmt) {
    const args = Array.prototype.slice.call(arguments, 1);

    function replace(part) {
        if (part[0] == "$") {
            return args[parseInt(part.slice(1))];
        } else
            return part;
    }

    return React.createElement.apply(null, [React.Fragment, { }].concat(fmt.split(/(\$[0-9]+)/g).map(replace)));
}
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!1101 blocks
openSUSE Build Service is sponsored by