Support basic auth

This commit is contained in:
Arnaud 2025-03-28 11:09:43 +01:00
parent d2be5a6ac2
commit 4b2e17907d
No known key found for this signature in database
GPG Key ID: 69D6CE281FCAE663
15 changed files with 446 additions and 364 deletions

Binary file not shown.

Binary file not shown.

View File

@ -77,6 +77,7 @@ test("does not display undefined when delete the url value", async ({
}
await expect(page.locator("#url")).toHaveValue("");
await page.locator("#url").blur();
await expect(page.locator("#url")).toHaveAttribute("aria-invalid");
await expect(page.locator(".refresh svg")).toHaveAttribute(
"color",

View File

@ -8,23 +8,80 @@ import test, { expect } from "@playwright/test";
// await expect(page.locator('span').filter({ hasText: 'success ! The log level has' }).locator('b')).toBeVisible();
// })
test('update the URL with wrong URL applies', async ({ page }) => {
await page.goto('/dashboard');
await page.locator('a').filter({ hasText: 'Settings' }).click();
await page.getByLabel('Address').click();
await page.getByLabel('Address').fill('hello');
await expect(page.getByLabel('Address')).toHaveAttribute("aria-invalid")
await expect(page.locator(".refresh svg")).toHaveAttribute("color", "#494949")
await page.getByLabel('Address').fill('http://127.0.0.1:8079');
await expect(page.getByLabel('Address')).not.toHaveAttribute("aria-invalid")
await expect(page.locator(".refresh svg")).not.toHaveAttribute("aria-disabled")
await expect(page.getByLabel('Address')).toHaveValue("http://127.0.0.1")
await expect(page.getByLabel('Port')).toHaveValue("8079")
await page.locator(".refresh").click()
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-error")).toBeVisible()
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-success")).not.toBeVisible()
await page.getByLabel('Address').fill('http://127.0.0.1:8080');
await page.locator(".refresh").click()
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-error")).not.toBeVisible()
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-success")).toBeVisible()
})
test("update the URL with wrong URL applies", async ({ page }) => {
await page.goto("/dashboard");
await page.locator("a").filter({ hasText: "Settings" }).click();
await page.getByLabel("Address").click();
await page.getByLabel("Address").fill("hello");
await page.getByLabel("Address").blur();
await expect(page.getByLabel("Address")).toHaveAttribute("aria-invalid");
await expect(page.locator(".refresh svg")).toHaveAttribute(
"color",
"#494949"
);
await page.getByLabel("Address").fill("http://127.0.0.1:8079");
await page.getByLabel("Address").blur();
await expect(page.getByLabel("Address")).not.toHaveAttribute("aria-invalid");
await expect(page.locator(".refresh svg")).not.toHaveAttribute(
"aria-disabled"
);
await expect(page.getByLabel("Address")).toHaveValue("http://127.0.0.1");
await expect(page.getByLabel("Port")).toHaveValue("8079");
await page.locator(".refresh").click();
await expect(
page.locator(".health-checks ul li").nth(2).getByTestId("icon-error")
).toBeVisible();
await expect(
page.locator(".health-checks ul li").nth(2).getByTestId("icon-success")
).not.toBeVisible();
await page.getByLabel("Address").fill("http://127.0.0.1:8080");
await page.locator(".refresh").click();
await expect(
page.locator(".health-checks ul li").nth(2).getByTestId("icon-error")
).not.toBeVisible();
await expect(
page.locator(".health-checks ul li").nth(2).getByTestId("icon-success")
).toBeVisible();
});
test("update the URL with basic auth save the credentials", async ({
page,
}) => {
await page.goto("/dashboard");
await page.locator("a").filter({ hasText: "Settings" }).click();
await page.getByLabel("Address").click();
await page.getByLabel("Address").fill("http://hello:world@localhost:8080");
await page.getByLabel("Address").blur();
await expect(page.getByLabel("Address")).not.toHaveAttribute("aria-invalid");
await expect(page.locator(".refresh svg")).not.toHaveAttribute(
"aria-disabled"
);
let failedRequestAuthorization;
page.on("requestfailed", async (request) => {
failedRequestAuthorization = await request.headerValue("Authorization");
});
await page.locator(".refresh").click();
await page.waitForTimeout(1000);
expect(failedRequestAuthorization).toBe("Basic " + btoa("hello:world"));
await page.getByLabel("Address").click();
await page.getByLabel("Address").fill("http://localhost:8080");
await page.getByLabel("Address").blur();
let successRequestAuthorization: string | null = "failed";
page.on("request", async (request) => {
if (request.url().endsWith("/api/codex/v1/spr")) {
successRequestAuthorization = await request.headerValue("Authorization");
}
});
await page.locator(".refresh").click();
await page.waitForRequest("http://localhost:8080/api/codex/v1/spr");
expect(successRequestAuthorization).toBeNull();
});

26
package-lock.json generated
View File

@ -9,8 +9,8 @@
"version": "0.0.16",
"license": "MIT",
"dependencies": {
"@codex-storage/marketplace-ui-components": "^0.0.51",
"@codex-storage/sdk-js": "^0.0.23",
"@codex-storage/marketplace-ui-components": "./codex-storage-marketplace-ui-components-0.0.51.tgz",
"@codex-storage/sdk-js": "./codex-storage-sdk-js-0.1.1.tgz",
"@sentry/browser": "^8.32.0",
"@sentry/react": "^8.31.0",
"@tanstack/react-query": "^5.51.15",
@ -420,8 +420,8 @@
},
"node_modules/@codex-storage/marketplace-ui-components": {
"version": "0.0.51",
"resolved": "https://registry.npmjs.org/@codex-storage/marketplace-ui-components/-/marketplace-ui-components-0.0.51.tgz",
"integrity": "sha512-KPPFlcpx3a83WCBSLbRONrF/yr4J/ctyTfFPxMaRSMTRD1LtfIE0uPy3QxtHs6tigOts2h4DEz6Kn2ynHdfKPg==",
"resolved": "file:codex-storage-marketplace-ui-components-0.0.51.tgz",
"integrity": "sha512-rIgI20HA4CbccaxJPVZJw+QvoxDpY6g2FWX1HDChFqYqBcw+AP1h2gtVSau5cSZn6Zn/EQIgMlYZk+1m0U3utQ==",
"license": "MIT",
"engines": {
"node": ">=18"
@ -433,15 +433,16 @@
}
},
"node_modules/@codex-storage/sdk-js": {
"version": "0.0.23",
"resolved": "https://registry.npmjs.org/@codex-storage/sdk-js/-/sdk-js-0.0.23.tgz",
"integrity": "sha512-+ktJs396GERPudRh5zjTvOMjwo3mRHVYN901Qvs0q3YlRK983aewSqJ+Z7NttSQ27oxTcvxQVrilcvzZRHQTkg==",
"version": "0.1.1",
"resolved": "file:codex-storage-sdk-js-0.1.1.tgz",
"integrity": "sha512-BwCYirG0gwNJxxF0c3Zkrachi9FcYH6sLGUE9V07zIPaC0XVtiJiyUXhP0ciI28jgg2sFqk5nim/5ZmMaQmqBw==",
"license": "MIT",
"dependencies": {
"undici": "^7.5.0",
"valibot": "^0.32.0"
},
"engines": {
"node": ">=20"
"node": ">=20.18.1"
}
},
"node_modules/@csstools/selector-resolve-nested": {
@ -5173,6 +5174,15 @@
"node": ">=14.17"
}
},
"node_modules/undici": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.6.0.tgz",
"integrity": "sha512-gaFsbThjrDGvAaD670r81RZro/s6H2PVZF640Qn0p5kZK+/rim7/mmyfp2W7VB5vOMaFM8vuFBJUaMlaZTYHlA==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
}
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",

View File

@ -26,8 +26,8 @@
"React"
],
"dependencies": {
"@codex-storage/marketplace-ui-components": "^0.0.51",
"@codex-storage/sdk-js": "^0.0.23",
"@codex-storage/marketplace-ui-components": "./codex-storage-marketplace-ui-components-0.0.51.tgz",
"@codex-storage/sdk-js": "./codex-storage-sdk-js-0.1.1.tgz",
"@sentry/browser": "^8.32.0",
"@sentry/react": "^8.31.0",
"@tanstack/react-query": "^5.51.15",

View File

@ -1,55 +0,0 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import { Button, Input, Toast } from "@codex-storage/marketplace-ui-components";
import { CodexSdk } from "../../sdk/codex";
export function CodexUrlSettings() {
const queryClient = useQueryClient();
const [url, setUrl] = useState(CodexSdk.url);
const [isInvalid, setIsInvalid] = useState(false);
const [toast, setToast] = useState({ time: 0, message: "" });
const { mutateAsync } = useMutation({
mutationFn: (url: string) => CodexSdk.updateURL(url),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["spr"] });
setToast({ message: "Settings saved successfully.", time: Date.now() });
},
});
const onChange = (e: React.FormEvent<HTMLInputElement>) => {
const element = e.currentTarget;
const value = element.value;
setUrl(value);
setIsInvalid(!element.checkValidity());
};
const onClick = () => {
if (isInvalid === false) {
mutateAsync(url);
}
};
return (
<>
<div className="settings-input">
<Input
id="url"
label="Codex client node URL"
onChange={onChange}
value={url}
isInvalid={isInvalid}
helper={isInvalid ? "The URL is not valid" : "Enter a valid URL"}
type="url"></Input>
</div>
<Button
className="settings-url-button"
disabled={isInvalid}
variant="primary"
label="Save changes"
onClick={onClick}></Button>
<Toast message={toast.message} time={toast.time} variant="success" />
</>
);
}

View File

@ -31,6 +31,7 @@ export function HealthChecks({ online, onStepValid }: Props) {
HealthCheckUtils.removePort(CodexSdk.url())
);
const [port, setPort] = useState(HealthCheckUtils.getPort(CodexSdk.url()));
const [auth, setAuth] = useState("");
const queryClient = useQueryClient();
useEffect(
@ -46,23 +47,31 @@ export function HealthChecks({ online, onStepValid }: Props) {
[persistence.refetch, onStepValid, portForwarding.refetch, codex.isSuccess]
);
const onAddressChange = (e: React.FormEvent<HTMLInputElement>) => {
const onAddressBlur = (e: React.FormEvent<HTMLInputElement>) => {
const element = e.currentTarget;
const value = e.currentTarget.value;
setIsAddressInvalid(!element.checkValidity());
setAddress(value);
const { auth, url } = HealthCheckUtils.extractBasicAuth(value);
if (HealthCheckUtils.containsPort(value)) {
const address = HealthCheckUtils.removePort(value);
setAddress(url);
if (HealthCheckUtils.containsPort(url)) {
const address = HealthCheckUtils.removePort(url);
setAddress(address);
const p = HealthCheckUtils.getPort(value);
const p = HealthCheckUtils.getPort(url);
setPort(p);
} else {
setAddress(value);
setAddress(url);
}
setAuth(auth || "");
};
const onChange = (e: React.FormEvent<HTMLInputElement>) => {
setAddress(e.currentTarget.value);
};
const onPortChange = (e: React.FormEvent<HTMLInputElement>) => {
@ -70,19 +79,25 @@ export function HealthChecks({ online, onStepValid }: Props) {
const value = element.value;
setIsPortInvalid(!element.checkValidity());
setPort(parseInt(value, 10));
setPort(value);
};
const onSave = () => {
const url = address + ":" + port;
let url = address;
if (port) {
url += ":" + port;
}
if (HealthCheckUtils.isUrlInvalid(url)) {
return;
}
CodexSdk.updateURL(url)
CodexSdk.updateURL(url, { auth: { basic: auth } })
.then(() => queryClient.invalidateQueries())
.then(() => codex.refetch());
setAuth("");
};
return (
@ -98,9 +113,11 @@ export function HealthChecks({ online, onStepValid }: Props) {
type="url"
label="Address"
required
mode={"manual"}
isInvalid={isAddressInvalid}
onChange={onAddressChange}
onBlur={onAddressBlur}
value={address}
onChange={onChange}
placeholder="127.0.0.1"></Input>
{isAddressInvalid ? (
<ErrorCircleIcon width={16} />

View File

@ -2,155 +2,167 @@ import { assert, describe, it } from "vitest";
import { HealthCheckUtils } from "./health-check.utils";
describe("health check", () => {
it("remove the port from an url", async () => {
assert.deepEqual(HealthCheckUtils.removePort("http://localhost:8080"), "http://localhost");
});
it("remove the port from an url", async () => {
assert.deepEqual(
HealthCheckUtils.removePort("http://localhost:8080"),
"http://localhost"
);
});
it("get the port from an url", async () => {
assert.deepEqual(HealthCheckUtils.getPort("http://localhost:8080"), 8080);
});
it("get the port from an url", async () => {
assert.deepEqual(HealthCheckUtils.getPort("http://localhost:8080"), "8080");
});
it("get the default port when the url does not contain the port", async () => {
assert.deepEqual(HealthCheckUtils.getPort("http://localhost"), 80);
});
it("get the default port when the url does not contain the port", async () => {
assert.deepEqual(HealthCheckUtils.getPort("http://localhost"), "");
});
it("returns true when the url contains a port", async () => {
assert.deepEqual(HealthCheckUtils.containsPort("http://localhost:8080"), true);
});
it("returns true when the url contains a port", async () => {
assert.deepEqual(
HealthCheckUtils.containsPort("http://localhost:8080"),
true
);
});
it("returns false when the url does not contain a port", async () => {
assert.deepEqual(HealthCheckUtils.containsPort("http://localhost"), false);
});
it("returns false when the url does not contain a port", async () => {
assert.deepEqual(HealthCheckUtils.containsPort("http://localhost"), false);
});
it("extracts the basic authentication", async () => {
assert.deepEqual(
HealthCheckUtils.extractBasicAuth("http://hello:world@localhost:8080"),
{ auth: "hello:world", url: "http://localhost:8080" }
);
});
it("returns true when the url is invalid", async () => {
assert.deepEqual(HealthCheckUtils.isUrlInvalid("http://"), true);
});
it("returns true when the url is invalid", async () => {
assert.deepEqual(HealthCheckUtils.isUrlInvalid("http://"), true);
});
it("returns false when the url is valid", async () => {
assert.deepEqual(HealthCheckUtils.isUrlInvalid("http://localhost:8080"), false);
});
it("returns false when the url is valid", async () => {
assert.deepEqual(
HealthCheckUtils.isUrlInvalid("http://localhost:8080"),
false
);
});
it("returns the tcp port", async () => {
const debug = {
"id": "a",
"addrs": [
"/ip4/127.0.0.1/tcp/8070"
],
"repo": "",
"spr": "",
"announceAddresses": [
"/ip4/127.0.0.1/tcp/8070"
],
"table": {
"localNode": {
"nodeId": "",
"peerId": "",
"record": "",
"address": "0.0.0.0:8090",
"seen": false
},
"nodes": []
},
"codex": {
"version": "v0.1.0\nv0.1.1\nv0.1.2\nv0.1.3\nv0.1.4\nv0.1.5\nv0.1.6\nv0.1.7",
"revision": "2fb7031e"
}
}
assert.deepEqual(HealthCheckUtils.getTcpPort(debug), { error: false, data: 8070 });
it("returns the tcp port", async () => {
const debug = {
id: "a",
addrs: ["/ip4/127.0.0.1/tcp/8070"],
repo: "",
spr: "",
announceAddresses: ["/ip4/127.0.0.1/tcp/8070"],
table: {
localNode: {
nodeId: "",
peerId: "",
record: "",
address: "0.0.0.0:8090",
seen: false,
},
nodes: [],
},
codex: {
version:
"v0.1.0\nv0.1.1\nv0.1.2\nv0.1.3\nv0.1.4\nv0.1.5\nv0.1.6\nv0.1.7",
revision: "2fb7031e",
},
};
assert.deepEqual(HealthCheckUtils.getTcpPort(debug), {
error: false,
data: 8070,
});
});
it("returns an error when the addr is empty", async () => {
const debug = {
"id": "a",
"addrs": [
],
"repo": "",
"spr": "",
"announceAddresses": [
"/ip4/127.0.0.1/tcp/8070"
],
"table": {
"localNode": {
"nodeId": "",
"peerId": "",
"record": "",
"address": "0.0.0.0:8090",
"seen": false
},
"nodes": []
},
"codex": {
"version": "v0.1.0\nv0.1.1\nv0.1.2\nv0.1.3\nv0.1.4\nv0.1.5\nv0.1.6\nv0.1.7",
"revision": "2fb7031e"
}
}
assert.deepEqual(HealthCheckUtils.getTcpPort(debug).error, true);
});
it("returns an error when the addr is empty", async () => {
const debug = {
id: "a",
addrs: [],
repo: "",
spr: "",
announceAddresses: ["/ip4/127.0.0.1/tcp/8070"],
table: {
localNode: {
nodeId: "",
peerId: "",
record: "",
address: "0.0.0.0:8090",
seen: false,
},
nodes: [],
},
codex: {
version:
"v0.1.0\nv0.1.1\nv0.1.2\nv0.1.3\nv0.1.4\nv0.1.5\nv0.1.6\nv0.1.7",
revision: "2fb7031e",
},
};
assert.deepEqual(HealthCheckUtils.getTcpPort(debug).error, true);
});
it("returns an error when the addr is misformated", async () => {
const debug = {
"id": "a",
"addrs": [
"/ip4/127.0.0.1/tcp/hello"
],
"repo": "",
"spr": "",
"announceAddresses": [
"/ip4/127.0.0.1/tcp/8070"
],
"table": {
"localNode": {
"nodeId": "",
"peerId": "",
"record": "",
"address": "0.0.0.0:8090",
"seen": false
},
"nodes": []
},
"codex": {
"version": "v0.1.0\nv0.1.1\nv0.1.2\nv0.1.3\nv0.1.4\nv0.1.5\nv0.1.6\nv0.1.7",
"revision": "2fb7031e"
}
}
assert.deepEqual(HealthCheckUtils.getTcpPort(debug).error, true);
});
it("returns an error when the addr is misformated", async () => {
const debug = {
id: "a",
addrs: ["/ip4/127.0.0.1/tcp/hello"],
repo: "",
spr: "",
announceAddresses: ["/ip4/127.0.0.1/tcp/8070"],
table: {
localNode: {
nodeId: "",
peerId: "",
record: "",
address: "0.0.0.0:8090",
seen: false,
},
nodes: [],
},
codex: {
version:
"v0.1.0\nv0.1.1\nv0.1.2\nv0.1.3\nv0.1.4\nv0.1.5\nv0.1.6\nv0.1.7",
revision: "2fb7031e",
},
};
assert.deepEqual(HealthCheckUtils.getTcpPort(debug).error, true);
});
it("returns an error when the port is misformated", async () => {
const debug = {
"id": "a",
"addrs": [
"hello"
],
"repo": "",
"spr": "",
"announceAddresses": [
"/ip4/127.0.0.1/tcp/8070"
],
"table": {
"localNode": {
"nodeId": "",
"peerId": "",
"record": "",
"address": "0.0.0.0:8090",
"seen": false
},
"nodes": []
},
"codex": {
"version": "v0.1.0\nv0.1.1\nv0.1.2\nv0.1.3\nv0.1.4\nv0.1.5\nv0.1.6\nv0.1.7",
"revision": "2fb7031e"
}
}
assert.deepEqual(HealthCheckUtils.getTcpPort(debug).error, true);
});
it("returns an error when the port is misformated", async () => {
const debug = {
id: "a",
addrs: ["hello"],
repo: "",
spr: "",
announceAddresses: ["/ip4/127.0.0.1/tcp/8070"],
table: {
localNode: {
nodeId: "",
peerId: "",
record: "",
address: "0.0.0.0:8090",
seen: false,
},
nodes: [],
},
codex: {
version:
"v0.1.0\nv0.1.1\nv0.1.2\nv0.1.3\nv0.1.4\nv0.1.5\nv0.1.6\nv0.1.7",
revision: "2fb7031e",
},
};
assert.deepEqual(HealthCheckUtils.getTcpPort(debug).error, true);
});
it("extracts the announced ip", async () => {
assert.deepEqual(HealthCheckUtils.extractAnnounceAddresses([
"/ip4/127.0.0.1/tcp/8070"
]).data, "127.0.0.1");
assert.deepEqual(HealthCheckUtils.extractAnnounceAddresses([]).error, true);
assert.deepEqual(HealthCheckUtils.extractAnnounceAddresses(["hello"]).error, true);
});
})
it("extracts the announced ip", async () => {
assert.deepEqual(
HealthCheckUtils.extractAnnounceAddresses(["/ip4/127.0.0.1/tcp/8070"])
.data,
"127.0.0.1"
);
assert.deepEqual(HealthCheckUtils.extractAnnounceAddresses([]).error, true);
assert.deepEqual(
HealthCheckUtils.extractAnnounceAddresses(["hello"]).error,
true
);
});
});

View File

@ -1,66 +1,83 @@
import { CodexDebugInfo, SafeValue, CodexError } from "@codex-storage/sdk-js"
import { CodexDebugInfo, SafeValue, CodexError } from "@codex-storage/sdk-js";
export const HealthCheckUtils = {
removePort(url: string) {
const parts = url.split(":")
return parts[0] + ":" + parts[1]
},
removePort(url: string) {
const parts = url.split(":");
return parts[0] + ":" + parts[1];
},
/*
* Extract the port from a protocol + ip + port string
*/
getPort(url: string) {
return parseInt(url.split(":")[2] || "80", 10)
},
containsPort(url: string) {
return url.split(":").length > 2
},
isUrlInvalid(url: string) {
try {
new URL(url)
return false
// We do not need to manage the error because we want to check
// if the URL is valid or not only.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (_) {
return true
}
},
extractAnnounceAddresses(announceAddresses: string[]): SafeValue<string> {
if (announceAddresses.length === 0) {
return { error: true, data: new CodexError("Not existing announce address") }
}
const ip = announceAddresses[0].split("/")
if (ip.length !== 5) {
return { error: true, data: new CodexError("Misformatted ip") }
}
return { error: false, data: ip[2] }
},
getTcpPort(info: CodexDebugInfo): SafeValue<number> {
if (info.addrs.length === 0) {
return { error: true, data: new CodexError("Not existing address") }
}
const parts = info.addrs[0].split("/")
if (parts.length < 2) {
return { error: true, data: new CodexError("Address misformated") }
}
const port = parseInt(parts[parts.length - 1], 10)
if (isNaN(port)) {
return { error: true, data: new CodexError("Port misformated") }
}
return { error: false, data: port }
extractBasicAuth(url: string) {
if (!url.includes("@")) {
return { auth: "", url };
}
}
const [prefix, rest] = url.split("@");
const [protocol, auth] = prefix.split("//");
return { auth, url: protocol + "//" + rest };
},
/*
* Extract the port from a protocol + ip + port string
*/
getPort(url: string) {
return url.split(":")[2] || "";
},
containsPort(url: string) {
if (url.includes("@")) {
return url.split(":").length > 3;
}
return url.split(":").length > 2;
},
isUrlInvalid(url: string) {
try {
new URL(url);
return false;
// We do not need to manage the error because we want to check
// if the URL is valid or not only.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (_) {
return true;
}
},
extractAnnounceAddresses(announceAddresses: string[]): SafeValue<string> {
if (announceAddresses.length === 0) {
return {
error: true,
data: new CodexError("Not existing announce address"),
};
}
const ip = announceAddresses[0].split("/");
if (ip.length !== 5) {
return { error: true, data: new CodexError("Misformatted ip") };
}
return { error: false, data: ip[2] };
},
getTcpPort(info: CodexDebugInfo): SafeValue<number> {
if (info.addrs.length === 0) {
return { error: true, data: new CodexError("Not existing address") };
}
const parts = info.addrs[0].split("/");
if (parts.length < 2) {
return { error: true, data: new CodexError("Address misformated") };
}
const port = parseInt(parts[parts.length - 1], 10);
if (isNaN(port)) {
return { error: true, data: new CodexError("Port misformated") };
}
return { error: false, data: port };
},
};

View File

@ -17,7 +17,6 @@ export function ManifestFetch() {
if (s.error === false) {
setCid("");
queryClient.invalidateQueries({ queryKey: ["cids"] });
console.info("Done");
}
return Promises.rejectOnError(s);
});

View File

@ -3,29 +3,31 @@ import { CodexSdk } from "../sdk/codex";
import { Promises } from "../utils/promises";
export function useDebug(throwOnError: boolean) {
const { data, isError, isPending, refetch, isSuccess, isFetching } = useQuery({
queryFn: () =>
CodexSdk.debug()
.info()
.then((s) => Promises.rejectOnError(s)),
const { data, isError, isPending, refetch, isSuccess, isFetching } = useQuery(
{
queryFn: () =>
CodexSdk.debug()
.info()
.then((s) => Promises.rejectOnError(s)),
queryKey: ["debug"],
queryKey: ["debug"],
// No need to retry because if the connection to the node
// is back again, all the queries will be invalidated.
retry: false,
// No need to retry because if the connection to the node
// is back again, all the queries will be invalidated.
retry: false,
// The client node should be local, so display the cache value while
// making a background request looks good.
staleTime: 0,
// The client node should be local, so display the cache value while
// making a background request looks good.
staleTime: 0,
// Refreshing when focus returns can be useful if a user comes back
// to the UI after performing an operation in the terminal.
refetchOnWindowFocus: true,
// Refreshing when focus returns can be useful if a user comes back
// to the UI after performing an operation in the terminal.
refetchOnWindowFocus: true,
// Throw the error to the error boundary
throwOnError,
});
// Throw the error to the error boundary
throwOnError,
}
);
return { data, isPending, isError, isSuccess, refetch, isFetching };
return { data, isPending, isError, isSuccess, refetch, isFetching };
}

View File

@ -135,7 +135,7 @@ if (rootElement) {
return;
}
return CodexSdk.updateURL(url);
return CodexSdk.updateURL(url, {});
}
}
}

View File

@ -7,41 +7,46 @@ import Logotype from "../../assets/icons/logotype.svg?react";
import Logo from "../../assets/icons/logo.svg?react";
import { Versions } from "../../components/Versions/Versions";
import { BackgroundImage } from "../../components/BackgroundImage/BackgroundImage";
import { useNetwork } from "../../network/useNetwork";
export const SettingsRoute = () => (
<div className="settings">
<header>
<div className="row gap">
<Logo height={48}></Logo>
<Logotype height={46}></Logotype>
</div>
<Versions></Versions>
</header>
<main>
<h3>Personalization</h3>
<ErrorBoundary
fallback={({ error }) => (
<ErrorPlaceholder
error={error}
subtitle="Cannot retrieve the data."
/>
)}>
<UserInfo />
</ErrorBoundary>
export const SettingsRoute = () => {
const online = useNetwork();
<h3>Connection</h3>
return (
<div className="settings">
<header>
<div className="row gap">
<Logo height={48}></Logo>
<Logotype height={46}></Logotype>
</div>
<Versions></Versions>
</header>
<main>
<h3>Personalization</h3>
<ErrorBoundary
fallback={({ error }) => (
<ErrorPlaceholder
error={error}
subtitle="Cannot retrieve the data."
/>
)}>
<UserInfo />
</ErrorBoundary>
<ErrorBoundary
fallback={({ error }) => (
<ErrorPlaceholder
error={error}
subtitle="Cannot retrieve the data."
/>
)}>
<HealthChecks online={true} onStepValid={() => {}} />
</ErrorBoundary>
</main>
<h3>Connection</h3>
<BackgroundImage />
</div>
);
<ErrorBoundary
fallback={({ error }) => (
<ErrorPlaceholder
error={error}
subtitle="Cannot retrieve the data."
/>
)}>
<HealthChecks online={online} onStepValid={() => {}} />
</ErrorBoundary>
</main>
<BackgroundImage />
</div>
);
};

View File

@ -4,38 +4,55 @@ import { WebStorage } from "../utils/web-storage";
let client: Codex = new Codex(import.meta.env.VITE_CODEX_API_URL);
let url: string = import.meta.env.VITE_CODEX_API_URL;
type CodexSdkUpdateOptions = {
auth?: {
basic?: string;
};
};
export const CodexSdk = {
url() {
return url;
},
load() {
return WebStorage.get<string>("codex-node-url").then((u) => {
url = u || import.meta.env.VITE_CODEX_API_URL;
client = new Codex(url);
});
async load() {
const [url = import.meta.env.VITE_CODEX_API_URL, basicAuthSecret] =
await Promise.all([
WebStorage.get<string>("codex-node-url"),
WebStorage.get<string>("codex-auth-basic"),
]);
client = new Codex(url, { auth: { basic: basicAuthSecret } });
},
updateURL(u: string) {
url = u;
client = new Codex(url);
updateURL(u: string, options: CodexSdkUpdateOptions) {
let basicAuthSecret: string = "";
return WebStorage.set("codex-node-url", url);
if (options.auth?.basic) {
basicAuthSecret = btoa(options.auth?.basic);
}
url = u;
client = new Codex(url, { auth: { basic: basicAuthSecret } });
return WebStorage.set("codex-auth-basic", basicAuthSecret).then(() =>
WebStorage.set("codex-node-url", url)
);
},
debug() {
return client.debug
return client.debug;
},
data() {
return client.data
return client.data;
},
node() {
return client.node
return client.node;
},
marketplace() {
return client.marketplace
return client.marketplace;
},
};