* Copyright (C) 2017 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
import cockpit from "cockpit";
import React from "react";
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
import { Progress, ProgressMeasureLocation } from "@patternfly/react-core/dist/esm/components/Progress/index.js";
import { Split, SplitItem } from "@patternfly/react-core/dist/esm/layouts/Split/index.js";
import { Spinner } from "@patternfly/react-core/dist/esm/components/Spinner/index.js";
import { show_modal_dialog } from "cockpit-components-dialog.jsx";
const _ = cockpit.gettext;
export function debug(...args: unknown[]) {
if (window.debugging === "all" || window.debugging?.includes("apps")) {
console.debug("apps:", ...args);
}
}
export function icon_url(path_or_url: string): string {
if (!path_or_url)
return "default.png";
if (path_or_url[0] !== '/')
return path_or_url;
interface QueryObj {
payload: string,
binary: "raw",
path: string,
external?: { [key: string]: string }
}
const queryobj: QueryObj = {
payload: "fsread1",
binary: "raw",
path: path_or_url,
};
if (path_or_url.endsWith(".svg")) {
queryobj.external = { "content-type": "image/svg+xml" };
}
const prefix = (new URL(cockpit.transport.uri("channel/" + cockpit.transport.csrf_token))).pathname;
const query = window.btoa(JSON.stringify(queryobj));
return prefix + '?' + query;
}
export interface JobProgress {
percentage: number,
waiting: boolean,
cancel: React.MouseEventHandler,
}
export const ProgressBar = ({ size, data, ariaLabelledBy }: {
size?: "lg" | "md" | "sm",
data: JobProgress,
ariaLabelledBy?: string,
}) => {
if (data.waiting) {
return (<Split>
<SplitItem className="progress-title" isFilled>
{_("Waiting for other programs to finish using the package manager...")}
</SplitItem>
<SplitItem>
<Spinner size="md" />
</SplitItem>
</Split>);
} else {
return <Progress className="progress-bar" value={data.percentage} size={size} measureLocation={ProgressMeasureLocation.inside} aria-labelledby={ariaLabelledBy} />;
}
};
export const CancelButton = ({ data }: { data: JobProgress }) => (
<Button variant="secondary" isDisabled={!data.cancel} onClick={data.cancel}>
{_("Cancel")}
</Button>);
type ProgressCallback = (data: JobProgress) => void
export class ProgressReporter {
base: number;
range: number;
percentage: number;
callback: ProgressCallback;
constructor(base: number, range: number, callback: ProgressCallback) {
this.base = base;
this.range = range;
this.callback = callback;
this.percentage = 0;
this.progress_reporter = this.progress_reporter.bind(this);
}
progress_reporter(data: JobProgress) {
if (data.percentage >= 0) {
const newPercentage = this.base + data.percentage / 100 * this.range;
if (this.percentage == null || newPercentage >= this.percentage)
this.percentage = newPercentage;
}
this.callback({ ...data, percentage: this.percentage });
}
}
export const show_error = (ex: any) => {
if (ex.code === "cancelled")
return;
if (ex.code === "not-found")
ex.detail = _("No installation package found for this application.");
show_modal_dialog(
{
title: _("Error"),
body: (
<p>{typeof ex == 'string' ? ex : (ex.detail || ex.message)}</p>
)
},
{
cancel_button: { text: _("Close"), variant: "secondary" },
actions: []
});
};
export interface Launchable {
type: string,
name: string,
}
export interface Component {
id: string,
pkgname: string,
name: string,
summary: string,
description: string,
icon: string,
screenshots: { full: string }[],
launchables: Launchable[],
urls: { type: string, link: string }[],
installed?: boolean,
}
export const launch = (comp: Component) => {
for (let i = 0; i < comp.launchables.length; i++) {
if (comp.launchables[i].type === "cockpit-manifest") {
debug("launching", comp.launchables[i].name, "in component", JSON.stringify(comp));
cockpit.jump([comp.launchables[i].name]);
return;
}
}
};
export function reload_bridge_packages() {
return cockpit.dbus(null, { bus: "internal" }).call("/packages", "cockpit.Packages", "Reload", []);
}