* Copyright (C) 2024 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
import cockpit from "cockpit";
import { ManifestKeyword, ManifestDocs, ManifestSection, Manifest, Manifests } from "./manifests";
import { Machine } from "./machines/machines";
export interface Location {
host: string;
path: string;
hash: string;
}
export function encode_location(location: Partial<Location>): string {
const shell_embedded = window.location.pathname.indexOf(".html") !== -1;
if (shell_embedded)
return window.location.toString();
const path = [];
if (location.host && location.host !== "localhost")
path.push("@" + location.host);
if (location.path)
path.push(...location.path.split("/"));
let string = cockpit.location.encode(path, undefined, true);
if (location.hash && location.hash !== "/")
string += "#" + location.hash;
return string;
}
export function decode_location(string: string): Location {
let hash = "";
const pos = string.indexOf("#");
if (pos !== -1) {
hash = string.substring(pos + 1);
string = string.substring(0, pos);
}
if (string[0] !== '/')
string = "/" + string;
const path = cockpit.location.decode(string);
let host;
if (path[0] && path[0][0] === "@")
host = (path.shift() as string).substring(1);
else
host = "localhost";
if (path.length && path[path.length - 1] === "index")
path.pop();
return { host, path: path.join("/"), hash };
}
export function decode_window_location(): Location {
const shell_embedded = window.location.pathname.indexOf(".html") !== -1;
if (shell_embedded)
return decode_location("/" + window.location.hash);
else
return decode_location(window.location.pathname + window.location.hash);
}
export function replace_window_location(location: Partial<Location>): void {
window.history.replaceState(null, "", encode_location(location));
}
export function push_window_location(location: Partial<Location>): void {
window.history.pushState(null, "", encode_location(location));
}
export interface ManifestItem {
path: string;
hash: string;
section: string;
label: string;
order: number;
docs: ManifestDocs[] | undefined;
keywords: ManifestKeyword[];
}
export class CompiledComponents {
manifests: Manifests;
items: { [path: string] : ManifestItem; } = { };
constructor(manifests?: Manifests) {
this.manifests = manifests || { };
}
load(section: string, getter: (man: Manifest) => ManifestSection | undefined): void {
Object.entries(this.manifests).forEach(([name, manifest]) => {
const manifest_section = getter(manifest) || {};
Object.entries(manifest_section).forEach(([prop, info]) => {
const item: ManifestItem = {
path: "",
hash: "",
section,
label: info.label ? cockpit.gettext(info.label) : prop,
order: info.order === undefined ? 1000 : info.order,
docs: info.docs,
keywords: info.keywords || [{ matches: [], weight: 3, translate: true, goto: undefined }],
};
const page_name = item.label.toLowerCase();
if (item.keywords[0].matches.indexOf(page_name) < 0)
item.keywords[0].matches.unshift(page_name);
item.keywords.forEach(i => {
i.weight = i.weight || 3;
i.translate = i.translate === undefined ? true : i.translate;
});
if (info.path)
item.path = info.path.replace(/\.html$/, "");
else
item.path = name + "/" + prop;
const pos = item.path.indexOf("#");
if (pos !== -1) {
item.hash = item.path.substring(pos + 1);
item.path = item.path.substring(0, pos);
}
if (item.path.indexOf("/") === -1)
item.path = name + "/" + item.path;
if (item.path.slice(-6) === "/index")
item.path = item.path.slice(0, -6);
this.items[item.path] = item;
});
});
}
ordered(section: string): ManifestItem[] {
const list: ManifestItem[] = [];
for (const x in this.items) {
if (!section || this.items[x].section === section)
list.push(this.items[x]);
}
list.sort(function(a, b) {
let ret = a.order - b.order;
if (ret === 0)
ret = a.label.localeCompare(b.label);
return ret;
});
return list;
}
find_path_item(path: string): ManifestItem {
let component = path;
if (this.items[path] === undefined) {
let s = path;
while (s && this.items[s] === undefined)
s = s.substring(0, s.lastIndexOf("/"));
component = s;
}
if (!component) {
const comp = this.manifests[path];
if (comp && comp.parent && comp.parent.component)
component = comp.parent.component;
}
const item = this.items[component];
if (item)
return item;
return {
path,
label: path,
section: "menu",
hash: "",
order: 3,
keywords: [],
docs: undefined
};
}
find_path_manifest(path: string): Manifest {
const parts = path.split("/");
const pkg = parts[0];
return (this.manifests[pkg]) || { };
}
}
export function compile_manifests(manifests?: Manifests): CompiledComponents {
const compiled = new CompiledComponents(manifests);
compiled.load("tools", m => m.tools);
compiled.load("dashboard", m => m.dashboard);
compiled.load("menu", m => m.menu);
return compiled;
}
function component_checksum(machine: Machine, path: string): string | undefined {
const parts = path.split("/");
const pkg = parts[0];
if (machine.manifests && machine.manifests[pkg] && machine.manifests[pkg][".checksum"])
return "$" + machine.manifests[pkg][".checksum"];
}
export function compute_frame_url(machine: Machine, path: string): string {
let base;
let checksum;
if (machine.manifests && machine.manifests[".checksum"])
checksum = "$" + machine.manifests[".checksum"];
else
checksum = machine.checksum;
if (checksum && checksum === component_checksum(machine, path)) {
if (machine.connection_string === "localhost")
base = "..";
else
base = "../../" + checksum;
} else {
checksum than the machine, load it via a non-caching @<host> path. This
makes sure that we get the right files, and also that we don't poisen the
cache with wrong files.
We can't use a $<component-checksum> path since cockpit-ws only knows how to
route the machine checksum.
TODO - make it possible to use $<component-checksum>.
*/
base = "../../@" + machine.connection_string;
}
let url = base + "/" + path;
if (path.indexOf("/") === -1)
url += "/index";
url += ".html";
return url;
}