mirror of
https://github.com/logos-storage/logos-storage-marketplace-ui.git
synced 2026-01-02 13:33:06 +00:00
Add unit tests and fix minor issues
This commit is contained in:
parent
3260ae5839
commit
29ff705f58
10
.github/workflows/playwright.yml
vendored
10
.github/workflows/playwright.yml
vendored
@ -112,16 +112,6 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
# - uses: actions/cache@v4
|
|
||||||
# id: playwright-browsers-cache
|
|
||||||
# with:
|
|
||||||
# path: ~/.cache/ms-playwright
|
|
||||||
# key: ${{ runner.os }}-node-${{ hashFiles('**/playwright.config.ts') }}
|
|
||||||
|
|
||||||
# - if: steps.playwright-browsers-cache.outputs.cache-hit != 'true'
|
|
||||||
# name: Install Playwright Browsers
|
|
||||||
# run: npx playwright install --with-deps
|
|
||||||
|
|
||||||
- name: Install Playwright Browsers
|
- name: Install Playwright Browsers
|
||||||
run: npx playwright install --with-deps
|
run: npx playwright install --with-deps
|
||||||
|
|
||||||
|
|||||||
37
.github/workflows/unit-testing.yml
vendored
Normal file
37
.github/workflows/unit-testing.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
name: Unit Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: *
|
||||||
|
pull_request:
|
||||||
|
branches: *
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
VITE_CODEX_API_URL: ${{ secrets.VITE_CODEX_API_URL }}
|
||||||
|
VITE_GEO_IP_URL: ${{ secrets.VITE_GEO_IP_URL }}
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
timeout-minutes: 60
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Install dependencies
|
||||||
|
run: sudo apt update
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
id: npm-cache
|
||||||
|
with:
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run unit tests
|
||||||
|
run: npm run test:unit
|
||||||
@ -1,10 +1,9 @@
|
|||||||
import test, { expect } from "@playwright/test";
|
import test, { expect } from "@playwright/test";
|
||||||
|
|
||||||
test('create an availability', async ({ page }) => {
|
test('create an availability', async ({ page }) => {
|
||||||
await page.goto('/dashboard');
|
await page.goto('/dashboard/availabilities');
|
||||||
await page.getByRole('link', { name: 'Sales' }).click();
|
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
await page.getByRole('button').first().click();
|
await page.locator('.availabilities-create').first().click();
|
||||||
await page.getByLabel('Total size').click();
|
await page.getByLabel('Total size').click();
|
||||||
await page.getByLabel('Total size').fill('0.50');
|
await page.getByLabel('Total size').fill('0.50');
|
||||||
await page.getByLabel('Duration').click();
|
await page.getByLabel('Duration').click();
|
||||||
@ -18,7 +17,7 @@ test('create an availability', async ({ page }) => {
|
|||||||
await page.getByLabel('Nickname').fill('test');
|
await page.getByLabel('Nickname').fill('test');
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await page.getByRole('button', { name: 'Next' }).click();
|
||||||
await expect(page.getByText('Confirm your new sale')).toBeVisible();
|
await expect(page.getByText('Confirm your new sale')).toBeVisible();
|
||||||
await page.locator('small').filter({ hasText: /^512\.0 MB$/ }).click();
|
await expect(page.getByText('512.0 MB').first()).toBeVisible();
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await page.getByRole('button', { name: 'Next' }).click();
|
||||||
await expect(page.getByText('Success', { exact: true })).toBeVisible();
|
await expect(page.getByText('Success', { exact: true })).toBeVisible();
|
||||||
await page.getByRole('button', { name: 'Finish' }).click();
|
await page.getByRole('button', { name: 'Finish' }).click();
|
||||||
@ -28,31 +27,31 @@ test('create an availability', async ({ page }) => {
|
|||||||
test('availability navigation buttons', async ({ page }) => {
|
test('availability navigation buttons', async ({ page }) => {
|
||||||
await page.goto('/dashboard/availabilities');
|
await page.goto('/dashboard/availabilities');
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
await page.getByRole('button').first().click();
|
await page.locator('.availabilities-create').first().click();
|
||||||
await expect(page.locator('.stepper-number-done')).not.toBeVisible()
|
await expect(page.locator('.stepper-number-done')).not.toBeVisible()
|
||||||
await expect(page.locator('.stepper-number-active')).toBeVisible()
|
await expect(page.locator('.step--active')).toBeVisible()
|
||||||
await expect(page.locator('.stepper-buttons .button--primary')).not.toHaveAttribute("disabled");
|
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||||
await expect(page.locator('.stepper-buttons .button--outline')).not.toHaveAttribute("disabled");
|
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
||||||
await page.getByLabel('Total size').click();
|
await page.getByLabel('Total size').click();
|
||||||
await page.getByLabel('Total size').fill('19');
|
await page.getByLabel('Total size').fill('19');
|
||||||
await expect(page.locator('.stepper-buttons .button--outline')).not.toHaveAttribute("disabled");
|
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
||||||
await expect(page.locator('.stepper-buttons .button--primary')).toHaveAttribute("disabled");
|
await expect(page.locator('footer .button--primary')).toHaveAttribute("disabled");
|
||||||
await page.getByLabel('Total size').click();
|
await page.getByLabel('Total size').click();
|
||||||
await page.getByLabel('Total size').fill('0.5');
|
await page.getByLabel('Total size').fill('0.5');
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await page.getByRole('button', { name: 'Next' }).click();
|
||||||
await expect(page.locator('.stepper-buttons .button--outline')).not.toHaveAttribute("disabled");
|
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
||||||
await expect(page.locator('.stepper-buttons .button--primary')).not.toHaveAttribute("disabled");
|
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||||
await expect(page.locator('.stepper-number-done')).toBeVisible()
|
await expect(page.locator('.step--done')).toBeVisible()
|
||||||
await expect(page.locator('.stepper-number-active')).toBeVisible()
|
await expect(page.locator('.step--active')).toBeVisible()
|
||||||
await page.getByRole('button', { name: 'Back' }).click();
|
await page.getByRole('button', { name: 'Back' }).click();
|
||||||
await expect(page.locator('.stepper-number-done')).not.toBeVisible()
|
await expect(page.locator('.step--done')).not.toBeVisible()
|
||||||
await expect(page.locator('.stepper-number-active')).toBeVisible()
|
await expect(page.locator('.step--active')).toBeVisible()
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await page.getByRole('button', { name: 'Next' }).click();
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await page.getByRole('button', { name: 'Next' }).click();
|
||||||
await expect(page.locator('.stepper-number-done')).toHaveCount(2)
|
await expect(page.locator('.step--done')).toHaveCount(2)
|
||||||
await expect(page.locator('.stepper-number-active')).toBeVisible()
|
await expect(page.locator('.step--active')).toBeVisible()
|
||||||
await expect(page.locator('.stepper-buttons .button--outline')).toHaveAttribute("disabled");
|
await expect(page.locator('footer .button--outline').first()).toHaveAttribute("disabled");
|
||||||
await expect(page.locator('.stepper-buttons .button--primary')).not.toHaveAttribute("disabled");
|
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||||
await page.getByRole('button', { name: 'Finish' }).click();
|
await page.getByRole('button', { name: 'Finish' }).click();
|
||||||
await expect(page.locator('.modal--open')).not.toBeVisible();
|
await expect(page.locator('.modal--open')).not.toBeVisible();
|
||||||
})
|
})
|
||||||
|
|||||||
@ -19,9 +19,9 @@ test('download a file', async ({ page, browserName }) => {
|
|||||||
await page.getByRole('button', { name: 'Copy CID' }).click();
|
await page.getByRole('button', { name: 'Copy CID' }).click();
|
||||||
const handle = await page.evaluateHandle(() => navigator.clipboard.readText());
|
const handle = await page.evaluateHandle(() => navigator.clipboard.readText());
|
||||||
const cid = await handle.jsonValue()
|
const cid = await handle.jsonValue()
|
||||||
await page.locator('.sheets-container > .backdrop').click();
|
await page.locator('.sheets > .backdrop').click();
|
||||||
await page.getByPlaceholder('CID').click();
|
await page.locator('.download-input input').click();
|
||||||
await page.getByPlaceholder('CID').fill(cid);
|
await page.locator('.download-input input').fill(cid);
|
||||||
// const page1Promise = page.waitForEvent('popup');
|
// const page1Promise = page.waitForEvent('popup');
|
||||||
const downloadPromise = page.waitForEvent('download');
|
const downloadPromise = page.waitForEvent('download');
|
||||||
await page.locator('div').filter({ hasText: /^Download a fileDownload$/ }).getByRole('button').click();
|
await page.locator('div').filter({ hasText: /^Download a fileDownload$/ }).getByRole('button').click();
|
||||||
|
|||||||
@ -3,17 +3,39 @@ import { test, expect } from '@playwright/test';
|
|||||||
test('onboarding steps', async ({ page }) => {
|
test('onboarding steps', async ({ page }) => {
|
||||||
await page.context().setOffline(false)
|
await page.context().setOffline(false)
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
await expect(page.locator('#root')).toContainText('Network connected');
|
await expect(page.getByText("Codex is a durable, decentralised data storage protocol, created so the world community can preserve its most important knowledge without risk of censorship.")).toBeVisible()
|
||||||
await page.locator('a').nth(2).click();
|
await page.locator('.navigation').click();
|
||||||
|
await expect(page.locator('.navigation')).toHaveAttribute("aria-disabled");
|
||||||
|
await page.getByLabel('Preferred name').fill('Arnaud');
|
||||||
|
await expect(page.locator('.navigation')).not.toHaveAttribute("aria-disabled");
|
||||||
|
await page.locator('.navigation').click();
|
||||||
|
|
||||||
|
// Network
|
||||||
|
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-error")).not.toBeVisible()
|
||||||
|
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-success")).toBeVisible()
|
||||||
|
|
||||||
|
// Port forwarding
|
||||||
|
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()
|
||||||
|
|
||||||
|
// Codex node
|
||||||
|
await expect(page.locator(".health-checks ul li").nth(3).getByTestId("icon-error")).not.toBeVisible()
|
||||||
|
await expect(page.locator(".health-checks ul li").nth(3).getByTestId("icon-success")).toBeVisible()
|
||||||
|
|
||||||
|
// Marketplace
|
||||||
|
await expect(page.locator(".health-checks ul li").nth(4).getByTestId("icon-error")).not.toBeVisible()
|
||||||
|
await expect(page.locator(".health-checks ul li").nth(4).getByTestId("icon-success")).toBeVisible()
|
||||||
|
|
||||||
await page.context().setOffline(true)
|
await page.context().setOffline(true)
|
||||||
await expect(page.locator('#root')).toContainText('Network disconnected');
|
|
||||||
await page.getByLabel('Display name').click();
|
// Network
|
||||||
await page.getByLabel('Display name').fill('Arnaud');
|
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-error")).toBeVisible()
|
||||||
await page.locator('a').click();
|
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-success")).not.toBeVisible()
|
||||||
await page.locator('div').filter({ hasText: /^Internet connectionStatus indicator for the Internet\.$/ }).first().click();
|
|
||||||
await expect(page.getByTestId("network").locator(".onboarding-check-icon--valid")).not.toBeInViewport()
|
|
||||||
await expect(page.getByTestId("network").locator(".onboarding-check-icon--invalid")).toBeInViewport()
|
|
||||||
await page.context().setOffline(false)
|
await page.context().setOffline(false)
|
||||||
await expect(page.getByTestId("network").locator(".onboarding-check-icon--valid")).toBeInViewport()
|
});
|
||||||
await expect(page.getByTestId("network").locator(".onboarding-check-icon--invalid")).not.toBeInViewport()
|
|
||||||
});
|
// await expect(page.locator('#root')).toContainText('Network connected');
|
||||||
|
// await page.locator('a').nth(2).click();
|
||||||
|
// await page.context().setOffline(true)
|
||||||
|
// await expect(page.locator('#root')).toContainText('Network disconnected');
|
||||||
@ -1,25 +1,30 @@
|
|||||||
import test, { expect } from "@playwright/test";
|
import test, { expect } from "@playwright/test";
|
||||||
|
|
||||||
test('update the log level', async ({ page }) => {
|
// test('update the log level', async ({ page }) => {
|
||||||
await page.goto('/dashboard');
|
// await page.goto('/dashboard');
|
||||||
await page.getByRole('link', { name: 'Settings' }).click();
|
// await page.getByRole('link', { name: 'Settings' }).click();
|
||||||
await page.getByLabel('Log level').selectOption('TRACE');
|
// await page.getByLabel('Log level').selectOption('TRACE');
|
||||||
await page.getByRole('main').locator('div').filter({ hasText: 'Log' }).getByRole('button').click();
|
// await page.getByRole('main').locator('div').filter({ hasText: 'Log' }).getByRole('button').click();
|
||||||
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.getByRole('link', { name: 'Settings' }).click();
|
await page.locator('a').filter({ hasText: 'Settings' }).click();
|
||||||
await page.getByLabel('Codex client node URL').click();
|
await page.getByLabel('Address').click();
|
||||||
await page.getByLabel('Codex client node URL').fill('hello');
|
await page.getByLabel('Address').fill('hello');
|
||||||
await expect.soft(page.getByText("The URL is not valid")).toBeVisible()
|
await expect(page.getByLabel('Address')).toHaveAttribute("aria-invalid")
|
||||||
await expect.soft(page.locator(".settings-url-button")).toBeDisabled()
|
await expect(page.locator(".refresh svg")).toHaveAttribute("aria-disabled")
|
||||||
await page.getByLabel('Codex client node URL').fill('http://127.0.0.1:8079');
|
await page.getByLabel('Address').fill('http://127.0.0.1:8079');
|
||||||
await expect.soft(page.getByText("The URL is not valid")).not.toBeVisible()
|
await expect(page.getByLabel('Address')).not.toHaveAttribute("aria-invalid")
|
||||||
await expect.soft(page.locator(".settings-url-button")).not.toBeDisabled()
|
await expect(page.locator(".refresh svg")).not.toHaveAttribute("aria-disabled")
|
||||||
await page.getByRole('button', { name: 'Save changes' }).nth(1).click();
|
await expect(page.getByLabel('Address')).toHaveValue("http://127.0.0.1")
|
||||||
await expect.soft(page.getByText("Cannot retrieve the data")).toBeVisible()
|
await expect(page.getByLabel('Port')).toHaveValue("8079")
|
||||||
await page.getByLabel('Codex client node URL').fill('http://127.0.0.1:8080');
|
await page.locator(".refresh").click()
|
||||||
await page.getByRole('button', { name: 'Save changes' }).nth(1).click();
|
await expect(page.locator(".health-checks ul li").nth(3).getByTestId("icon-error")).toBeVisible()
|
||||||
|
await expect(page.locator(".health-checks ul li").nth(3).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(3).getByTestId("icon-error")).not.toBeVisible()
|
||||||
|
await expect(page.locator(".health-checks ul li").nth(3).getByTestId("icon-success")).toBeVisible()
|
||||||
})
|
})
|
||||||
@ -7,12 +7,13 @@ const __dirname = dirname(__filename);
|
|||||||
|
|
||||||
test('create a storage request', async ({ page }) => {
|
test('create a storage request', async ({ page }) => {
|
||||||
await page.goto('/dashboard');
|
await page.goto('/dashboard');
|
||||||
await page.getByRole('link', { name: 'Purchases' }).click();
|
await page.locator('a').filter({ hasText: 'Purchases' }).click();
|
||||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
await page.getByRole('button', { name: 'Storage Request' }).click();
|
||||||
|
|
||||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
await page.locator('div').getByTestId("upload").setInputFiles([
|
||||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||||
]);
|
]);
|
||||||
await expect(page.locator('#cid')).toHaveValue("zDvZRwzkvwapyNeL4mzw5gBsZvyn7x8F8Y9n4RYSC7ETBssDYpGe")
|
await expect(page.locator('#cid')).not.toBeEmpty()
|
||||||
await expect(page.getByText('Success, the CID has been')).toBeVisible();
|
await expect(page.getByText('Success, the CID has been')).toBeVisible();
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await page.getByRole('button', { name: 'Next' }).click();
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await page.getByRole('button', { name: 'Next' }).click();
|
||||||
@ -27,51 +28,51 @@ test('select a uploaded cid when creating a storage request', async ({ page }) =
|
|||||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
await page.locator('div').getByTestId("upload").setInputFiles([
|
||||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||||
]);
|
]);
|
||||||
await page.getByRole('link', { name: 'Purchases' }).click();
|
await page.locator('a').filter({ hasText: 'Purchases' }).click();
|
||||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
await page.getByRole('button', { name: 'Storage Request' }).click();
|
||||||
await page.getByPlaceholder('Select or type your CID').click();
|
await page.getByPlaceholder('Select or type your CID').click();
|
||||||
await page.locator('.dropdown-option').nth(1).click();
|
await page.locator('.dropdown ul li').nth(1).click();
|
||||||
await expect(page.getByText('button[disabled]')).not.toBeVisible();
|
await expect(page.getByText('button[disabled]')).not.toBeVisible();
|
||||||
})
|
})
|
||||||
|
|
||||||
test('storage request navigation buttons', async ({ page }) => {
|
test('storage request navigation buttons', async ({ page }) => {
|
||||||
await page.goto('/dashboard/purchases');
|
await page.goto('/dashboard/purchases');
|
||||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
await page.getByRole('button', { name: 'Storage Request' }).click();
|
||||||
await expect(page.locator('.stepper-number-done')).not.toBeVisible()
|
await expect(page.locator('.step--done')).not.toBeVisible()
|
||||||
await expect(page.locator('.stepper-number-active')).toBeVisible()
|
await expect(page.locator('.step--active')).toBeVisible()
|
||||||
await expect(page.locator('.stepper-buttons .button--primary')).toHaveAttribute("disabled");
|
await expect(page.locator('footer .button--primary')).toHaveAttribute("disabled");
|
||||||
await expect(page.locator('.stepper-buttons .button--outline')).not.toHaveAttribute("disabled");
|
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
||||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
await page.locator('div').getByTestId("upload").setInputFiles([
|
||||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||||
]);
|
]);
|
||||||
await expect(page.locator('.stepper-buttons .button--outline')).not.toHaveAttribute("disabled");
|
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
||||||
await expect(page.locator('.stepper-buttons .button--primary')).not.toHaveAttribute("disabled");
|
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await page.getByRole('button', { name: 'Next' }).click();
|
||||||
await expect(page.locator('.stepper-buttons .button--outline')).not.toHaveAttribute("disabled");
|
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
||||||
await expect(page.locator('.stepper-buttons .button--primary')).not.toHaveAttribute("disabled");
|
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||||
await expect(page.locator('.stepper-number-done')).toBeVisible()
|
await expect(page.locator('.step--done')).toBeVisible()
|
||||||
await expect(page.locator('.stepper-number-active')).toBeVisible()
|
await expect(page.locator('.step--active')).toBeVisible()
|
||||||
await page.getByRole('button', { name: 'Back' }).click();
|
await page.getByRole('button', { name: 'Back' }).click();
|
||||||
await expect(page.locator('.stepper-number-done')).not.toBeVisible()
|
await expect(page.locator('.step--done')).not.toBeVisible()
|
||||||
await expect(page.locator('.stepper-number-active')).toBeVisible()
|
await expect(page.locator('.step--active')).toBeVisible()
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await page.getByRole('button', { name: 'Next' }).click();
|
||||||
await page.getByRole('button', { name: 'Next' }).click();
|
await page.getByRole('button', { name: 'Next' }).click();
|
||||||
await expect(page.locator('.stepper-number-done')).toHaveCount(2)
|
await expect(page.locator('.step--done')).toHaveCount(2)
|
||||||
await expect(page.locator('.stepper-number-active')).toBeVisible()
|
await expect(page.locator('.step--active')).toBeVisible()
|
||||||
await expect(page.locator('.stepper-buttons .button--outline')).toHaveAttribute("disabled");
|
await expect(page.locator('footer .button--outline').first()).toHaveAttribute("disabled");
|
||||||
await expect(page.locator('.stepper-buttons .button--primary')).not.toHaveAttribute("disabled");
|
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||||
await page.getByRole('button', { name: 'Finish' }).click();
|
await page.getByRole('button', { name: 'Finish' }).click();
|
||||||
await expect(page.locator('.modal--open')).not.toBeVisible();
|
await expect(page.locator('.modal--open')).not.toBeVisible();
|
||||||
})
|
})
|
||||||
|
|
||||||
test('remove the CID when the file is deleted', async ({ page }) => {
|
test('remove the CID when the file is deleted', async ({ page }) => {
|
||||||
await page.goto('/dashboard');
|
await page.goto('/dashboard');
|
||||||
await page.getByRole('link', { name: 'Purchases' }).click();
|
await page.locator('a').filter({ hasText: 'Purchases' }).click();
|
||||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
await page.getByRole('button', { name: 'Storage Request' }).click();
|
||||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
await page.locator('div').getByTestId("upload").setInputFiles([
|
||||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||||
]);
|
]);
|
||||||
await expect(page.locator('#cid')).toHaveValue("zDvZRwzkvwapyNeL4mzw5gBsZvyn7x8F8Y9n4RYSC7ETBssDYpGe")
|
await expect(page.locator('#cid')).not.toBeEmpty()
|
||||||
await page.locator('.uploadFile-infoRight .buttonIcon--small').click();
|
await page.locator('.button-icon--small').click();
|
||||||
await expect(page.locator('#cid')).toHaveValue("")
|
await expect(page.locator('#cid')).toBeEmpty()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -26,8 +26,8 @@ test('multiple files upload', async ({ page }) => {
|
|||||||
await expect(page.getByText('File uploaded successfully').nth(1)).toBeVisible();
|
await expect(page.getByText('File uploaded successfully').nth(1)).toBeVisible();
|
||||||
|
|
||||||
|
|
||||||
await page.locator('.uploadFile-infoRight > .buttonIcon').first().click();
|
await page.locator('.upload-file .button-icon--small').first().click();
|
||||||
await page.locator('.uploadFile-infoRight > .buttonIcon').click();
|
await page.locator('.upload-file .button-icon--small').click();
|
||||||
|
|
||||||
await expect(page.getByText('File uploaded successfully').first()).not.toBeVisible();
|
await expect(page.getByText('File uploaded successfully').first()).not.toBeVisible();
|
||||||
await expect(page.getByText('File uploaded successfully').nth(1)).not.toBeVisible();
|
await expect(page.getByText('File uploaded successfully').nth(1)).not.toBeVisible();
|
||||||
@ -42,7 +42,7 @@ test('drag and drop file', async ({ page }) => {
|
|||||||
const dataTransfer = await page.evaluateHandle((data) => {
|
const dataTransfer = await page.evaluateHandle((data) => {
|
||||||
const dt = new DataTransfer();
|
const dt = new DataTransfer();
|
||||||
// Convert the buffer to a hex array
|
// Convert the buffer to a hex array
|
||||||
const file = new File([data.toString('hex')], 'chat.jpg', { type: 'image/jpg' });
|
const file = new File([data.toString('hex')], 'chat.jpg', { type: 'image/jpeg' });
|
||||||
dt.items.add(file);
|
dt.items.add(file);
|
||||||
return dt;
|
return dt;
|
||||||
}, buffer);
|
}, buffer);
|
||||||
@ -63,7 +63,7 @@ test('drag and drop file', async ({ page }) => {
|
|||||||
// mimeType: 'text/plain'
|
// mimeType: 'text/plain'
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// await page.locator('.uploadFile-infoRight > .buttonIcon--small').click();
|
// await page.locator('.uploadFile-infoRight > .button-icon--small').click();
|
||||||
|
|
||||||
// await expect(page.getByText('The upload has been cancelled')).toBeVisible();
|
// await expect(page.getByText('The upload has been cancelled')).toBeVisible();
|
||||||
// });
|
// });
|
||||||
368
package-lock.json
generated
368
package-lock.json
generated
@ -41,7 +41,8 @@
|
|||||||
"postcss-nesting": "^13.0.1",
|
"postcss-nesting": "^13.0.1",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"typescript": "5.5.4",
|
"typescript": "5.5.4",
|
||||||
"vite": "^5.4.7"
|
"vite": "^5.4.7",
|
||||||
|
"vitest": "^2.1.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@ -1994,6 +1995,112 @@
|
|||||||
"vite": "^4.2.0 || ^5.0.0"
|
"vite": "^4.2.0 || ^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vitest/expect": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/spy": "2.1.4",
|
||||||
|
"@vitest/utils": "2.1.4",
|
||||||
|
"chai": "^5.1.2",
|
||||||
|
"tinyrainbow": "^1.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/mocker": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/spy": "2.1.4",
|
||||||
|
"estree-walker": "^3.0.3",
|
||||||
|
"magic-string": "^0.30.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"msw": "^2.4.9",
|
||||||
|
"vite": "^5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"msw": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vite": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/pretty-format": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tinyrainbow": "^1.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/runner": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/utils": "2.1.4",
|
||||||
|
"pathe": "^1.1.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/snapshot": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/pretty-format": "2.1.4",
|
||||||
|
"magic-string": "^0.30.12",
|
||||||
|
"pathe": "^1.1.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/spy": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tinyspy": "^3.0.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/utils": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/pretty-format": "2.1.4",
|
||||||
|
"loupe": "^3.1.2",
|
||||||
|
"tinyrainbow": "^1.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.12.1",
|
"version": "8.12.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -2065,6 +2172,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Python-2.0"
|
"license": "Python-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/assertion-error": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/babel-dead-code-elimination": {
|
"node_modules/babel-dead-code-elimination": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -2152,6 +2268,15 @@
|
|||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/cac": {
|
||||||
|
"version": "6.7.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
||||||
|
"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/callsites": {
|
"node_modules/callsites": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -2180,6 +2305,22 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/chai": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"assertion-error": "^2.0.1",
|
||||||
|
"check-error": "^2.1.1",
|
||||||
|
"deep-eql": "^5.0.1",
|
||||||
|
"loupe": "^3.1.0",
|
||||||
|
"pathval": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||||
@ -2194,6 +2335,15 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/check-error": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -2317,6 +2467,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/deep-eql": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/deep-is": {
|
"node_modules/deep-is": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -2672,6 +2831,15 @@
|
|||||||
"node": ">=4.0"
|
"node": ">=4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/estree-walker": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/esutils": {
|
"node_modules/esutils": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -2680,6 +2848,15 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/expect-type": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -3103,6 +3280,12 @@
|
|||||||
"loose-envify": "cli.js"
|
"loose-envify": "cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/loupe": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/lru-cache": {
|
"node_modules/lru-cache": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
@ -3120,6 +3303,15 @@
|
|||||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/magic-string": {
|
||||||
|
"version": "0.30.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
|
||||||
|
"integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/merge2": {
|
"node_modules/merge2": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||||
@ -3289,6 +3481,21 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pathe": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/pathval": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
@ -4070,6 +4277,12 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/siginfo": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
@ -4077,6 +4290,18 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stackback": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/std-env": {
|
||||||
|
"version": "3.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz",
|
||||||
|
"integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/strip-ansi": {
|
"node_modules/strip-ansi": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -4124,6 +4349,45 @@
|
|||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/tinybench": {
|
||||||
|
"version": "2.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||||
|
"integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/tinyexec": {
|
||||||
|
"version": "0.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz",
|
||||||
|
"integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/tinypool": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.0.0 || >=20.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tinyrainbow": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tinyspy": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/to-fast-properties": {
|
"node_modules/to-fast-properties": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -4347,6 +4611,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vite-node": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"cac": "^6.7.14",
|
||||||
|
"debug": "^4.3.7",
|
||||||
|
"pathe": "^1.1.2",
|
||||||
|
"vite": "^5.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"vite-node": "vite-node.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.0.0 || >=20.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-x64": {
|
"node_modules/vite/node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
@ -4765,6 +5050,71 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vitest": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/expect": "2.1.4",
|
||||||
|
"@vitest/mocker": "2.1.4",
|
||||||
|
"@vitest/pretty-format": "^2.1.4",
|
||||||
|
"@vitest/runner": "2.1.4",
|
||||||
|
"@vitest/snapshot": "2.1.4",
|
||||||
|
"@vitest/spy": "2.1.4",
|
||||||
|
"@vitest/utils": "2.1.4",
|
||||||
|
"chai": "^5.1.2",
|
||||||
|
"debug": "^4.3.7",
|
||||||
|
"expect-type": "^1.1.0",
|
||||||
|
"magic-string": "^0.30.12",
|
||||||
|
"pathe": "^1.1.2",
|
||||||
|
"std-env": "^3.7.0",
|
||||||
|
"tinybench": "^2.9.0",
|
||||||
|
"tinyexec": "^0.3.1",
|
||||||
|
"tinypool": "^1.0.1",
|
||||||
|
"tinyrainbow": "^1.2.0",
|
||||||
|
"vite": "^5.0.0",
|
||||||
|
"vite-node": "2.1.4",
|
||||||
|
"why-is-node-running": "^2.3.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"vitest": "vitest.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.0.0 || >=20.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@edge-runtime/vm": "*",
|
||||||
|
"@types/node": "^18.0.0 || >=20.0.0",
|
||||||
|
"@vitest/browser": "2.1.4",
|
||||||
|
"@vitest/ui": "2.1.4",
|
||||||
|
"happy-dom": "*",
|
||||||
|
"jsdom": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@edge-runtime/vm": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/browser": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/ui": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"happy-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"jsdom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/webpack-virtual-modules": {
|
"node_modules/webpack-virtual-modules": {
|
||||||
"version": "0.6.2",
|
"version": "0.6.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -4784,6 +5134,22 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/why-is-node-running": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"siginfo": "^2.0.0",
|
||||||
|
"stackback": "0.0.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"why-is-node-running": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/wkt-parser": {
|
"node_modules/wkt-parser": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/wkt-parser/-/wkt-parser-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/wkt-parser/-/wkt-parser-1.3.3.tgz",
|
||||||
|
|||||||
@ -14,7 +14,8 @@
|
|||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"preview": "vite preview --host 127.0.0.1 --port 5173",
|
"preview": "vite preview --host 127.0.0.1 --port 5173",
|
||||||
"format": "prettier --write ./src",
|
"format": "prettier --write ./src",
|
||||||
"test": "npx playwright test"
|
"test": "npx playwright test",
|
||||||
|
"test:unit": "vitest run"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"Codex",
|
"Codex",
|
||||||
@ -56,7 +57,8 @@
|
|||||||
"postcss-nesting": "^13.0.1",
|
"postcss-nesting": "^13.0.1",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"typescript": "5.5.4",
|
"typescript": "5.5.4",
|
||||||
"vite": "^5.4.7"
|
"vite": "^5.4.7",
|
||||||
|
"vitest": "^2.1.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { ReactElement, useEffect } from "react";
|
|||||||
import { useCodexConnection } from "../../hooks/useCodexConnection";
|
import { useCodexConnection } from "../../hooks/useCodexConnection";
|
||||||
import { NodesIcon } from "../Menu/NodesIcon";
|
import { NodesIcon } from "../Menu/NodesIcon";
|
||||||
import { usePersistence } from "../../hooks/usePersistence";
|
import { usePersistence } from "../../hooks/usePersistence";
|
||||||
import { useNavigate, useRouterState } from "@tanstack/react-router";
|
import { useLocation, useNavigate } from "@tanstack/react-router";
|
||||||
import { PeersIcon } from "../Menu/PeersIcon";
|
import { PeersIcon } from "../Menu/PeersIcon";
|
||||||
import { SettingsIcon } from "../Menu/SettingsIcon";
|
import { SettingsIcon } from "../Menu/SettingsIcon";
|
||||||
|
|
||||||
@ -33,8 +33,8 @@ export function AppBar({ onIconClick }: Props) {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const codex = useCodexConnection();
|
const codex = useCodexConnection();
|
||||||
const persistence = usePersistence(codex.enabled);
|
const persistence = usePersistence(codex.enabled);
|
||||||
const router = useRouterState();
|
const location = useLocation();
|
||||||
const navigate = useNavigate({ from: router.location.pathname });
|
const navigate = useNavigate({ from: location.pathname });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
@ -43,13 +43,12 @@ export function AppBar({ onIconClick }: Props) {
|
|||||||
});
|
});
|
||||||
}, [queryClient, codex.enabled]);
|
}, [queryClient, codex.enabled]);
|
||||||
|
|
||||||
const onNodeClick = () => navigate({ to: "/dashboard/settings" });
|
|
||||||
|
|
||||||
const offline = !online || !codex.enabled;
|
const offline = !online || !codex.enabled;
|
||||||
|
|
||||||
|
const onNodeClick = () => navigate({ to: "/dashboard/settings" });
|
||||||
|
|
||||||
const title =
|
const title =
|
||||||
router.location.pathname.split("/")[2] ||
|
location.pathname.split("/")[2] || location.pathname.split("/")[1];
|
||||||
router.location.pathname.split("/")[1];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -59,7 +59,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cardNumber-dataContainer .buttonIcon {
|
.cardNumber-dataContainer .button-icon {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 3px;
|
top: 3px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
export function ErrorCircleIcon() {
|
export function ErrorCircleIcon() {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
|
data-testid="icon-error"
|
||||||
width="16"
|
width="16"
|
||||||
height="16"
|
height="16"
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 16 16"
|
||||||
|
|||||||
@ -3,16 +3,17 @@ import { useEffect, useState } from "react";
|
|||||||
import { useDebug } from "../../hooks/useDebug";
|
import { useDebug } from "../../hooks/useDebug";
|
||||||
import { usePersistence } from "../../hooks/usePersistence";
|
import { usePersistence } from "../../hooks/usePersistence";
|
||||||
import { usePortForwarding } from "../../hooks/usePortForwarding";
|
import { usePortForwarding } from "../../hooks/usePortForwarding";
|
||||||
import { CodexSdk } from "../../proxy";
|
|
||||||
import { ErrorCircleIcon } from "../ErrorCircleIcon/ErrorCircleIcon";
|
import { ErrorCircleIcon } from "../ErrorCircleIcon/ErrorCircleIcon";
|
||||||
import { SuccessCheckIcon } from "../SuccessCheckIcon/SuccessCheckIcon";
|
import { SuccessCheckIcon } from "../SuccessCheckIcon/SuccessCheckIcon";
|
||||||
import { WarningIcon } from "../WarningIcon/WarningIcon";
|
import { WarningIcon } from "../WarningIcon/WarningIcon";
|
||||||
import { HealthCheckIcon } from "./HealthCheckIcon";
|
import { HealthCheckIcon } from "./HealthCheckIcon";
|
||||||
import { Input } from "@codex-storage/marketplace-ui-components";
|
import { Input } from "@codex-storage/marketplace-ui-components";
|
||||||
import { classnames } from "../../utils/classnames";
|
import { classnames } from "../../utils/classnames";
|
||||||
import { DebugUtils } from "../../utils/debug";
|
|
||||||
import { RefreshIcon } from "../RefreshIcon/RefreshIcon";
|
import { RefreshIcon } from "../RefreshIcon/RefreshIcon";
|
||||||
import "./HealthChecks.css";
|
import "./HealthChecks.css";
|
||||||
|
import { CodexSdk } from "../../sdk/codex";
|
||||||
|
import { HealthCheckUtil } from "./health-check.util";
|
||||||
|
import { PortForwardingUtil } from "../../hooks/port-forwarding.util";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
online: boolean;
|
online: boolean;
|
||||||
@ -29,45 +30,40 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
|||||||
const [isAddressInvalid, setIsAddressInvalid] = useState(false);
|
const [isAddressInvalid, setIsAddressInvalid] = useState(false);
|
||||||
const [isPortInvalid, setIsPortInvalid] = useState(false);
|
const [isPortInvalid, setIsPortInvalid] = useState(false);
|
||||||
const [address, setAddress] = useState(
|
const [address, setAddress] = useState(
|
||||||
CodexSdk.url().split(":")[0] + ":" + CodexSdk.url().split(":")[1]
|
HealthCheckUtil.removePort(CodexSdk.url())
|
||||||
);
|
|
||||||
const [port, setPort] = useState(
|
|
||||||
parseInt(CodexSdk.url().split(":")[2] || "80", 10)
|
|
||||||
);
|
);
|
||||||
|
const [port, setPort] = useState(HealthCheckUtil.getPort(CodexSdk.url()));
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(
|
||||||
if (codex.isSuccess) {
|
() => {
|
||||||
persistence.refetch();
|
if (codex.isSuccess) {
|
||||||
portForwarding.refetch().then(({ data }) => {
|
persistence.refetch();
|
||||||
onStepValid(data?.reachable || false);
|
portForwarding.refetch().then(({ data }) => {
|
||||||
});
|
onStepValid(data?.reachable || false);
|
||||||
} else {
|
});
|
||||||
onStepValid(false);
|
} else {
|
||||||
}
|
onStepValid(false);
|
||||||
}, [
|
}
|
||||||
persistence.refetch,
|
},
|
||||||
onStepValid,
|
// We really do not want to add persistence and portForwarding as
|
||||||
portForwarding.refetch,
|
// dependencies because it will cause a re-render every time.
|
||||||
codex.isSuccess,
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
]);
|
[persistence.refetch, onStepValid, portForwarding.refetch, codex.isSuccess]
|
||||||
|
);
|
||||||
|
|
||||||
const onAddressChange = (e: React.FormEvent<HTMLInputElement>) => {
|
const onAddressChange = (e: React.FormEvent<HTMLInputElement>) => {
|
||||||
const element = e.currentTarget;
|
const element = e.currentTarget;
|
||||||
const parts = e.currentTarget.value.split(":");
|
const value = e.currentTarget.value;
|
||||||
|
|
||||||
setIsAddressInvalid(!element.checkValidity());
|
setIsAddressInvalid(!element.checkValidity());
|
||||||
|
|
||||||
if (parts.length > 2) {
|
const address = HealthCheckUtil.removePort(value);
|
||||||
const [protocol, addr, port] = parts;
|
setAddress(address);
|
||||||
setAddress(protocol + ":" + addr);
|
|
||||||
|
|
||||||
const p = parseInt(port, 10);
|
if (HealthCheckUtil.containsPort(value)) {
|
||||||
if (!isNaN(p)) {
|
const p = HealthCheckUtil.getPort(value);
|
||||||
setPort(p);
|
setPort(p);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setAddress(parts.join(":"));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -80,11 +76,13 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSave = () => {
|
const onSave = () => {
|
||||||
if (isAddressInvalid || isPortInvalid) {
|
const url = address + ":" + port;
|
||||||
|
|
||||||
|
if (HealthCheckUtil.isUrlInvalid(url)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CodexSdk.updateURL(address + ":" + port)
|
CodexSdk.updateURL(url)
|
||||||
.then(() => queryClient.invalidateQueries())
|
.then(() => queryClient.invalidateQueries())
|
||||||
.then(() => codex.refetch());
|
.then(() => codex.refetch());
|
||||||
};
|
};
|
||||||
@ -92,7 +90,7 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
|||||||
let forwardingPortValue = defaultPort;
|
let forwardingPortValue = defaultPort;
|
||||||
|
|
||||||
if (codex.isSuccess && codex.data) {
|
if (codex.isSuccess && codex.data) {
|
||||||
const port = DebugUtils.getTcpPort(codex.data);
|
const port = PortForwardingUtil.getTcpPort(codex.data);
|
||||||
if (!port.error) {
|
if (!port.error) {
|
||||||
forwardingPortValue = port.data;
|
forwardingPortValue = port.data;
|
||||||
}
|
}
|
||||||
@ -135,7 +133,9 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="refresh">
|
<div className="refresh">
|
||||||
<RefreshIcon onClick={onSave}></RefreshIcon>
|
<RefreshIcon
|
||||||
|
onClick={onSave}
|
||||||
|
disabled={isAddressInvalid || isPortInvalid}></RefreshIcon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
148
src/components/HealthChecks/health-check.util.test.ts
Normal file
148
src/components/HealthChecks/health-check.util.test.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import { assert, describe, it } from "vitest";
|
||||||
|
import { HealthCheckUtil } from "./health-check.util";
|
||||||
|
|
||||||
|
describe("health check", () => {
|
||||||
|
it("remove the port from an url", async () => {
|
||||||
|
assert.deepEqual(HealthCheckUtil.removePort("http://localhost:8080"), "http://localhost");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("get the port from an url", async () => {
|
||||||
|
assert.deepEqual(HealthCheckUtil.getPort("http://localhost:8080"), 8080);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("get the default port when the url does not contain the port", async () => {
|
||||||
|
assert.deepEqual(HealthCheckUtil.getPort("http://localhost"), 80);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns true when the url contains a port", async () => {
|
||||||
|
assert.deepEqual(HealthCheckUtil.containsPort("http://localhost:8080"), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns false when the url does not contain a port", async () => {
|
||||||
|
assert.deepEqual(HealthCheckUtil.containsPort("http://localhost"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("returns true when the url is invalid", async () => {
|
||||||
|
assert.deepEqual(HealthCheckUtil.isUrlInvalid("http://"), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns false when the url is valid", async () => {
|
||||||
|
assert.deepEqual(HealthCheckUtil.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(HealthCheckUtil.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(HealthCheckUtil.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(HealthCheckUtil.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(HealthCheckUtil.getTcpPort(debug).error, true);
|
||||||
|
});
|
||||||
|
})
|
||||||
51
src/components/HealthChecks/health-check.util.ts
Normal file
51
src/components/HealthChecks/health-check.util.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { CodexDebugInfo, SafeValue, CodexError } from "@codex-storage/sdk-js"
|
||||||
|
|
||||||
|
export const HealthCheckUtil = {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -149,6 +149,14 @@
|
|||||||
label {
|
label {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.health-checks {
|
||||||
|
.address {
|
||||||
|
.refresh {
|
||||||
|
top: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.get-started {
|
.get-started {
|
||||||
|
|||||||
@ -1,22 +1,14 @@
|
|||||||
import { Cell } from "@codex-storage/marketplace-ui-components";
|
import { Cell } from "@codex-storage/marketplace-ui-components";
|
||||||
import { PeerPin } from "./types";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import "./PeerCountryCell.css";
|
import "./PeerCountryCell.css";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import { PeerPin, PeerUtils } from "./peers.util";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
address: string;
|
address: string;
|
||||||
onPinAdd: (pin: PeerPin & { countryIso: string; ip: string }) => void;
|
onPinAdd: (pin: PeerPin & { countryIso: string; ip: string }) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFlagEmoji = (countryCode: string) => {
|
|
||||||
const codePoints = countryCode
|
|
||||||
.toUpperCase()
|
|
||||||
.split("")
|
|
||||||
.map((char) => 127397 + char.charCodeAt(0));
|
|
||||||
return String.fromCodePoint(...codePoints);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function PeerCountryCell({ address, onPinAdd }: Props) {
|
export function PeerCountryCell({ address, onPinAdd }: Props) {
|
||||||
const { data } = useQuery({
|
const { data } = useQuery({
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
@ -61,7 +53,7 @@ export function PeerCountryCell({ address, onPinAdd }: Props) {
|
|||||||
<div className="peer-country">
|
<div className="peer-country">
|
||||||
{data ? (
|
{data ? (
|
||||||
<>
|
<>
|
||||||
<span> {!!data && getFlagEmoji(data.country_iso)}</span>
|
<span> {!!data && PeerUtils.geCountryEmoji(data.country_iso)}</span>
|
||||||
<span>{data?.country}</span>
|
<span>{data?.country}</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
max-width: 1320px;
|
max-width: 1320px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
padding-bottom: 32px;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
max-width: 1320px;
|
max-width: 1320px;
|
||||||
@ -194,7 +195,7 @@
|
|||||||
|
|
||||||
div:nth-child(1) {
|
div:nth-child(1) {
|
||||||
border-color: var(--codex-color-primary);
|
border-color: var(--codex-color-primary);
|
||||||
transform: rotate(calc(var(--codex-peers-percent) * 1deg));
|
transform: rotate(calc(var(--codex-peers-degrees) * 1deg));
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,78 +1,36 @@
|
|||||||
import {
|
import {
|
||||||
Cell,
|
|
||||||
Row,
|
|
||||||
Table,
|
|
||||||
TabSortState,
|
TabSortState,
|
||||||
|
Row,
|
||||||
|
Cell,
|
||||||
|
Table,
|
||||||
} from "@codex-storage/marketplace-ui-components";
|
} from "@codex-storage/marketplace-ui-components";
|
||||||
import { getMapJSON } from "dotted-map";
|
|
||||||
import DottedMap from "dotted-map/without-countries";
|
import DottedMap from "dotted-map/without-countries";
|
||||||
import { Promises } from "../../utils/promises";
|
import { useRef, useState, useCallback } from "react";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { ErrorCircleIcon } from "../ErrorCircleIcon/ErrorCircleIcon";
|
||||||
import { PeerCountryCell } from "../../components/Peers/PeerCountryCell";
|
import { PeersIcon } from "../Menu/PeersIcon";
|
||||||
import { useCallback, useRef, useState } from "react";
|
import { PeerCountryCell } from "./PeerCountryCell";
|
||||||
import { PeerPin } from "../../components/Peers/types";
|
import { SuccessCheckIcon } from "../SuccessCheckIcon/SuccessCheckIcon";
|
||||||
import "./peers.css";
|
import { useDebug } from "../../hooks/useDebug";
|
||||||
import { CodexSdk } from "../../sdk/codex";
|
import { getMapJSON } from "dotted-map";
|
||||||
import { ErrorBoundary } from "@sentry/react";
|
import "./Peers.css";
|
||||||
import { ErrorPlaceholder } from "../../components/ErrorPlaceholder/ErrorPlaceholder";
|
import { PeerPin, PeerSortFn, PeerUtils } from "./peers.util";
|
||||||
import { PeersIcon } from "../../components/Menu/PeersIcon";
|
|
||||||
import { SuccessCheckIcon } from "../../components/SuccessCheckIcon/SuccessCheckIcon";
|
|
||||||
import { ErrorCircleIcon } from "../../components/ErrorCircleIcon/ErrorCircleIcon";
|
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
|
||||||
import { Network } from "../../utils/network";
|
|
||||||
|
|
||||||
// This function accepts the same arguments as DottedMap in the example above.
|
// This function accepts the same arguments as DottedMap in the example above.
|
||||||
const mapJsonString = getMapJSON({ height: 60, grid: "diagonal" });
|
const mapJsonString = getMapJSON({ height: 60, grid: "diagonal" });
|
||||||
|
|
||||||
type CustomCSSProperties = React.CSSProperties & {
|
type CustomCSSProperties = React.CSSProperties & {
|
||||||
"--codex-peers-percent": number;
|
"--codex-peers-degrees": number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Node = {
|
const throwOnError = true;
|
||||||
nodeId: string;
|
|
||||||
peerId: string;
|
|
||||||
record: string;
|
|
||||||
address: string;
|
|
||||||
seen: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type SortFn = (a: Node, b: Node) => number;
|
export const Peers = () => {
|
||||||
|
|
||||||
const sortByBooleanValue = (state: TabSortState) => {
|
|
||||||
return (a: Node, b: Node) => {
|
|
||||||
const order = state === "desc" ? 1 : -1;
|
|
||||||
return a?.seen === b?.seen ? 0 : b?.seen ? order : -order;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const Peers = () => {
|
|
||||||
const ips = useRef<Record<string, string>>({});
|
const ips = useRef<Record<string, string>>({});
|
||||||
const [pins, setPins] = useState<[PeerPin, number][]>([]);
|
const [pins, setPins] = useState<[PeerPin, number][]>([]);
|
||||||
const [sortFn, setSortFn] = useState<SortFn | null>(() =>
|
const [sortFn, setSortFn] = useState<PeerSortFn | null>(() =>
|
||||||
sortByBooleanValue("desc")
|
PeerUtils.sortByBoolean("desc")
|
||||||
);
|
);
|
||||||
const { data } = useQuery({
|
const { data } = useDebug(throwOnError);
|
||||||
queryFn: () =>
|
|
||||||
CodexSdk.debug()
|
|
||||||
.info()
|
|
||||||
.then((s) => Promises.rejectOnError(s)),
|
|
||||||
queryKey: ["debug"],
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
|
|
||||||
// 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: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const onPinAdd = useCallback(
|
const onPinAdd = useCallback(
|
||||||
({
|
({
|
||||||
@ -80,11 +38,7 @@ const Peers = () => {
|
|||||||
ip,
|
ip,
|
||||||
...pin
|
...pin
|
||||||
}: PeerPin & { countryIso: string; ip: string }) => {
|
}: PeerPin & { countryIso: string; ip: string }) => {
|
||||||
setPins((val) => {
|
setPins((val) => PeerUtils.incPin(val, pin));
|
||||||
const [, quantity = 0] =
|
|
||||||
val.find(([p]) => p.lat === pin.lat && p.lng == pin.lng) || [];
|
|
||||||
return [...val, [pin, quantity + 1]];
|
|
||||||
});
|
|
||||||
ips.current[ip] = countryIso;
|
ips.current[ip] = countryIso;
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
@ -115,24 +69,16 @@ const Peers = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSortFn(() => (a: Node, b: Node) => {
|
setSortFn(() => PeerUtils.sortByCountry(state, ips.current));
|
||||||
const countryA = ips.current[Network.getIp(a.address)] || "";
|
|
||||||
const countryB = ips.current[Network.getIp(b.address)] || "";
|
|
||||||
|
|
||||||
return state === "desc"
|
|
||||||
? countryA.localeCompare(countryB)
|
|
||||||
: countryB.localeCompare(countryA);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSortActive = (state: TabSortState) => {
|
const onSortActive = (state: TabSortState) => {
|
||||||
console.info("fdf");
|
|
||||||
if (!state) {
|
if (!state) {
|
||||||
setSortFn(null);
|
setSortFn(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSortFn(() => sortByBooleanValue(state));
|
setSortFn(() => PeerUtils.sortByBoolean(state));
|
||||||
};
|
};
|
||||||
|
|
||||||
const headers = [
|
const headers = [
|
||||||
@ -165,15 +111,14 @@ const Peers = () => {
|
|||||||
]}></Row>
|
]}></Row>
|
||||||
));
|
));
|
||||||
|
|
||||||
const actives = sorted.reduce((acc, cur) => acc + (cur.seen ? 1 : 0), 0) || 0;
|
const actives = PeerUtils.countActives(sorted);
|
||||||
const total = data?.table.nodes.length || 1;
|
const degrees = PeerUtils.calculareDegrees(sorted);
|
||||||
|
const good = actives > 0;
|
||||||
|
|
||||||
const styles: CustomCSSProperties = {
|
const styles: CustomCSSProperties = {
|
||||||
"--codex-peers-percent": (actives / total) * 180,
|
"--codex-peers-degrees": degrees,
|
||||||
};
|
};
|
||||||
|
|
||||||
const good = actives > 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="peers">
|
<div className="peers">
|
||||||
<div>
|
<div>
|
||||||
@ -217,14 +162,3 @@ const Peers = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/dashboard/peers")({
|
|
||||||
component: () => (
|
|
||||||
<ErrorBoundary
|
|
||||||
fallback={({ error }) => (
|
|
||||||
<ErrorPlaceholder error={error} subtitle="Cannot retrieve the data." />
|
|
||||||
)}>
|
|
||||||
<Peers />
|
|
||||||
</ErrorBoundary>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
68
src/components/Peers/peers.util.test.ts
Normal file
68
src/components/Peers/peers.util.test.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { assert, describe, it } from "vitest";
|
||||||
|
import { PeerUtils } from "./peers.util";
|
||||||
|
|
||||||
|
describe("peers", () => {
|
||||||
|
it("sorts by boolean", async () => {
|
||||||
|
const a = { nodeId: "a", peerId: "", record: "", address: "", seen: false }
|
||||||
|
const b = { nodeId: "a", peerId: "", record: "", address: "", seen: true }
|
||||||
|
const items = [a, b,]
|
||||||
|
|
||||||
|
const descSorted = items.slice().sort(PeerUtils.sortByBoolean("desc"))
|
||||||
|
|
||||||
|
assert.deepEqual(descSorted, [b, a]);
|
||||||
|
|
||||||
|
const ascSorted = items.slice().sort(PeerUtils.sortByBoolean("asc"))
|
||||||
|
|
||||||
|
assert.deepEqual(ascSorted, [a, b]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sorts by country", async () => {
|
||||||
|
const a = { nodeId: "a", peerId: "", record: "", address: "127.0.0.1", seen: false }
|
||||||
|
const b = { nodeId: "a", peerId: "", record: "", address: "127.0.0.2", seen: true }
|
||||||
|
const table = {
|
||||||
|
"127.0.0.1": "US",
|
||||||
|
"127.0.0.2": "France",
|
||||||
|
}
|
||||||
|
const items = [a, b,]
|
||||||
|
|
||||||
|
const descSorted = items.slice().sort(PeerUtils.sortByCountry("desc", table))
|
||||||
|
|
||||||
|
assert.deepEqual(descSorted, [b, a]);
|
||||||
|
|
||||||
|
const ascSorted = items.slice().sort(PeerUtils.sortByCountry("asc", table))
|
||||||
|
|
||||||
|
assert.deepEqual(ascSorted, [a, b]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds a new pin", async () => {
|
||||||
|
const latLng = { lat: 0, lng: 0 }
|
||||||
|
const values = PeerUtils.incPin([], latLng)
|
||||||
|
|
||||||
|
assert.deepEqual(values, [[latLng, 1]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("increments an existing pin", async () => {
|
||||||
|
const latLng = { lat: 0, lng: 0 }
|
||||||
|
const values = PeerUtils.incPin([[latLng, 1]], latLng)
|
||||||
|
|
||||||
|
assert.deepEqual(values, [[latLng, 2]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("count active peers nodes", async () => {
|
||||||
|
const a = { nodeId: "a", peerId: "", record: "", address: "127.0.0.1", seen: false }
|
||||||
|
const b = { nodeId: "a", peerId: "", record: "", address: "127.0.0.2", seen: true }
|
||||||
|
|
||||||
|
assert.equal(PeerUtils.countActives([a, b]), 1)
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calculates active peers nodes degrees", async () => {
|
||||||
|
const a = { nodeId: "a", peerId: "", record: "", address: "127.0.0.1", seen: false }
|
||||||
|
const b = { nodeId: "a", peerId: "", record: "", address: "127.0.0.2", seen: true }
|
||||||
|
|
||||||
|
assert.equal(PeerUtils.calculareDegrees([a, b]), 90)
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns the country flag", async () => {
|
||||||
|
assert.equal(PeerUtils.geCountryEmoji("FR"), "🇫🇷")
|
||||||
|
});
|
||||||
|
})
|
||||||
64
src/components/Peers/peers.util.ts
Normal file
64
src/components/Peers/peers.util.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { TabSortState } from "@codex-storage/marketplace-ui-components";
|
||||||
|
|
||||||
|
export type PeerNode = {
|
||||||
|
nodeId: string;
|
||||||
|
peerId: string;
|
||||||
|
record: string;
|
||||||
|
address: string;
|
||||||
|
seen: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PeerPin = {
|
||||||
|
lat: number;
|
||||||
|
lng: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PeerSortFn = (a: PeerNode, b: PeerNode) => number;
|
||||||
|
|
||||||
|
export const PeerUtils = {
|
||||||
|
sortByBoolean: (state: TabSortState) => (a: PeerNode, b: PeerNode) => {
|
||||||
|
const order = state === "desc" ? 1 : -1;
|
||||||
|
return a?.seen === b?.seen ? 0 : b?.seen ? order : -order;
|
||||||
|
},
|
||||||
|
|
||||||
|
sortByCountry: (state: TabSortState, ipTable: Record<string, string>) =>
|
||||||
|
(a: PeerNode, b: PeerNode) => {
|
||||||
|
const [ipA = ""] = a.address.split(":")
|
||||||
|
const [ipB = ""] = b.address.split(":")
|
||||||
|
const countryA = ipTable[ipA] || "";
|
||||||
|
const countryB = ipTable[ipB] || "";
|
||||||
|
|
||||||
|
return state === "desc"
|
||||||
|
? countryA.localeCompare(countryB)
|
||||||
|
: countryB.localeCompare(countryA);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the number of pin for a location
|
||||||
|
*/
|
||||||
|
incPin(val: [PeerPin, number][], pin: PeerPin): [PeerPin, number][] {
|
||||||
|
const [, quantity = 0] =
|
||||||
|
val.find(([p]) => p.lat === pin.lat && p.lng == pin.lng) || [];
|
||||||
|
const rest = val.filter(([p]) => p.lat !== pin.lat || p.lng !== pin.lng)
|
||||||
|
return [...rest, [pin, quantity + 1]];
|
||||||
|
},
|
||||||
|
|
||||||
|
countActives: (peers: PeerNode[]) =>
|
||||||
|
peers.reduce((acc, cur) => acc + (cur.seen ? 1 : 0), 0) || 0,
|
||||||
|
|
||||||
|
calculareDegrees: (peers: PeerNode[]) => {
|
||||||
|
const actives = PeerUtils.countActives(peers);
|
||||||
|
const total = peers.length || 1;
|
||||||
|
|
||||||
|
return (actives / total) * 180
|
||||||
|
},
|
||||||
|
|
||||||
|
geCountryEmoji: (countryCode: string) => {
|
||||||
|
const codePoints = countryCode
|
||||||
|
.toUpperCase()
|
||||||
|
.split("")
|
||||||
|
.map((char) => 127397 + char.charCodeAt(0));
|
||||||
|
return String.fromCodePoint(...codePoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,4 +0,0 @@
|
|||||||
export type PeerPin = {
|
|
||||||
lat: number;
|
|
||||||
lng: number;
|
|
||||||
};
|
|
||||||
@ -1,11 +1,16 @@
|
|||||||
|
import { attributes } from "../../utils/attributes";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function RefreshIcon({ className, onClick }: Props) {
|
export function RefreshIcon({ className, onClick, disabled = false }: Props) {
|
||||||
|
const color = disabled ? "#494949" : "var(--codex-color-primary)";
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
|
{...attributes({ "aria-disabled": disabled })}
|
||||||
className={className}
|
className={className}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
width="24"
|
width="24"
|
||||||
@ -15,7 +20,7 @@ export function RefreshIcon({ className, onClick }: Props) {
|
|||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
d="M12 24C5.3724 24 0 18.6276 0 12C0 5.3724 5.3724 0 12 0C18.6276 0 24 5.3724 24 12C24 18.6276 18.6276 24 12 24ZM17.784 18.0912C19.2325 16.7182 20.1449 14.8744 20.3576 12.8899C20.5703 10.9055 20.0695 8.91012 18.9449 7.26133C17.8203 5.61253 16.1454 4.41803 14.2202 3.89182C12.295 3.36561 10.2453 3.54208 8.4384 4.3896L9.6084 6.4956C10.5215 6.09874 11.519 5.93535 12.511 6.02015C13.503 6.10495 14.4584 6.43528 15.2909 6.98135C16.1234 7.52743 16.8069 8.27209 17.2798 9.14821C17.7528 10.0243 18.0003 11.0044 18 12H14.4L17.784 18.0912ZM15.5616 19.6104L14.3916 17.5044C13.4785 17.9013 12.481 18.0646 11.489 17.9798C10.497 17.8951 9.54165 17.5647 8.70914 17.0186C7.87664 16.4726 7.1931 15.7279 6.72016 14.8518C6.24722 13.9757 5.99973 12.9956 6 12H9.6L6.216 5.9088C4.76747 7.28176 3.85511 9.12563 3.64239 11.1101C3.42966 13.0945 3.93047 15.0899 5.05508 16.7387C6.17969 18.3875 7.85462 19.582 9.77982 20.1082C11.705 20.6344 13.7547 20.4579 15.5616 19.6104V19.6104Z"
|
d="M12 24C5.3724 24 0 18.6276 0 12C0 5.3724 5.3724 0 12 0C18.6276 0 24 5.3724 24 12C24 18.6276 18.6276 24 12 24ZM17.784 18.0912C19.2325 16.7182 20.1449 14.8744 20.3576 12.8899C20.5703 10.9055 20.0695 8.91012 18.9449 7.26133C17.8203 5.61253 16.1454 4.41803 14.2202 3.89182C12.295 3.36561 10.2453 3.54208 8.4384 4.3896L9.6084 6.4956C10.5215 6.09874 11.519 5.93535 12.511 6.02015C13.503 6.10495 14.4584 6.43528 15.2909 6.98135C16.1234 7.52743 16.8069 8.27209 17.2798 9.14821C17.7528 10.0243 18.0003 11.0044 18 12H14.4L17.784 18.0912ZM15.5616 19.6104L14.3916 17.5044C13.4785 17.9013 12.481 18.0646 11.489 17.9798C10.497 17.8951 9.54165 17.5647 8.70914 17.0186C7.87664 16.4726 7.1931 15.7279 6.72016 14.8518C6.24722 13.9757 5.99973 12.9956 6 12H9.6L6.216 5.9088C4.76747 7.28176 3.85511 9.12563 3.64239 11.1101C3.42966 13.0945 3.93047 15.0899 5.05508 16.7387C6.17969 18.3875 7.85462 19.582 9.77982 20.1082C11.705 20.6344 13.7547 20.4579 15.5616 19.6104V19.6104Z"
|
||||||
fill="var(--codex-color-primary)"
|
fill={color}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -7,6 +7,7 @@ export function SuccessCheckIcon({ variant }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
|
data-testid="icon-success"
|
||||||
width="16"
|
width="16"
|
||||||
height="16"
|
height="16"
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 16 16"
|
||||||
|
|||||||
@ -54,6 +54,8 @@ export function UserInfo({ onNameChange }: Props) {
|
|||||||
"objects",
|
"objects",
|
||||||
"symbols",
|
"symbols",
|
||||||
"flags",
|
"flags",
|
||||||
|
// The type does not allow a string array but the api yes
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
] as any
|
] as any
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -2,14 +2,14 @@ import { useDebug } from "../../hooks/useDebug";
|
|||||||
import { AlphaText } from "../AlphaText/AlphaText";
|
import { AlphaText } from "../AlphaText/AlphaText";
|
||||||
import { AlphaIcon } from "../OnBoarding/AlphaIcon";
|
import { AlphaIcon } from "../OnBoarding/AlphaIcon";
|
||||||
import "./Versions.css";
|
import "./Versions.css";
|
||||||
|
import { VersionsUtil } from "./versions.util";
|
||||||
|
|
||||||
const throwOnError = true;
|
const throwOnError = false;
|
||||||
|
|
||||||
export function Versions() {
|
export function Versions() {
|
||||||
const debug = useDebug(throwOnError);
|
const debug = useDebug(throwOnError);
|
||||||
|
|
||||||
const parts = debug.data?.codex.version.split("\n") || [""];
|
const version = VersionsUtil.clientVersion(debug.data?.codex.version);
|
||||||
const version = parts[parts.length - 1];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="versions">
|
<div className="versions">
|
||||||
@ -20,7 +20,7 @@ export function Versions() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>Vault</p>
|
<p>Vault</p>
|
||||||
<small>VER. {import.meta.env.PACKAGE_VERSION}</small>
|
<small>VER. {VersionsUtil.codexVersion()}</small>
|
||||||
<AlphaText variant="failure" width={37}></AlphaText>
|
<AlphaText variant="failure" width={37}></AlphaText>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
9
src/components/Versions/versions.util.test.ts
Normal file
9
src/components/Versions/versions.util.test.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { assert, describe, it } from "vitest";
|
||||||
|
import { VersionsUtil } from "./versions.util";
|
||||||
|
|
||||||
|
describe("versions", () => {
|
||||||
|
it("gets the last client version", async () => {
|
||||||
|
const 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"
|
||||||
|
assert.equal(VersionsUtil.clientVersion(version), "v0.1.7")
|
||||||
|
})
|
||||||
|
})
|
||||||
8
src/components/Versions/versions.util.ts
Normal file
8
src/components/Versions/versions.util.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export const VersionsUtil = {
|
||||||
|
codexVersion: () => import.meta.env.PACKAGE_VERSION,
|
||||||
|
|
||||||
|
clientVersion: (version: string | undefined) => {
|
||||||
|
const parts = version?.split("\n") || [""];
|
||||||
|
return parts[parts.length - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,9 @@
|
|||||||
import { CodexDebugInfo, CodexError, SafeValue } from "@codex-storage/sdk-js";
|
import { CodexDebugInfo, SafeValue, CodexError } from "@codex-storage/sdk-js"
|
||||||
|
|
||||||
|
export const PortForwardingUtil = {
|
||||||
|
check: (port: number) => fetch(import.meta.env.VITE_GEO_IP_URL + "/port/" + port)
|
||||||
|
.then((res) => res.json()),
|
||||||
|
|
||||||
export const DebugUtils = {
|
|
||||||
getTcpPort(info: CodexDebugInfo): SafeValue<number> {
|
getTcpPort(info: CodexDebugInfo): SafeValue<number> {
|
||||||
if (info.addrs.length === 0) {
|
if (info.addrs.length === 0) {
|
||||||
return { error: true, data: new CodexError("Not existing address") }
|
return { error: true, data: new CodexError("Not existing address") }
|
||||||
@ -20,4 +23,5 @@ export const DebugUtils = {
|
|||||||
|
|
||||||
return { error: false, data: port }
|
return { error: false, data: port }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -27,6 +27,8 @@ export function useCodexConnection() {
|
|||||||
|
|
||||||
// Cache is not useful for the spr endpoint
|
// Cache is not useful for the spr endpoint
|
||||||
gcTime: 0,
|
gcTime: 0,
|
||||||
|
|
||||||
|
throwOnError: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { enabled: !isError && !!data, isFetching, refetch };
|
return { enabled: !isError && !!data, isFetching, refetch };
|
||||||
|
|||||||
@ -6,12 +6,13 @@ const report = false;
|
|||||||
|
|
||||||
export function usePersistence(isCodexOnline: boolean) {
|
export function usePersistence(isCodexOnline: boolean) {
|
||||||
const { data, isError, isFetching, refetch } = useQuery({
|
const { data, isError, isFetching, refetch } = useQuery({
|
||||||
queryKey: ["availabilities"],
|
queryKey: [],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return CodexSdk.marketplace()
|
return CodexSdk.marketplace()
|
||||||
.purchases()
|
.purchases()
|
||||||
.then((data) => Promises.rejectOnError(data, report));
|
.then((data) => Promises.rejectOnError(data, report));
|
||||||
},
|
},
|
||||||
|
|
||||||
refetchInterval: 5000,
|
refetchInterval: 5000,
|
||||||
|
|
||||||
// Enable only when the use has an internet connection
|
// Enable only when the use has an internet connection
|
||||||
@ -27,6 +28,8 @@ export function usePersistence(isCodexOnline: boolean) {
|
|||||||
// 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,
|
||||||
|
|
||||||
|
throwOnError: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { enabled: !isError && !!data, isFetching, refetch };
|
return { enabled: !isError && !!data, isFetching, refetch };
|
||||||
|
|||||||
@ -1,20 +1,21 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Echo } from "../utils/echo";
|
|
||||||
import { Errors } from "../utils/errors";
|
import { Errors } from "../utils/errors";
|
||||||
import { CodexDebugInfo } from "@codex-storage/sdk-js";
|
import { CodexDebugInfo } from "@codex-storage/sdk-js";
|
||||||
import { DebugUtils } from "../utils/debug";
|
import { PortForwardingUtil } from "./port-forwarding.util";
|
||||||
|
|
||||||
type PortForwardingResponse = { reachable: boolean };
|
type PortForwardingResponse = { reachable: boolean };
|
||||||
|
|
||||||
export function usePortForwarding(info: CodexDebugInfo | undefined) {
|
export function usePortForwarding(info: CodexDebugInfo | undefined) {
|
||||||
const { data, isFetching, refetch } = useQuery({
|
const { data, isFetching, refetch } = useQuery({
|
||||||
queryFn: (): Promise<PortForwardingResponse> => {
|
queryFn: (): Promise<PortForwardingResponse> => {
|
||||||
const port = DebugUtils.getTcpPort(info!);
|
const port = PortForwardingUtil.getTcpPort(info!);
|
||||||
if (port.error) {
|
if (port.error) {
|
||||||
Errors.report(port);
|
Errors.report(port);
|
||||||
return Promise.resolve({ reachable: false });
|
return Promise.resolve({ reachable: false });
|
||||||
} else {
|
} else {
|
||||||
return Echo.portForwarding(port.data).catch((e) => Errors.report(e));
|
return PortForwardingUtil.check(port.data).catch((e) =>
|
||||||
|
Errors.report(e)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
queryKey: ["port-forwarding"],
|
queryKey: ["port-forwarding"],
|
||||||
@ -33,6 +34,8 @@ export function usePortForwarding(info: CodexDebugInfo | undefined) {
|
|||||||
// The user may try to change the port forwarding and go back
|
// The user may try to change the port forwarding and go back
|
||||||
// to the tab
|
// to the tab
|
||||||
refetchOnWindowFocus: true,
|
refetchOnWindowFocus: true,
|
||||||
|
|
||||||
|
throwOnError: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { enabled: data.reachable, isFetching, refetch };
|
return { enabled: data.reachable, isFetching, refetch };
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import { Route as DashboardWalletImport } from './routes/dashboard/wallet'
|
|||||||
import { Route as DashboardSettingsImport } from './routes/dashboard/settings'
|
import { Route as DashboardSettingsImport } from './routes/dashboard/settings'
|
||||||
import { Route as DashboardRequestsImport } from './routes/dashboard/requests'
|
import { Route as DashboardRequestsImport } from './routes/dashboard/requests'
|
||||||
import { Route as DashboardPurchasesImport } from './routes/dashboard/purchases'
|
import { Route as DashboardPurchasesImport } from './routes/dashboard/purchases'
|
||||||
|
import { Route as DashboardPeersImport } from './routes/dashboard/peers'
|
||||||
import { Route as DashboardNodesImport } from './routes/dashboard/nodes'
|
import { Route as DashboardNodesImport } from './routes/dashboard/nodes'
|
||||||
import { Route as DashboardLogsImport } from './routes/dashboard/logs'
|
import { Route as DashboardLogsImport } from './routes/dashboard/logs'
|
||||||
import { Route as DashboardHelpImport } from './routes/dashboard/help'
|
import { Route as DashboardHelpImport } from './routes/dashboard/help'
|
||||||
@ -34,7 +35,6 @@ import { Route as DashboardAboutImport } from './routes/dashboard/about'
|
|||||||
|
|
||||||
// Create Virtual Routes
|
// Create Virtual Routes
|
||||||
|
|
||||||
const DashboardPeersLazyImport = createFileRoute('/dashboard/peers')()
|
|
||||||
const DashboardAvailabilitiesLazyImport = createFileRoute(
|
const DashboardAvailabilitiesLazyImport = createFileRoute(
|
||||||
'/dashboard/availabilities',
|
'/dashboard/availabilities',
|
||||||
)()
|
)()
|
||||||
@ -71,14 +71,6 @@ const DashboardIndexRoute = DashboardIndexImport.update({
|
|||||||
getParentRoute: () => DashboardRoute,
|
getParentRoute: () => DashboardRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const DashboardPeersLazyRoute = DashboardPeersLazyImport.update({
|
|
||||||
id: '/peers',
|
|
||||||
path: '/peers',
|
|
||||||
getParentRoute: () => DashboardRoute,
|
|
||||||
} as any).lazy(() =>
|
|
||||||
import('./routes/dashboard/peers.lazy').then((d) => d.Route),
|
|
||||||
)
|
|
||||||
|
|
||||||
const DashboardAvailabilitiesLazyRoute =
|
const DashboardAvailabilitiesLazyRoute =
|
||||||
DashboardAvailabilitiesLazyImport.update({
|
DashboardAvailabilitiesLazyImport.update({
|
||||||
id: '/availabilities',
|
id: '/availabilities',
|
||||||
@ -112,6 +104,12 @@ const DashboardPurchasesRoute = DashboardPurchasesImport.update({
|
|||||||
getParentRoute: () => DashboardRoute,
|
getParentRoute: () => DashboardRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const DashboardPeersRoute = DashboardPeersImport.update({
|
||||||
|
id: '/peers',
|
||||||
|
path: '/peers',
|
||||||
|
getParentRoute: () => DashboardRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const DashboardNodesRoute = DashboardNodesImport.update({
|
const DashboardNodesRoute = DashboardNodesImport.update({
|
||||||
id: '/nodes',
|
id: '/nodes',
|
||||||
path: '/nodes',
|
path: '/nodes',
|
||||||
@ -261,6 +259,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof DashboardNodesImport
|
preLoaderRoute: typeof DashboardNodesImport
|
||||||
parentRoute: typeof DashboardImport
|
parentRoute: typeof DashboardImport
|
||||||
}
|
}
|
||||||
|
'/dashboard/peers': {
|
||||||
|
id: '/dashboard/peers'
|
||||||
|
path: '/peers'
|
||||||
|
fullPath: '/dashboard/peers'
|
||||||
|
preLoaderRoute: typeof DashboardPeersImport
|
||||||
|
parentRoute: typeof DashboardImport
|
||||||
|
}
|
||||||
'/dashboard/purchases': {
|
'/dashboard/purchases': {
|
||||||
id: '/dashboard/purchases'
|
id: '/dashboard/purchases'
|
||||||
path: '/purchases'
|
path: '/purchases'
|
||||||
@ -296,13 +301,6 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof DashboardAvailabilitiesLazyImport
|
preLoaderRoute: typeof DashboardAvailabilitiesLazyImport
|
||||||
parentRoute: typeof DashboardImport
|
parentRoute: typeof DashboardImport
|
||||||
}
|
}
|
||||||
'/dashboard/peers': {
|
|
||||||
id: '/dashboard/peers'
|
|
||||||
path: '/peers'
|
|
||||||
fullPath: '/dashboard/peers'
|
|
||||||
preLoaderRoute: typeof DashboardPeersLazyImport
|
|
||||||
parentRoute: typeof DashboardImport
|
|
||||||
}
|
|
||||||
'/dashboard/': {
|
'/dashboard/': {
|
||||||
id: '/dashboard/'
|
id: '/dashboard/'
|
||||||
path: '/'
|
path: '/'
|
||||||
@ -325,12 +323,12 @@ interface DashboardRouteChildren {
|
|||||||
DashboardHelpRoute: typeof DashboardHelpRoute
|
DashboardHelpRoute: typeof DashboardHelpRoute
|
||||||
DashboardLogsRoute: typeof DashboardLogsRoute
|
DashboardLogsRoute: typeof DashboardLogsRoute
|
||||||
DashboardNodesRoute: typeof DashboardNodesRoute
|
DashboardNodesRoute: typeof DashboardNodesRoute
|
||||||
|
DashboardPeersRoute: typeof DashboardPeersRoute
|
||||||
DashboardPurchasesRoute: typeof DashboardPurchasesRoute
|
DashboardPurchasesRoute: typeof DashboardPurchasesRoute
|
||||||
DashboardRequestsRoute: typeof DashboardRequestsRoute
|
DashboardRequestsRoute: typeof DashboardRequestsRoute
|
||||||
DashboardSettingsRoute: typeof DashboardSettingsRoute
|
DashboardSettingsRoute: typeof DashboardSettingsRoute
|
||||||
DashboardWalletRoute: typeof DashboardWalletRoute
|
DashboardWalletRoute: typeof DashboardWalletRoute
|
||||||
DashboardAvailabilitiesLazyRoute: typeof DashboardAvailabilitiesLazyRoute
|
DashboardAvailabilitiesLazyRoute: typeof DashboardAvailabilitiesLazyRoute
|
||||||
DashboardPeersLazyRoute: typeof DashboardPeersLazyRoute
|
|
||||||
DashboardIndexRoute: typeof DashboardIndexRoute
|
DashboardIndexRoute: typeof DashboardIndexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,12 +342,12 @@ const DashboardRouteChildren: DashboardRouteChildren = {
|
|||||||
DashboardHelpRoute: DashboardHelpRoute,
|
DashboardHelpRoute: DashboardHelpRoute,
|
||||||
DashboardLogsRoute: DashboardLogsRoute,
|
DashboardLogsRoute: DashboardLogsRoute,
|
||||||
DashboardNodesRoute: DashboardNodesRoute,
|
DashboardNodesRoute: DashboardNodesRoute,
|
||||||
|
DashboardPeersRoute: DashboardPeersRoute,
|
||||||
DashboardPurchasesRoute: DashboardPurchasesRoute,
|
DashboardPurchasesRoute: DashboardPurchasesRoute,
|
||||||
DashboardRequestsRoute: DashboardRequestsRoute,
|
DashboardRequestsRoute: DashboardRequestsRoute,
|
||||||
DashboardSettingsRoute: DashboardSettingsRoute,
|
DashboardSettingsRoute: DashboardSettingsRoute,
|
||||||
DashboardWalletRoute: DashboardWalletRoute,
|
DashboardWalletRoute: DashboardWalletRoute,
|
||||||
DashboardAvailabilitiesLazyRoute: DashboardAvailabilitiesLazyRoute,
|
DashboardAvailabilitiesLazyRoute: DashboardAvailabilitiesLazyRoute,
|
||||||
DashboardPeersLazyRoute: DashboardPeersLazyRoute,
|
|
||||||
DashboardIndexRoute: DashboardIndexRoute,
|
DashboardIndexRoute: DashboardIndexRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,12 +369,12 @@ export interface FileRoutesByFullPath {
|
|||||||
'/dashboard/help': typeof DashboardHelpRoute
|
'/dashboard/help': typeof DashboardHelpRoute
|
||||||
'/dashboard/logs': typeof DashboardLogsRoute
|
'/dashboard/logs': typeof DashboardLogsRoute
|
||||||
'/dashboard/nodes': typeof DashboardNodesRoute
|
'/dashboard/nodes': typeof DashboardNodesRoute
|
||||||
|
'/dashboard/peers': typeof DashboardPeersRoute
|
||||||
'/dashboard/purchases': typeof DashboardPurchasesRoute
|
'/dashboard/purchases': typeof DashboardPurchasesRoute
|
||||||
'/dashboard/requests': typeof DashboardRequestsRoute
|
'/dashboard/requests': typeof DashboardRequestsRoute
|
||||||
'/dashboard/settings': typeof DashboardSettingsRoute
|
'/dashboard/settings': typeof DashboardSettingsRoute
|
||||||
'/dashboard/wallet': typeof DashboardWalletRoute
|
'/dashboard/wallet': typeof DashboardWalletRoute
|
||||||
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
|
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
|
||||||
'/dashboard/peers': typeof DashboardPeersLazyRoute
|
|
||||||
'/dashboard/': typeof DashboardIndexRoute
|
'/dashboard/': typeof DashboardIndexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,12 +391,12 @@ export interface FileRoutesByTo {
|
|||||||
'/dashboard/help': typeof DashboardHelpRoute
|
'/dashboard/help': typeof DashboardHelpRoute
|
||||||
'/dashboard/logs': typeof DashboardLogsRoute
|
'/dashboard/logs': typeof DashboardLogsRoute
|
||||||
'/dashboard/nodes': typeof DashboardNodesRoute
|
'/dashboard/nodes': typeof DashboardNodesRoute
|
||||||
|
'/dashboard/peers': typeof DashboardPeersRoute
|
||||||
'/dashboard/purchases': typeof DashboardPurchasesRoute
|
'/dashboard/purchases': typeof DashboardPurchasesRoute
|
||||||
'/dashboard/requests': typeof DashboardRequestsRoute
|
'/dashboard/requests': typeof DashboardRequestsRoute
|
||||||
'/dashboard/settings': typeof DashboardSettingsRoute
|
'/dashboard/settings': typeof DashboardSettingsRoute
|
||||||
'/dashboard/wallet': typeof DashboardWalletRoute
|
'/dashboard/wallet': typeof DashboardWalletRoute
|
||||||
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
|
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
|
||||||
'/dashboard/peers': typeof DashboardPeersLazyRoute
|
|
||||||
'/dashboard': typeof DashboardIndexRoute
|
'/dashboard': typeof DashboardIndexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,12 +415,12 @@ export interface FileRoutesById {
|
|||||||
'/dashboard/help': typeof DashboardHelpRoute
|
'/dashboard/help': typeof DashboardHelpRoute
|
||||||
'/dashboard/logs': typeof DashboardLogsRoute
|
'/dashboard/logs': typeof DashboardLogsRoute
|
||||||
'/dashboard/nodes': typeof DashboardNodesRoute
|
'/dashboard/nodes': typeof DashboardNodesRoute
|
||||||
|
'/dashboard/peers': typeof DashboardPeersRoute
|
||||||
'/dashboard/purchases': typeof DashboardPurchasesRoute
|
'/dashboard/purchases': typeof DashboardPurchasesRoute
|
||||||
'/dashboard/requests': typeof DashboardRequestsRoute
|
'/dashboard/requests': typeof DashboardRequestsRoute
|
||||||
'/dashboard/settings': typeof DashboardSettingsRoute
|
'/dashboard/settings': typeof DashboardSettingsRoute
|
||||||
'/dashboard/wallet': typeof DashboardWalletRoute
|
'/dashboard/wallet': typeof DashboardWalletRoute
|
||||||
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
|
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
|
||||||
'/dashboard/peers': typeof DashboardPeersLazyRoute
|
|
||||||
'/dashboard/': typeof DashboardIndexRoute
|
'/dashboard/': typeof DashboardIndexRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -442,12 +440,12 @@ export interface FileRouteTypes {
|
|||||||
| '/dashboard/help'
|
| '/dashboard/help'
|
||||||
| '/dashboard/logs'
|
| '/dashboard/logs'
|
||||||
| '/dashboard/nodes'
|
| '/dashboard/nodes'
|
||||||
|
| '/dashboard/peers'
|
||||||
| '/dashboard/purchases'
|
| '/dashboard/purchases'
|
||||||
| '/dashboard/requests'
|
| '/dashboard/requests'
|
||||||
| '/dashboard/settings'
|
| '/dashboard/settings'
|
||||||
| '/dashboard/wallet'
|
| '/dashboard/wallet'
|
||||||
| '/dashboard/availabilities'
|
| '/dashboard/availabilities'
|
||||||
| '/dashboard/peers'
|
|
||||||
| '/dashboard/'
|
| '/dashboard/'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to:
|
to:
|
||||||
@ -463,12 +461,12 @@ export interface FileRouteTypes {
|
|||||||
| '/dashboard/help'
|
| '/dashboard/help'
|
||||||
| '/dashboard/logs'
|
| '/dashboard/logs'
|
||||||
| '/dashboard/nodes'
|
| '/dashboard/nodes'
|
||||||
|
| '/dashboard/peers'
|
||||||
| '/dashboard/purchases'
|
| '/dashboard/purchases'
|
||||||
| '/dashboard/requests'
|
| '/dashboard/requests'
|
||||||
| '/dashboard/settings'
|
| '/dashboard/settings'
|
||||||
| '/dashboard/wallet'
|
| '/dashboard/wallet'
|
||||||
| '/dashboard/availabilities'
|
| '/dashboard/availabilities'
|
||||||
| '/dashboard/peers'
|
|
||||||
| '/dashboard'
|
| '/dashboard'
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
@ -485,12 +483,12 @@ export interface FileRouteTypes {
|
|||||||
| '/dashboard/help'
|
| '/dashboard/help'
|
||||||
| '/dashboard/logs'
|
| '/dashboard/logs'
|
||||||
| '/dashboard/nodes'
|
| '/dashboard/nodes'
|
||||||
|
| '/dashboard/peers'
|
||||||
| '/dashboard/purchases'
|
| '/dashboard/purchases'
|
||||||
| '/dashboard/requests'
|
| '/dashboard/requests'
|
||||||
| '/dashboard/settings'
|
| '/dashboard/settings'
|
||||||
| '/dashboard/wallet'
|
| '/dashboard/wallet'
|
||||||
| '/dashboard/availabilities'
|
| '/dashboard/availabilities'
|
||||||
| '/dashboard/peers'
|
|
||||||
| '/dashboard/'
|
| '/dashboard/'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
@ -542,12 +540,12 @@ export const routeTree = rootRoute
|
|||||||
"/dashboard/help",
|
"/dashboard/help",
|
||||||
"/dashboard/logs",
|
"/dashboard/logs",
|
||||||
"/dashboard/nodes",
|
"/dashboard/nodes",
|
||||||
|
"/dashboard/peers",
|
||||||
"/dashboard/purchases",
|
"/dashboard/purchases",
|
||||||
"/dashboard/requests",
|
"/dashboard/requests",
|
||||||
"/dashboard/settings",
|
"/dashboard/settings",
|
||||||
"/dashboard/wallet",
|
"/dashboard/wallet",
|
||||||
"/dashboard/availabilities",
|
"/dashboard/availabilities",
|
||||||
"/dashboard/peers",
|
|
||||||
"/dashboard/"
|
"/dashboard/"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -593,6 +591,10 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "dashboard/nodes.tsx",
|
"filePath": "dashboard/nodes.tsx",
|
||||||
"parent": "/dashboard"
|
"parent": "/dashboard"
|
||||||
},
|
},
|
||||||
|
"/dashboard/peers": {
|
||||||
|
"filePath": "dashboard/peers.tsx",
|
||||||
|
"parent": "/dashboard"
|
||||||
|
},
|
||||||
"/dashboard/purchases": {
|
"/dashboard/purchases": {
|
||||||
"filePath": "dashboard/purchases.tsx",
|
"filePath": "dashboard/purchases.tsx",
|
||||||
"parent": "/dashboard"
|
"parent": "/dashboard"
|
||||||
@ -613,10 +615,6 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "dashboard/availabilities.lazy.tsx",
|
"filePath": "dashboard/availabilities.lazy.tsx",
|
||||||
"parent": "/dashboard"
|
"parent": "/dashboard"
|
||||||
},
|
},
|
||||||
"/dashboard/peers": {
|
|
||||||
"filePath": "dashboard/peers.lazy.tsx",
|
|
||||||
"parent": "/dashboard"
|
|
||||||
},
|
|
||||||
"/dashboard/": {
|
"/dashboard/": {
|
||||||
"filePath": "dashboard/index.tsx",
|
"filePath": "dashboard/index.tsx",
|
||||||
"parent": "/dashboard"
|
"parent": "/dashboard"
|
||||||
|
|||||||
@ -1,17 +1,12 @@
|
|||||||
import {
|
import { createFileRoute, Outlet } from "@tanstack/react-router";
|
||||||
createFileRoute,
|
|
||||||
Outlet,
|
|
||||||
useRouterState,
|
|
||||||
} from "@tanstack/react-router";
|
|
||||||
import "./layout.css";
|
import "./layout.css";
|
||||||
import { Menu } from "../components/Menu/Menu";
|
import { Menu } from "../components/Menu/Menu";
|
||||||
import { useEffect, useState } from "react";
|
import { useState } from "react";
|
||||||
import { AppBar } from "../components/AppBar/AppBar";
|
import { AppBar } from "../components/AppBar/AppBar";
|
||||||
import { Backdrop } from "@codex-storage/marketplace-ui-components";
|
import { Backdrop } from "@codex-storage/marketplace-ui-components";
|
||||||
|
|
||||||
const Layout = () => {
|
const Layout = () => {
|
||||||
const [hasMobileMenu, setHasMobileMenu] = useState(false);
|
const [hasMobileMenu, setHasMobileMenu] = useState(false);
|
||||||
const router = useRouterState();
|
|
||||||
|
|
||||||
const onIconClick = () => {
|
const onIconClick = () => {
|
||||||
if (window.innerWidth <= 999) {
|
if (window.innerWidth <= 999) {
|
||||||
@ -19,10 +14,6 @@ const Layout = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setHasMobileMenu(false);
|
|
||||||
}, [router.location.pathname]);
|
|
||||||
|
|
||||||
const onClose = () => setHasMobileMenu(false);
|
const onClose = () => setHasMobileMenu(false);
|
||||||
|
|
||||||
const isMobileMenuDisplayed =
|
const isMobileMenuDisplayed =
|
||||||
|
|||||||
15
src/routes/dashboard/peers.tsx
Normal file
15
src/routes/dashboard/peers.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { ErrorBoundary } from "@sentry/react";
|
||||||
|
import { ErrorPlaceholder } from "../../components/ErrorPlaceholder/ErrorPlaceholder";
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { Peers } from "../../components/Peers/Peers";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/dashboard/peers")({
|
||||||
|
component: () => (
|
||||||
|
<ErrorBoundary
|
||||||
|
fallback={({ error }) => (
|
||||||
|
<ErrorPlaceholder error={error} subtitle="Cannot retrieve the data." />
|
||||||
|
)}>
|
||||||
|
<Peers />
|
||||||
|
</ErrorBoundary>
|
||||||
|
),
|
||||||
|
});
|
||||||
@ -1,5 +0,0 @@
|
|||||||
export const Echo = {
|
|
||||||
portForwarding: (port: number) => fetch(import.meta.env.VITE_GEO_IP_URL + "/port/" + port)
|
|
||||||
.then((res) => res.json())
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
export const Network = {
|
|
||||||
getIp(address: string) {
|
|
||||||
const [ip] = address.split(":")
|
|
||||||
return ip
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +1,8 @@
|
|||||||
export const Strings = {
|
export const Strings = {
|
||||||
shortId: (id: string) => id.slice(0, 5) + "..." + id.slice(-5),
|
shortId: (id: string) => id.slice(0, 5) + "..." + id.slice(-5),
|
||||||
|
|
||||||
splitURLAndPort(url: string) {
|
|
||||||
const [protocol, hostname = "", port = ""] = url.split(":")
|
|
||||||
|
|
||||||
return [protocol + ":" + hostname, port]
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,8 @@ export default defineConfig({
|
|||||||
output: {
|
output: {
|
||||||
manualChunks: {
|
manualChunks: {
|
||||||
"@sentry/react": ["@sentry/react"],
|
"@sentry/react": ["@sentry/react"],
|
||||||
"emoji-picker-react": ["emoji-picker-react"]
|
"emoji-picker-react": ["emoji-picker-react"],
|
||||||
|
"dotted-map": ["dotted-map"],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onwarn(warning, defaultHandler) {
|
onwarn(warning, defaultHandler) {
|
||||||
|
|||||||
8
vitest.config.ts
Normal file
8
vitest.config.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/// <reference types="vitest" />
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
include: ["src/**/*.test.ts"]
|
||||||
|
},
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user