mirror of
https://github.com/logos-storage/logos-storage-marketplace-ui.git
synced 2026-01-02 13:33:06 +00:00
Support basic auth
This commit is contained in:
parent
d2be5a6ac2
commit
4b2e17907d
BIN
codex-storage-marketplace-ui-components-0.0.51.tgz
Normal file
BIN
codex-storage-marketplace-ui-components-0.0.51.tgz
Normal file
Binary file not shown.
BIN
codex-storage-sdk-js-0.1.1.tgz
Normal file
BIN
codex-storage-sdk-js-0.1.1.tgz
Normal file
Binary file not shown.
@ -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",
|
||||
|
||||
@ -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
26
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -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} />
|
||||
|
||||
@ -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
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -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 };
|
||||
},
|
||||
};
|
||||
|
||||
@ -17,7 +17,6 @@ export function ManifestFetch() {
|
||||
if (s.error === false) {
|
||||
setCid("");
|
||||
queryClient.invalidateQueries({ queryKey: ["cids"] });
|
||||
console.info("Done");
|
||||
}
|
||||
return Promises.rejectOnError(s);
|
||||
});
|
||||
|
||||
@ -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 };
|
||||
}
|
||||
|
||||
@ -135,7 +135,7 @@ if (rootElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
return CodexSdk.updateURL(url);
|
||||
return CodexSdk.updateURL(url, {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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;
|
||||
},
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user