mirror of
https://github.com/logos-storage/logos-storage-marketplace-ui.git
synced 2026-01-04 06:23:08 +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 expect(page.locator("#url")).toHaveValue("");
|
||||||
|
await page.locator("#url").blur();
|
||||||
await expect(page.locator("#url")).toHaveAttribute("aria-invalid");
|
await expect(page.locator("#url")).toHaveAttribute("aria-invalid");
|
||||||
await expect(page.locator(".refresh svg")).toHaveAttribute(
|
await expect(page.locator(".refresh svg")).toHaveAttribute(
|
||||||
"color",
|
"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();
|
// 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 }) => {
|
test("update the URL with wrong URL applies", async ({ page }) => {
|
||||||
await page.goto('/dashboard');
|
await page.goto("/dashboard");
|
||||||
await page.locator('a').filter({ hasText: 'Settings' }).click();
|
await page.locator("a").filter({ hasText: "Settings" }).click();
|
||||||
await page.getByLabel('Address').click();
|
await page.getByLabel("Address").click();
|
||||||
await page.getByLabel('Address').fill('hello');
|
await page.getByLabel("Address").fill("hello");
|
||||||
await expect(page.getByLabel('Address')).toHaveAttribute("aria-invalid")
|
await page.getByLabel("Address").blur();
|
||||||
await expect(page.locator(".refresh svg")).toHaveAttribute("color", "#494949")
|
await expect(page.getByLabel("Address")).toHaveAttribute("aria-invalid");
|
||||||
await page.getByLabel('Address').fill('http://127.0.0.1:8079');
|
await expect(page.locator(".refresh svg")).toHaveAttribute(
|
||||||
await expect(page.getByLabel('Address')).not.toHaveAttribute("aria-invalid")
|
"color",
|
||||||
await expect(page.locator(".refresh svg")).not.toHaveAttribute("aria-disabled")
|
"#494949"
|
||||||
await expect(page.getByLabel('Address')).toHaveValue("http://127.0.0.1")
|
);
|
||||||
await expect(page.getByLabel('Port')).toHaveValue("8079")
|
await page.getByLabel("Address").fill("http://127.0.0.1:8079");
|
||||||
await page.locator(".refresh").click()
|
await page.getByLabel("Address").blur();
|
||||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-error")).toBeVisible()
|
await expect(page.getByLabel("Address")).not.toHaveAttribute("aria-invalid");
|
||||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-success")).not.toBeVisible()
|
await expect(page.locator(".refresh svg")).not.toHaveAttribute(
|
||||||
await page.getByLabel('Address').fill('http://127.0.0.1:8080');
|
"aria-disabled"
|
||||||
await page.locator(".refresh").click()
|
);
|
||||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-error")).not.toBeVisible()
|
await expect(page.getByLabel("Address")).toHaveValue("http://127.0.0.1");
|
||||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-success")).toBeVisible()
|
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",
|
"version": "0.0.16",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codex-storage/marketplace-ui-components": "^0.0.51",
|
"@codex-storage/marketplace-ui-components": "./codex-storage-marketplace-ui-components-0.0.51.tgz",
|
||||||
"@codex-storage/sdk-js": "^0.0.23",
|
"@codex-storage/sdk-js": "./codex-storage-sdk-js-0.1.1.tgz",
|
||||||
"@sentry/browser": "^8.32.0",
|
"@sentry/browser": "^8.32.0",
|
||||||
"@sentry/react": "^8.31.0",
|
"@sentry/react": "^8.31.0",
|
||||||
"@tanstack/react-query": "^5.51.15",
|
"@tanstack/react-query": "^5.51.15",
|
||||||
@ -420,8 +420,8 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@codex-storage/marketplace-ui-components": {
|
"node_modules/@codex-storage/marketplace-ui-components": {
|
||||||
"version": "0.0.51",
|
"version": "0.0.51",
|
||||||
"resolved": "https://registry.npmjs.org/@codex-storage/marketplace-ui-components/-/marketplace-ui-components-0.0.51.tgz",
|
"resolved": "file:codex-storage-marketplace-ui-components-0.0.51.tgz",
|
||||||
"integrity": "sha512-KPPFlcpx3a83WCBSLbRONrF/yr4J/ctyTfFPxMaRSMTRD1LtfIE0uPy3QxtHs6tigOts2h4DEz6Kn2ynHdfKPg==",
|
"integrity": "sha512-rIgI20HA4CbccaxJPVZJw+QvoxDpY6g2FWX1HDChFqYqBcw+AP1h2gtVSau5cSZn6Zn/EQIgMlYZk+1m0U3utQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@ -433,15 +433,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codex-storage/sdk-js": {
|
"node_modules/@codex-storage/sdk-js": {
|
||||||
"version": "0.0.23",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@codex-storage/sdk-js/-/sdk-js-0.0.23.tgz",
|
"resolved": "file:codex-storage-sdk-js-0.1.1.tgz",
|
||||||
"integrity": "sha512-+ktJs396GERPudRh5zjTvOMjwo3mRHVYN901Qvs0q3YlRK983aewSqJ+Z7NttSQ27oxTcvxQVrilcvzZRHQTkg==",
|
"integrity": "sha512-BwCYirG0gwNJxxF0c3Zkrachi9FcYH6sLGUE9V07zIPaC0XVtiJiyUXhP0ciI28jgg2sFqk5nim/5ZmMaQmqBw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"undici": "^7.5.0",
|
||||||
"valibot": "^0.32.0"
|
"valibot": "^0.32.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20"
|
"node": ">=20.18.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@csstools/selector-resolve-nested": {
|
"node_modules/@csstools/selector-resolve-nested": {
|
||||||
@ -5173,6 +5174,15 @@
|
|||||||
"node": ">=14.17"
|
"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": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.20.0",
|
"version": "6.20.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||||
|
|||||||
@ -26,8 +26,8 @@
|
|||||||
"React"
|
"React"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codex-storage/marketplace-ui-components": "^0.0.51",
|
"@codex-storage/marketplace-ui-components": "./codex-storage-marketplace-ui-components-0.0.51.tgz",
|
||||||
"@codex-storage/sdk-js": "^0.0.23",
|
"@codex-storage/sdk-js": "./codex-storage-sdk-js-0.1.1.tgz",
|
||||||
"@sentry/browser": "^8.32.0",
|
"@sentry/browser": "^8.32.0",
|
||||||
"@sentry/react": "^8.31.0",
|
"@sentry/react": "^8.31.0",
|
||||||
"@tanstack/react-query": "^5.51.15",
|
"@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())
|
HealthCheckUtils.removePort(CodexSdk.url())
|
||||||
);
|
);
|
||||||
const [port, setPort] = useState(HealthCheckUtils.getPort(CodexSdk.url()));
|
const [port, setPort] = useState(HealthCheckUtils.getPort(CodexSdk.url()));
|
||||||
|
const [auth, setAuth] = useState("");
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
@ -46,23 +47,31 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
|||||||
[persistence.refetch, onStepValid, portForwarding.refetch, codex.isSuccess]
|
[persistence.refetch, onStepValid, portForwarding.refetch, codex.isSuccess]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onAddressChange = (e: React.FormEvent<HTMLInputElement>) => {
|
const onAddressBlur = (e: React.FormEvent<HTMLInputElement>) => {
|
||||||
const element = e.currentTarget;
|
const element = e.currentTarget;
|
||||||
const value = e.currentTarget.value;
|
const value = e.currentTarget.value;
|
||||||
|
|
||||||
setIsAddressInvalid(!element.checkValidity());
|
setIsAddressInvalid(!element.checkValidity());
|
||||||
|
|
||||||
setAddress(value);
|
const { auth, url } = HealthCheckUtils.extractBasicAuth(value);
|
||||||
|
|
||||||
if (HealthCheckUtils.containsPort(value)) {
|
setAddress(url);
|
||||||
const address = HealthCheckUtils.removePort(value);
|
|
||||||
|
if (HealthCheckUtils.containsPort(url)) {
|
||||||
|
const address = HealthCheckUtils.removePort(url);
|
||||||
setAddress(address);
|
setAddress(address);
|
||||||
|
|
||||||
const p = HealthCheckUtils.getPort(value);
|
const p = HealthCheckUtils.getPort(url);
|
||||||
setPort(p);
|
setPort(p);
|
||||||
} else {
|
} else {
|
||||||
setAddress(value);
|
setAddress(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAuth(auth || "");
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChange = (e: React.FormEvent<HTMLInputElement>) => {
|
||||||
|
setAddress(e.currentTarget.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPortChange = (e: React.FormEvent<HTMLInputElement>) => {
|
const onPortChange = (e: React.FormEvent<HTMLInputElement>) => {
|
||||||
@ -70,19 +79,25 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
|||||||
const value = element.value;
|
const value = element.value;
|
||||||
|
|
||||||
setIsPortInvalid(!element.checkValidity());
|
setIsPortInvalid(!element.checkValidity());
|
||||||
setPort(parseInt(value, 10));
|
setPort(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSave = () => {
|
const onSave = () => {
|
||||||
const url = address + ":" + port;
|
let url = address;
|
||||||
|
|
||||||
|
if (port) {
|
||||||
|
url += ":" + port;
|
||||||
|
}
|
||||||
|
|
||||||
if (HealthCheckUtils.isUrlInvalid(url)) {
|
if (HealthCheckUtils.isUrlInvalid(url)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CodexSdk.updateURL(url)
|
CodexSdk.updateURL(url, { auth: { basic: auth } })
|
||||||
.then(() => queryClient.invalidateQueries())
|
.then(() => queryClient.invalidateQueries())
|
||||||
.then(() => codex.refetch());
|
.then(() => codex.refetch());
|
||||||
|
|
||||||
|
setAuth("");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -98,9 +113,11 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
|||||||
type="url"
|
type="url"
|
||||||
label="Address"
|
label="Address"
|
||||||
required
|
required
|
||||||
|
mode={"manual"}
|
||||||
isInvalid={isAddressInvalid}
|
isInvalid={isAddressInvalid}
|
||||||
onChange={onAddressChange}
|
onBlur={onAddressBlur}
|
||||||
value={address}
|
value={address}
|
||||||
|
onChange={onChange}
|
||||||
placeholder="127.0.0.1"></Input>
|
placeholder="127.0.0.1"></Input>
|
||||||
{isAddressInvalid ? (
|
{isAddressInvalid ? (
|
||||||
<ErrorCircleIcon width={16} />
|
<ErrorCircleIcon width={16} />
|
||||||
|
|||||||
@ -2,155 +2,167 @@ import { assert, describe, it } from "vitest";
|
|||||||
import { HealthCheckUtils } from "./health-check.utils";
|
import { HealthCheckUtils } from "./health-check.utils";
|
||||||
|
|
||||||
describe("health check", () => {
|
describe("health check", () => {
|
||||||
it("remove the port from an url", async () => {
|
it("remove the port from an url", async () => {
|
||||||
assert.deepEqual(HealthCheckUtils.removePort("http://localhost:8080"), "http://localhost");
|
assert.deepEqual(
|
||||||
});
|
HealthCheckUtils.removePort("http://localhost:8080"),
|
||||||
|
"http://localhost"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("get the port from an url", async () => {
|
it("get the port from an url", async () => {
|
||||||
assert.deepEqual(HealthCheckUtils.getPort("http://localhost:8080"), 8080);
|
assert.deepEqual(HealthCheckUtils.getPort("http://localhost:8080"), "8080");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("get the default port when the url does not contain the port", async () => {
|
it("get the default port when the url does not contain the port", async () => {
|
||||||
assert.deepEqual(HealthCheckUtils.getPort("http://localhost"), 80);
|
assert.deepEqual(HealthCheckUtils.getPort("http://localhost"), "");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns true when the url contains a port", async () => {
|
it("returns true when the url contains a port", async () => {
|
||||||
assert.deepEqual(HealthCheckUtils.containsPort("http://localhost:8080"), true);
|
assert.deepEqual(
|
||||||
});
|
HealthCheckUtils.containsPort("http://localhost:8080"),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("returns false when the url does not contain a port", async () => {
|
it("returns false when the url does not contain a port", async () => {
|
||||||
assert.deepEqual(HealthCheckUtils.containsPort("http://localhost"), false);
|
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 () => {
|
it("returns true when the url is invalid", async () => {
|
||||||
assert.deepEqual(HealthCheckUtils.isUrlInvalid("http://"), true);
|
assert.deepEqual(HealthCheckUtils.isUrlInvalid("http://"), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns false when the url is valid", async () => {
|
it("returns false when the url is valid", async () => {
|
||||||
assert.deepEqual(HealthCheckUtils.isUrlInvalid("http://localhost:8080"), false);
|
assert.deepEqual(
|
||||||
});
|
HealthCheckUtils.isUrlInvalid("http://localhost:8080"),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("returns the tcp port", async () => {
|
it("returns the tcp port", async () => {
|
||||||
const debug = {
|
const debug = {
|
||||||
"id": "a",
|
id: "a",
|
||||||
"addrs": [
|
addrs: ["/ip4/127.0.0.1/tcp/8070"],
|
||||||
"/ip4/127.0.0.1/tcp/8070"
|
repo: "",
|
||||||
],
|
spr: "",
|
||||||
"repo": "",
|
announceAddresses: ["/ip4/127.0.0.1/tcp/8070"],
|
||||||
"spr": "",
|
table: {
|
||||||
"announceAddresses": [
|
localNode: {
|
||||||
"/ip4/127.0.0.1/tcp/8070"
|
nodeId: "",
|
||||||
],
|
peerId: "",
|
||||||
"table": {
|
record: "",
|
||||||
"localNode": {
|
address: "0.0.0.0:8090",
|
||||||
"nodeId": "",
|
seen: false,
|
||||||
"peerId": "",
|
},
|
||||||
"record": "",
|
nodes: [],
|
||||||
"address": "0.0.0.0:8090",
|
},
|
||||||
"seen": false
|
codex: {
|
||||||
},
|
version:
|
||||||
"nodes": []
|
"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",
|
||||||
"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,
|
||||||
assert.deepEqual(HealthCheckUtils.getTcpPort(debug), { error: false, data: 8070 });
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("returns an error when the addr is empty", async () => {
|
it("returns an error when the addr is empty", async () => {
|
||||||
const debug = {
|
const debug = {
|
||||||
"id": "a",
|
id: "a",
|
||||||
"addrs": [
|
addrs: [],
|
||||||
],
|
repo: "",
|
||||||
"repo": "",
|
spr: "",
|
||||||
"spr": "",
|
announceAddresses: ["/ip4/127.0.0.1/tcp/8070"],
|
||||||
"announceAddresses": [
|
table: {
|
||||||
"/ip4/127.0.0.1/tcp/8070"
|
localNode: {
|
||||||
],
|
nodeId: "",
|
||||||
"table": {
|
peerId: "",
|
||||||
"localNode": {
|
record: "",
|
||||||
"nodeId": "",
|
address: "0.0.0.0:8090",
|
||||||
"peerId": "",
|
seen: false,
|
||||||
"record": "",
|
},
|
||||||
"address": "0.0.0.0:8090",
|
nodes: [],
|
||||||
"seen": false
|
},
|
||||||
},
|
codex: {
|
||||||
"nodes": []
|
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",
|
||||||
"codex": {
|
revision: "2fb7031e",
|
||||||
"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);
|
||||||
}
|
});
|
||||||
assert.deepEqual(HealthCheckUtils.getTcpPort(debug).error, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns an error when the addr is misformated", async () => {
|
it("returns an error when the addr is misformated", async () => {
|
||||||
const debug = {
|
const debug = {
|
||||||
"id": "a",
|
id: "a",
|
||||||
"addrs": [
|
addrs: ["/ip4/127.0.0.1/tcp/hello"],
|
||||||
"/ip4/127.0.0.1/tcp/hello"
|
repo: "",
|
||||||
],
|
spr: "",
|
||||||
"repo": "",
|
announceAddresses: ["/ip4/127.0.0.1/tcp/8070"],
|
||||||
"spr": "",
|
table: {
|
||||||
"announceAddresses": [
|
localNode: {
|
||||||
"/ip4/127.0.0.1/tcp/8070"
|
nodeId: "",
|
||||||
],
|
peerId: "",
|
||||||
"table": {
|
record: "",
|
||||||
"localNode": {
|
address: "0.0.0.0:8090",
|
||||||
"nodeId": "",
|
seen: false,
|
||||||
"peerId": "",
|
},
|
||||||
"record": "",
|
nodes: [],
|
||||||
"address": "0.0.0.0:8090",
|
},
|
||||||
"seen": false
|
codex: {
|
||||||
},
|
version:
|
||||||
"nodes": []
|
"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",
|
||||||
"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);
|
||||||
}
|
});
|
||||||
}
|
|
||||||
assert.deepEqual(HealthCheckUtils.getTcpPort(debug).error, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns an error when the port is misformated", async () => {
|
it("returns an error when the port is misformated", async () => {
|
||||||
const debug = {
|
const debug = {
|
||||||
"id": "a",
|
id: "a",
|
||||||
"addrs": [
|
addrs: ["hello"],
|
||||||
"hello"
|
repo: "",
|
||||||
],
|
spr: "",
|
||||||
"repo": "",
|
announceAddresses: ["/ip4/127.0.0.1/tcp/8070"],
|
||||||
"spr": "",
|
table: {
|
||||||
"announceAddresses": [
|
localNode: {
|
||||||
"/ip4/127.0.0.1/tcp/8070"
|
nodeId: "",
|
||||||
],
|
peerId: "",
|
||||||
"table": {
|
record: "",
|
||||||
"localNode": {
|
address: "0.0.0.0:8090",
|
||||||
"nodeId": "",
|
seen: false,
|
||||||
"peerId": "",
|
},
|
||||||
"record": "",
|
nodes: [],
|
||||||
"address": "0.0.0.0:8090",
|
},
|
||||||
"seen": false
|
codex: {
|
||||||
},
|
version:
|
||||||
"nodes": []
|
"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",
|
||||||
"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);
|
||||||
}
|
});
|
||||||
}
|
|
||||||
assert.deepEqual(HealthCheckUtils.getTcpPort(debug).error, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("extracts the announced ip", async () => {
|
it("extracts the announced ip", async () => {
|
||||||
assert.deepEqual(HealthCheckUtils.extractAnnounceAddresses([
|
assert.deepEqual(
|
||||||
"/ip4/127.0.0.1/tcp/8070"
|
HealthCheckUtils.extractAnnounceAddresses(["/ip4/127.0.0.1/tcp/8070"])
|
||||||
]).data, "127.0.0.1");
|
.data,
|
||||||
assert.deepEqual(HealthCheckUtils.extractAnnounceAddresses([]).error, true);
|
"127.0.0.1"
|
||||||
assert.deepEqual(HealthCheckUtils.extractAnnounceAddresses(["hello"]).error, true);
|
);
|
||||||
});
|
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 = {
|
export const HealthCheckUtils = {
|
||||||
removePort(url: string) {
|
removePort(url: string) {
|
||||||
const parts = url.split(":")
|
const parts = url.split(":");
|
||||||
return parts[0] + ":" + parts[1]
|
return parts[0] + ":" + parts[1];
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
extractBasicAuth(url: string) {
|
||||||
* Extract the port from a protocol + ip + port string
|
if (!url.includes("@")) {
|
||||||
*/
|
return { auth: "", url };
|
||||||
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 }
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
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) {
|
if (s.error === false) {
|
||||||
setCid("");
|
setCid("");
|
||||||
queryClient.invalidateQueries({ queryKey: ["cids"] });
|
queryClient.invalidateQueries({ queryKey: ["cids"] });
|
||||||
console.info("Done");
|
|
||||||
}
|
}
|
||||||
return Promises.rejectOnError(s);
|
return Promises.rejectOnError(s);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,29 +3,31 @@ import { CodexSdk } from "../sdk/codex";
|
|||||||
import { Promises } from "../utils/promises";
|
import { Promises } from "../utils/promises";
|
||||||
|
|
||||||
export function useDebug(throwOnError: boolean) {
|
export function useDebug(throwOnError: boolean) {
|
||||||
const { data, isError, isPending, refetch, isSuccess, isFetching } = useQuery({
|
const { data, isError, isPending, refetch, isSuccess, isFetching } = useQuery(
|
||||||
queryFn: () =>
|
{
|
||||||
CodexSdk.debug()
|
queryFn: () =>
|
||||||
.info()
|
CodexSdk.debug()
|
||||||
.then((s) => Promises.rejectOnError(s)),
|
.info()
|
||||||
|
.then((s) => Promises.rejectOnError(s)),
|
||||||
|
|
||||||
queryKey: ["debug"],
|
queryKey: ["debug"],
|
||||||
|
|
||||||
// No need to retry because if the connection to the node
|
// No need to retry because if the connection to the node
|
||||||
// is back again, all the queries will be invalidated.
|
// is back again, all the queries will be invalidated.
|
||||||
retry: false,
|
retry: false,
|
||||||
|
|
||||||
// The client node should be local, so display the cache value while
|
// The client node should be local, so display the cache value while
|
||||||
// making a background request looks good.
|
// making a background request looks good.
|
||||||
staleTime: 0,
|
staleTime: 0,
|
||||||
|
|
||||||
// Refreshing when focus returns can be useful if a user comes back
|
// Refreshing when focus returns can be useful if a user comes back
|
||||||
// to the UI after performing an operation in the terminal.
|
// to the UI after performing an operation in the terminal.
|
||||||
refetchOnWindowFocus: true,
|
refetchOnWindowFocus: true,
|
||||||
|
|
||||||
// Throw the error to the error boundary
|
// Throw the error to the error boundary
|
||||||
throwOnError,
|
throwOnError,
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return { data, isPending, isError, isSuccess, refetch, isFetching };
|
return { data, isPending, isError, isSuccess, refetch, isFetching };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -135,7 +135,7 @@ if (rootElement) {
|
|||||||
return;
|
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 Logo from "../../assets/icons/logo.svg?react";
|
||||||
import { Versions } from "../../components/Versions/Versions";
|
import { Versions } from "../../components/Versions/Versions";
|
||||||
import { BackgroundImage } from "../../components/BackgroundImage/BackgroundImage";
|
import { BackgroundImage } from "../../components/BackgroundImage/BackgroundImage";
|
||||||
|
import { useNetwork } from "../../network/useNetwork";
|
||||||
|
|
||||||
export const SettingsRoute = () => (
|
export const SettingsRoute = () => {
|
||||||
<div className="settings">
|
const online = useNetwork();
|
||||||
<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>
|
|
||||||
|
|
||||||
<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
|
<h3>Connection</h3>
|
||||||
fallback={({ error }) => (
|
|
||||||
<ErrorPlaceholder
|
|
||||||
error={error}
|
|
||||||
subtitle="Cannot retrieve the data."
|
|
||||||
/>
|
|
||||||
)}>
|
|
||||||
<HealthChecks online={true} onStepValid={() => {}} />
|
|
||||||
</ErrorBoundary>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<BackgroundImage />
|
<ErrorBoundary
|
||||||
</div>
|
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 client: Codex = new Codex(import.meta.env.VITE_CODEX_API_URL);
|
||||||
let url: string = 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 = {
|
export const CodexSdk = {
|
||||||
url() {
|
url() {
|
||||||
return url;
|
return url;
|
||||||
},
|
},
|
||||||
|
|
||||||
load() {
|
async load() {
|
||||||
return WebStorage.get<string>("codex-node-url").then((u) => {
|
const [url = import.meta.env.VITE_CODEX_API_URL, basicAuthSecret] =
|
||||||
url = u || import.meta.env.VITE_CODEX_API_URL;
|
await Promise.all([
|
||||||
client = new Codex(url);
|
WebStorage.get<string>("codex-node-url"),
|
||||||
});
|
WebStorage.get<string>("codex-auth-basic"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
client = new Codex(url, { auth: { basic: basicAuthSecret } });
|
||||||
},
|
},
|
||||||
|
|
||||||
updateURL(u: string) {
|
updateURL(u: string, options: CodexSdkUpdateOptions) {
|
||||||
url = u;
|
let basicAuthSecret: string = "";
|
||||||
client = new Codex(url);
|
|
||||||
|
|
||||||
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() {
|
debug() {
|
||||||
return client.debug
|
return client.debug;
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return client.data
|
return client.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
node() {
|
node() {
|
||||||
return client.node
|
return client.node;
|
||||||
},
|
},
|
||||||
|
|
||||||
marketplace() {
|
marketplace() {
|
||||||
return client.marketplace
|
return client.marketplace;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user