Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

shell: TypeScript workflow demo #21265

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pkg/lib/cockpit.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ declare module 'cockpit' {
export const manifests: { [package in string]?: JsonObject };

export let language: string;
export let language_direction: string;

interface Transport {
csrf_token: string;
Expand Down Expand Up @@ -98,6 +99,8 @@ declare module 'cockpit' {
changed(): void;
}

function event_target<T, EM extends EventMap>(obj: T): T & EventSource<EM>;
mvollmer marked this conversation as resolved.
Show resolved Hide resolved

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

interface ControlMessage extends JsonObject {
Expand Down Expand Up @@ -188,6 +191,9 @@ declare module 'cockpit' {
href: string;
go(path: Location | string, options?: { [key: string]: string }): void;
replace(path: Location | string, options?: { [key: string]: string }): void;

encode(path: string[], options?: { [key: string]: string }, with_root?: boolean): string;
decode(string: string, options?: { [key: string]: string }): string[];
mvollmer marked this conversation as resolved.
Show resolved Hide resolved
martinpitt marked this conversation as resolved.
Show resolved Hide resolved
}

export const location: Location;
Expand Down
13 changes: 6 additions & 7 deletions pkg/shell/hosts.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import { Tooltip } from "@patternfly/react-core/dist/esm/components/Tooltip";

import 'polyfills';
import { CockpitNav, CockpitNavItem } from "./nav.jsx";
import { build_href, split_connection_string } from "./util.jsx";
import { encode_location } from "./util.jsx";
import { split_connection_string } from "./machines/machines";
import { add_host, edit_host, connect_host } from "./hosts_dialog.jsx";

const _ = cockpit.gettext;
Expand Down Expand Up @@ -127,8 +128,7 @@ export class CockpitHosts extends React.Component {
const connection_string = await connect_host(host_modal_state, state, machine);
if (connection_string) {
const parts = split_connection_string(connection_string);
const addr = build_href({ host: parts.address });
state.jump(addr);
state.jump({ host: parts.address });
}
}

Expand All @@ -144,8 +144,7 @@ export class CockpitHosts extends React.Component {

if (current_machine === machine) {
// Removing machine underneath ourself - jump to localhost
const addr = build_href({ host: "localhost" });
state.jump(addr);
state.jump({ host: "localhost" });
}

if (state.machines.list.length <= 2)
Expand Down Expand Up @@ -186,14 +185,14 @@ export class CockpitHosts extends React.Component {
const render = (m, term) => <CockpitNavItem
term={term}
keyword={m.keyword}
to={build_href({ host: m.address })}
href={encode_location({ host: m.address })}
active={m === current_machine}
key={m.key}
name={m.label}
header={(m.user ? m.user : this.state.current_user) + " @"}
status={m.state === "failed" ? { type: "error", title: _("Connection error") } : null}
className={m.state}
jump={() => this.onHostSwitch(m)}
onClick={() => this.onHostSwitch(m)}
actions={<>
<Tooltip content={_("Edit")} position="right">
<Button isDisabled={m.address === "localhost"} className="nav-action" hidden={!editing} onClick={e => this.onHostEdit(e, m)} key={m.label + "edit"} variant="secondary"><EditIcon /></Button>
Expand Down
8 changes: 4 additions & 4 deletions pkg/shell/hosts_dialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@

import cockpit from "cockpit";

import { get_init_superuser_for_options } from "./machines/machines";
import {
get_init_superuser_for_options, split_connection_string, generate_connection_string
} from "./machines/machines";
import * as credentials from "credentials";
import ssh_show_default_key_sh from "../lib/ssh-show-default-key.sh";
import ssh_add_key_sh from "../lib/ssh-add-key.sh";
Expand All @@ -46,8 +48,6 @@ import { FormHelper } from "cockpit-components-form-helper";
import { ModalError } from "cockpit-components-inline-notification.jsx";
import { fmt_to_fragments } from "utils.js";

import { build_href, split_connection_string, generate_connection_string } from "./util.jsx";

const _ = cockpit.gettext;

export const HostModalState = () => {
Expand Down Expand Up @@ -88,7 +88,7 @@ function jump_to_new_connection_string(shell_state, connection_string) {
// manifests.
shell_state.loader.connect(addr);
// Navigate to it.
shell_state.jump(build_href({ host: addr }));
shell_state.jump({ host: addr });
}

export async function add_host(state, shell_state) {
Expand Down
73 changes: 73 additions & 0 deletions pkg/shell/machines/machines.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { EventSource, EventMap } from "cockpit";

export function generate_connection_string(user: string | null, port: string | null, addr: string) : string;
export function split_connection_string (conn_to: string) : { address: string, user?: string, port?: number };

export interface ManifestKeyword {
matches: string[];
goto?: string;
weight: number;
translate: boolean;
}

export interface ManifestDocs {
label: string;
url: string;
}

export interface ManifestEntry {
path?: string;
label?: string;
order?: number;
docs?: ManifestDocs[];
keywords?: ManifestKeyword[];
}

export interface ManifestSection {
[name: string]: ManifestEntry;
}

export interface Manifest {
[section: string]: ManifestSection;
}

export interface Manifests {
[pkg: string]: Manifest;
}

export interface Machine {
key: string;
connection_string: string;
address: string;
user?: string;
port?: number;
label: string;
state: null | "failed" | "connecting" | "connected";
manifests?: Manifests;
checksum?: string;
visible?: boolean;
problem: string | null;
}

interface MachinesEvents extends EventMap {
ready: () => void;
added: (machine: Machine) => void;
removed: (machine: Machine) => void;
updated: (machine: Machine) => void;
}

export interface Machines extends EventSource<MachinesEvents> {
ready: boolean;

lookup: (conection_string: string) => Machine;
}

interface Loader {
connect: (connection_string: string) => void;
expect_restart: (connection_string: string) => void;
}

export const machines: {
instance: () => Machines;
loader: (machines: Machines) => Loader;
};
43 changes: 41 additions & 2 deletions pkg/shell/machines/machines.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import cockpit from "cockpit";

import ssh_add_key_sh from "../../lib/ssh-add-key.sh";

import { split_connection_string, generate_connection_string } from "../util.jsx";

const mod = { };

/*
Expand Down Expand Up @@ -70,6 +68,47 @@ export function get_init_superuser_for_options(options) {
return value;
}

export function generate_connection_string(user, port, addr) {
let address = addr;
if (user)
address = user + "@" + address;

if (port)
address = address + ":" + port;

return address;
}

export function split_connection_string (conn_to) {
const parts = { address: "" };
let user_spot = -1;
let port_spot = -1;

if (conn_to) {
if (conn_to.substring(0, 6) === "ssh://")
conn_to = conn_to.substring(6);
user_spot = conn_to.lastIndexOf('@');
port_spot = conn_to.lastIndexOf(':');
}

if (user_spot > 0) {
parts.user = conn_to.substring(0, user_spot);
conn_to = conn_to.substring(user_spot + 1);
port_spot = conn_to.lastIndexOf(':');
}

if (port_spot > -1) {
const port = parseInt(conn_to.substring(port_spot + 1), 10);
if (!isNaN(port)) {
parts.port = port;
conn_to = conn_to.substring(0, port_spot);
}
}

parts.address = conn_to;
return parts;
}

function Machines() {
const self = this;

Expand Down
35 changes: 21 additions & 14 deletions pkg/shell/nav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { SearchInput } from "@patternfly/react-core/dist/esm/components/SearchIn
import { Tooltip, TooltipPosition } from "@patternfly/react-core/dist/esm/components/Tooltip/index.js";
import { ContainerNodeIcon, ExclamationCircleIcon, ExclamationTriangleIcon, InfoCircleIcon } from '@patternfly/react-icons';

import { build_href } from "./util.jsx";
import { encode_location } from "./util.jsx";

const _ = cockpit.gettext;

Expand Down Expand Up @@ -145,10 +145,14 @@ export class CockpitNav extends React.Component {
<div className="nav-group-heading">
<h2 className="pf-v5-c-nav__section-title" id={"section-title-" + g.name}>{g.name}</h2>
{ g.action &&
<a className="pf-v5-c-nav__section-title nav-item" href={g.action.path} onClick={ ev => {
this.props.jump(g.action.path);
ev.preventDefault();
}}>{g.action.label}</a>
<a className="pf-v5-c-nav__section-title nav-item"
href={encode_location(g.action.target)}
onClick={ ev => {
this.props.jump(g.action.target);
ev.preventDefault();
Comment on lines +148 to +152
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 5 added lines are not executed by any test.

}}>
{g.action.label}
</a>
Comment on lines +154 to +155
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 2 added lines are not executed by any test.

}
</div>
<ul className="pf-v5-c-nav__list">
Expand Down Expand Up @@ -220,8 +224,9 @@ export function CockpitNavItem(props) {
<li className={classes.join(" ")}>
<a className={"pf-v5-c-nav__link" + (props.active ? " pf-m-current" : "")}
aria-current={props.active && "page"}
href={cockpit.location.encode(props.to, {}, true)} onClick={ev => {
props.jump(props.to);
href={props.href}
onClick={ev => {
props.onClick();
ev.preventDefault();
}}>
{ props.header && <span className="nav-item-hint">{header_matches ? <FormattedText keyword={props.header} term={props.term} /> : props.header}</span> }
Expand All @@ -240,8 +245,8 @@ export function CockpitNavItem(props) {

CockpitNavItem.propTypes = {
name: PropTypes.string.isRequired,
to: PropTypes.string.isRequired,
jump: PropTypes.func,
href: PropTypes.string.isRequired,
onClick: PropTypes.func,
status: PropTypes.object,
active: PropTypes.bool,
keyword: PropTypes.string,
Expand Down Expand Up @@ -328,15 +333,17 @@ export const PageNav = ({ state }) => {
if (page_status[current_machine.key])
status = page_status[current_machine.key][item.path];

const target_location = { host: current_machine.address, path, hash };

return (
<CockpitNavItem key={item.label}
name={item.label}
active={active}
status={status}
keyword={item.keyword.keyword}
term={term}
to={build_href({ host: current_machine.address, path, hash })}
jump={state.jump} />
href={encode_location(target_location)}
onClick={() => state.jump(target_location)} />
);
}

Expand All @@ -356,10 +363,10 @@ export const PageNav = ({ state }) => {
if (current_machine_manifest_items.items.apps && groups.length === 3)
groups[0].action = {
label: _("Edit"),
path: build_href({
target: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test.

host: current_machine.address,
path: current_machine_manifest_items.items.apps.path
})
path: current_machine_manifest_items.items.apps.path,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This added line is not executed by any test.

}
};

return <CockpitNav groups={groups}
Expand Down
Loading