Skip to content

Commit

Permalink
WIP - navigation experiment
Browse files Browse the repository at this point in the history
 - let's git rid of our state objects (but keep pushState and
   replaceState in the right places).

 - let's trigger connections explicitly instead of letting navigate do
   it.
  • Loading branch information
mvollmer committed Sep 24, 2024
1 parent 24b1266 commit f13e171
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 140 deletions.
103 changes: 24 additions & 79 deletions pkg/shell/base_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import cockpit from "cockpit";

import { Router } from "./router.jsx";
import { encode_state, decode_state } from "./util.jsx";

const shell_embedded = window.location.pathname.indexOf(".html") !== -1;

Expand Down Expand Up @@ -57,72 +58,13 @@ function Index() {
return false;
};

/*
* Navigation is driven by state objects, which are used with pushState()
* and friends. The state is the canonical navigation location, and not
* the URL. Only when no state has been pushed or we are arriving from
* a link, do we parse the state from the URL.
*
* Each state object has:
* host: a machine host
* component: the stripped component to load
* hash: the hash to pass to the component
* sidebar: set to true to hint that we want a component with a sidebar
*
* If state.sidebar is set, and no component has yet been chosen for the
* given state, then we try to find one that would show a sidebar.
*/

/* Encode navigate state into a string
* If with_root is true the configured
* url root will be added to the generated
* url. with_root should be used when
* navigating to a new url or updating
* history, but is not needed when simply
* generating a string for a link.
*/
function encode(state, sidebar, with_root) {
const path = [];
if (state.host && (sidebar || state.host !== "localhost"))
path.push("@" + state.host);
if (state.component)
path.push.apply(path, state.component.split("/"));
let string = cockpit.location.encode(path, null, with_root);
if (state.hash && state.hash !== "/")
string += "#" + state.hash;
return string;
}

/* Decodes navigate state from a string */
function decode(string) {
const state = { version: "v1", hash: "" };
const pos = string.indexOf("#");
if (pos !== -1) {
state.hash = string.substring(pos + 1);
string = string.substring(0, pos);
}
if (string[0] != '/')
string = "/" + string;
const path = cockpit.location.decode(string);
if (path[0] && path[0][0] == "@") {
state.host = path.shift().substring(1);
state.sidebar = true;
} else {
state.host = "localhost";
}
if (path.length && path[path.length - 1] == "index")
path.pop();
state.component = path.join("/");
return state;
}

self.retrieve_state = function() {
let state = window.history.state;
if (!state || state.version !== "v1") {
if (shell_embedded)
state = decode("/" + window.location.hash);
state = decode_state("/" + window.location.hash);
else
state = decode(window.location.pathname + window.location.hash);
state = decode_state(window.location.pathname + window.location.hash);
}
return state;
};
Expand All @@ -138,12 +80,23 @@ function Index() {
return null;
}

self.track_hash = function track_hash(address, component, hash) {
if (!last_hash_for_host_fullpath[address])
last_hash_for_host_fullpath[address] = { };
last_hash_for_host_fullpath[address][component] = hash;
};

self.last_component_for_host = { };

/* Jumps to a given navigate state */
self.jump = function (state, replace) {
if (replace)
return;

console.log("JUMP", JSON.stringify(state));

if (typeof (state) === "string")
state = decode(state);
state = decode_state(state);

const current = self.retrieve_state();

Expand All @@ -168,34 +121,23 @@ function Index() {
if (frame_change && !state.hash)
state.hash = lookup_component_hash(state.host, state.component);

if (!last_hash_for_host_fullpath[state.host])
last_hash_for_host_fullpath[state.host] = { };
last_hash_for_host_fullpath[state.host][state.component] = state.hash;
self.track_hash(state.host, state.component, state.hash);

const target = shell_embedded ? window.location : encode(state, null, true);

if (replace) {
history.replaceState(state, "", target);
return false;
}
const target = shell_embedded ? window.location : encode_state(state, null, true);

if (frame_change || state.hash !== current.hash) {
console.log("PUSH", JSON.stringify(state), target);
history.pushState(state, "", target);
const nav_system = document.getElementById("nav-system");
if (nav_system)
nav_system.classList.remove("interact");
self.navigate(state, true);
self.navigate();
return true;
}

return false;
};

/* Build an href for use in an <a> */
self.href = function (state, sidebar) {
return encode(state, sidebar);
};

self.show_oops = function () {
self.has_oops = true;
self.dispatchEvent("update");
Expand All @@ -220,10 +162,13 @@ function Index() {

self.ready = function () {
window.addEventListener("popstate", ev => {
self.navigate(ev.state, true);
console.log("POP!");
self.navigate();
self.ensure_connection();
});

self.navigate(null, true);
self.navigate();
self.ensure_connection();
cockpit.translate();
document.body.removeAttribute("hidden");
};
Expand Down
13 changes: 2 additions & 11 deletions pkg/shell/failures.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,7 @@ export const EarlyFailure = ({ ca_cert_url }) => {
);
};

export const EarlyFailureReady = ({ loading, title, paragraph, reconnect, troubleshoot, onTroubleshoot, watchdog_problem, navigate }) => {
const onReconnect = () => {
if (watchdog_problem) {
cockpit.sessionStorage.clear();
window.location.reload(true);
} else {
navigate(null, true);
}
};

export const EarlyFailureReady = ({ loading, title, paragraph, reconnect, troubleshoot, onTroubleshoot, watchdog_problem, onReconnect }) => {
return (
<Page>
<PageSection variant={PageSectionVariants.light}>
Expand Down Expand Up @@ -117,7 +108,7 @@ export const MachineTroubleshoot = ({ machine, onClick }) => {
reconnect={reconnect}
troubleshoot={troubleshooting}
onTroubleshoot={onClick}
navigate={onClick}
onReconnect={onClick}
paragraph={message} />
);
};
24 changes: 15 additions & 9 deletions pkg/shell/hosts.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { Tooltip } from "@patternfly/react-core/dist/esm/components/Tooltip";
import 'polyfills';
import { CockpitNav, CockpitNavItem } from "./nav.jsx";
import { HostModal, try2Connect, codes } from "./hosts_dialog.jsx";
import { useLoggedInUser } from "hooks";
import { build_href } from "./util.jsx";

const _ = cockpit.gettext;

Expand Down Expand Up @@ -93,7 +93,10 @@ export class CockpitHosts extends React.Component {
if (!this.state.modal_properties)
this.connectHost(machine);
};
this.props.index.navigate(null, true);
// XXX - Only because trigger_connection_flow isn't available
// until we are mounted. If that is fixed, remove this
// here.
this.props.index.ensure_connection();
}

componentWillUnmount() {
Expand Down Expand Up @@ -141,7 +144,7 @@ export class CockpitHosts extends React.Component {
const connection_string = await this.showModal({ address: machine.address });
if (connection_string) {
const parts = this.props.machines.split_connection_string(connection_string);
const addr = this.props.hostAddr({ host: parts.address }, true);
const addr = build_href({ host: parts.address });
if (machine == this.props.machine && parts.address != machine.address) {
this.props.loader.connect(parts.address);
this.props.jump(addr);
Expand All @@ -150,9 +153,14 @@ export class CockpitHosts extends React.Component {
}

async connectHost(machine) {
if (machine.address == "localhost" || machine.state == "connected" || machine.state == "connecting")
if (machine.state == "connected" || machine.state == "connecting")
return machine.connection_string;

if (machine.connection_string == "localhost") {
this.props.loader.connect(machine.address);
return;
}

let connection_string = null;

if (machine.problem && codes[machine.problem]) {
Expand Down Expand Up @@ -196,7 +204,7 @@ export class CockpitHosts extends React.Component {
const connection_string = await this.connectHost(machine);
if (connection_string) {
const parts = this.props.machines.split_connection_string(connection_string);
const addr = this.props.hostAddr({ host: parts.address }, true);
const addr = build_href({ host: parts.address });
this.props.jump(addr);
}
}
Expand All @@ -210,7 +218,7 @@ export class CockpitHosts extends React.Component {

if (this.props.machine === machine) {
// Removing machine underneath ourself - jump to localhost
const addr = this.props.hostAddr({ host: "localhost" }, true);
const addr = build_href({ host: "localhost" });
this.props.jump(addr);
}

Expand Down Expand Up @@ -241,7 +249,6 @@ export class CockpitHosts extends React.Component {
// 1. It does not change the arrow when opened/closed
// 2. It closes the dropdown even when trying to search... and cannot tell it not to
render() {
const hostAddr = this.props.hostAddr;
const editing = this.state.editing;
const groups = [{
name: _("Hosts"),
Expand All @@ -250,7 +257,7 @@ export class CockpitHosts extends React.Component {
const render = (m, term) => <CockpitNavItem
term={term}
keyword={m.keyword}
to={hostAddr({ host: m.address }, true)}
to={build_href({ host: m.address })}
active={m === this.props.machine}
key={m.key}
name={m.label}
Expand Down Expand Up @@ -336,6 +343,5 @@ CockpitHosts.propTypes = {
index: PropTypes.object.isRequired,
loader: PropTypes.object.isRequired,
selector: PropTypes.string.isRequired,
hostAddr: PropTypes.func.isRequired,
jump: PropTypes.func.isRequired,
};
7 changes: 4 additions & 3 deletions pkg/shell/nav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ 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";

const _ = cockpit.gettext;

export const SidebarToggle = () => {
Expand Down Expand Up @@ -253,7 +255,6 @@ export const PageNav = ({
component,
compiled,
page_status,
build_href,
jump
}) => {
if (!machine || machine.state != "connected") {
Expand Down Expand Up @@ -333,7 +334,7 @@ export const PageNav = ({
status={status}
keyword={item.keyword.keyword}
term={term}
to={build_href({ host: machine.address, component: path, hash })}
to={build_href({ host: machine.address, component: path, hash }, false)}
jump={jump} />
);
}
Expand All @@ -354,7 +355,7 @@ export const PageNav = ({
if (compiled.items.apps && groups.length === 3)
groups[0].action = {
label: _("Edit"),
path: build_href({ host: machine.address, component: compiled.items.apps.path })
path: build_href({ host: machine.address, component: compiled.items.apps.path }, false)
};

return <CockpitNav groups={groups}
Expand Down
10 changes: 7 additions & 3 deletions pkg/shell/router.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@

import cockpit from "cockpit";

import { replace_window_location } from "./util.jsx";

export function Router(index) {
const self = this;

Expand Down Expand Up @@ -99,11 +101,13 @@ export function Router(index) {
if (hash === "/")
hash = "";
/* The browser has already pushed an appropriate entry to
the history, so let's just replace it with our custom
state object.
the history, so let's just replace it with one that
includes the right hash.
*/
const state = Object.assign({}, index.retrieve_state(), { hash });
index.navigate(state, true);
replace_window_location(state);
index.track_hash(state.host, state.component, state.hash);
index.navigate();
}
}

Expand Down
Loading

0 comments on commit f13e171

Please sign in to comment.