Add unit tests and fix minor issues
This commit is contained in:
parent
3260ae5839
commit
29ff705f58
|
@ -112,16 +112,6 @@ jobs:
|
|||
- name: 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
|
||||
run: npx playwright install --with-deps
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
||||
test('create an availability', async ({ page }) => {
|
||||
await page.goto('/dashboard');
|
||||
await page.getByRole('link', { name: 'Sales' }).click();
|
||||
await page.goto('/dashboard/availabilities');
|
||||
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').fill('0.50');
|
||||
await page.getByLabel('Duration').click();
|
||||
|
@ -18,7 +17,7 @@ test('create an availability', async ({ page }) => {
|
|||
await page.getByLabel('Nickname').fill('test');
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
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 expect(page.getByText('Success', { exact: true })).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Finish' }).click();
|
||||
|
@ -28,31 +27,31 @@ test('create an availability', async ({ page }) => {
|
|||
test('availability navigation buttons', async ({ page }) => {
|
||||
await page.goto('/dashboard/availabilities');
|
||||
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-active')).toBeVisible()
|
||||
await expect(page.locator('.stepper-buttons .button--primary')).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('.stepper-buttons .button--outline')).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('.step--active')).toBeVisible()
|
||||
await expect(page.locator('footer .button--primary')).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').fill('19');
|
||||
await expect(page.locator('.stepper-buttons .button--outline')).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('.stepper-buttons .button--primary')).toHaveAttribute("disabled");
|
||||
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('footer .button--primary')).toHaveAttribute("disabled");
|
||||
await page.getByLabel('Total size').click();
|
||||
await page.getByLabel('Total size').fill('0.5');
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
await expect(page.locator('.stepper-buttons .button--outline')).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('.stepper-buttons .button--primary')).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('.stepper-number-done')).toBeVisible()
|
||||
await expect(page.locator('.stepper-number-active')).toBeVisible()
|
||||
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('.step--done')).toBeVisible()
|
||||
await expect(page.locator('.step--active')).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Back' }).click();
|
||||
await expect(page.locator('.stepper-number-done')).not.toBeVisible()
|
||||
await expect(page.locator('.stepper-number-active')).toBeVisible()
|
||||
await expect(page.locator('.step--done')).not.toBeVisible()
|
||||
await expect(page.locator('.step--active')).toBeVisible()
|
||||
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('.stepper-number-active')).toBeVisible()
|
||||
await expect(page.locator('.stepper-buttons .button--outline')).toHaveAttribute("disabled");
|
||||
await expect(page.locator('.stepper-buttons .button--primary')).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('.step--done')).toHaveCount(2)
|
||||
await expect(page.locator('.step--active')).toBeVisible()
|
||||
await expect(page.locator('footer .button--outline').first()).toHaveAttribute("disabled");
|
||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||
await page.getByRole('button', { name: 'Finish' }).click();
|
||||
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();
|
||||
const handle = await page.evaluateHandle(() => navigator.clipboard.readText());
|
||||
const cid = await handle.jsonValue()
|
||||
await page.locator('.sheets-container > .backdrop').click();
|
||||
await page.getByPlaceholder('CID').click();
|
||||
await page.getByPlaceholder('CID').fill(cid);
|
||||
await page.locator('.sheets > .backdrop').click();
|
||||
await page.locator('.download-input input').click();
|
||||
await page.locator('.download-input input').fill(cid);
|
||||
// const page1Promise = page.waitForEvent('popup');
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
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 }) => {
|
||||
await page.context().setOffline(false)
|
||||
await page.goto('/');
|
||||
await expect(page.locator('#root')).toContainText('Network connected');
|
||||
await page.locator('a').nth(2).click();
|
||||
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('.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 expect(page.locator('#root')).toContainText('Network disconnected');
|
||||
await page.getByLabel('Display name').click();
|
||||
await page.getByLabel('Display name').fill('Arnaud');
|
||||
await page.locator('a').click();
|
||||
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()
|
||||
|
||||
// Network
|
||||
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-error")).toBeVisible()
|
||||
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-success")).not.toBeVisible()
|
||||
|
||||
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";
|
||||
|
||||
test('update the log level', async ({ page }) => {
|
||||
await page.goto('/dashboard');
|
||||
await page.getByRole('link', { name: 'Settings' }).click();
|
||||
await page.getByLabel('Log level').selectOption('TRACE');
|
||||
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();
|
||||
})
|
||||
// test('update the log level', async ({ page }) => {
|
||||
// await page.goto('/dashboard');
|
||||
// await page.getByRole('link', { name: 'Settings' }).click();
|
||||
// await page.getByLabel('Log level').selectOption('TRACE');
|
||||
// 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();
|
||||
// })
|
||||
|
||||
test('update the URL with wrong URL applies', async ({ page }) => {
|
||||
await page.goto('/dashboard');
|
||||
await page.getByRole('link', { name: 'Settings' }).click();
|
||||
await page.getByLabel('Codex client node URL').click();
|
||||
await page.getByLabel('Codex client node URL').fill('hello');
|
||||
await expect.soft(page.getByText("The URL is not valid")).toBeVisible()
|
||||
await expect.soft(page.locator(".settings-url-button")).toBeDisabled()
|
||||
await page.getByLabel('Codex client node URL').fill('http://127.0.0.1:8079');
|
||||
await expect.soft(page.getByText("The URL is not valid")).not.toBeVisible()
|
||||
await expect.soft(page.locator(".settings-url-button")).not.toBeDisabled()
|
||||
await page.getByRole('button', { name: 'Save changes' }).nth(1).click();
|
||||
await expect.soft(page.getByText("Cannot retrieve the data")).toBeVisible()
|
||||
await page.getByLabel('Codex client node URL').fill('http://127.0.0.1:8080');
|
||||
await page.getByRole('button', { name: 'Save changes' }).nth(1).click();
|
||||
await page.locator('a').filter({ hasText: 'Settings' }).click();
|
||||
await page.getByLabel('Address').click();
|
||||
await page.getByLabel('Address').fill('hello');
|
||||
await expect(page.getByLabel('Address')).toHaveAttribute("aria-invalid")
|
||||
await expect(page.locator(".refresh svg")).toHaveAttribute("aria-disabled")
|
||||
await page.getByLabel('Address').fill('http://127.0.0.1:8079');
|
||||
await expect(page.getByLabel('Address')).not.toHaveAttribute("aria-invalid")
|
||||
await expect(page.locator(".refresh svg")).not.toHaveAttribute("aria-disabled")
|
||||
await expect(page.getByLabel('Address')).toHaveValue("http://127.0.0.1")
|
||||
await expect(page.getByLabel('Port')).toHaveValue("8079")
|
||||
await page.locator(".refresh").click()
|
||||
await expect(page.locator(".health-checks ul li").nth(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 }) => {
|
||||
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.locator('div').getByTestId("upload").setInputFiles([
|
||||
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 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([
|
||||
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.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();
|
||||
})
|
||||
|
||||
test('storage request navigation buttons', async ({ page }) => {
|
||||
await page.goto('/dashboard/purchases');
|
||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
||||
await expect(page.locator('.stepper-number-done')).not.toBeVisible()
|
||||
await expect(page.locator('.stepper-number-active')).toBeVisible()
|
||||
await expect(page.locator('.stepper-buttons .button--primary')).toHaveAttribute("disabled");
|
||||
await expect(page.locator('.stepper-buttons .button--outline')).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('.step--done')).not.toBeVisible()
|
||||
await expect(page.locator('.step--active')).toBeVisible()
|
||||
await expect(page.locator('footer .button--primary')).toHaveAttribute("disabled");
|
||||
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||
]);
|
||||
await expect(page.locator('.stepper-buttons .button--outline')).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('.stepper-buttons .button--primary')).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
await expect(page.locator('.stepper-buttons .button--outline')).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('.stepper-buttons .button--primary')).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('.stepper-number-done')).toBeVisible()
|
||||
await expect(page.locator('.stepper-number-active')).toBeVisible()
|
||||
await expect(page.locator('footer .button--outline').first()).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('.step--done')).toBeVisible()
|
||||
await expect(page.locator('.step--active')).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Back' }).click();
|
||||
await expect(page.locator('.stepper-number-done')).not.toBeVisible()
|
||||
await expect(page.locator('.stepper-number-active')).toBeVisible()
|
||||
await expect(page.locator('.step--done')).not.toBeVisible()
|
||||
await expect(page.locator('.step--active')).toBeVisible()
|
||||
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('.stepper-number-active')).toBeVisible()
|
||||
await expect(page.locator('.stepper-buttons .button--outline')).toHaveAttribute("disabled");
|
||||
await expect(page.locator('.stepper-buttons .button--primary')).not.toHaveAttribute("disabled");
|
||||
await expect(page.locator('.step--done')).toHaveCount(2)
|
||||
await expect(page.locator('.step--active')).toBeVisible()
|
||||
await expect(page.locator('footer .button--outline').first()).toHaveAttribute("disabled");
|
||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||
await page.getByRole('button', { name: 'Finish' }).click();
|
||||
await expect(page.locator('.modal--open')).not.toBeVisible();
|
||||
})
|
||||
|
||||
test('remove the CID when the file is deleted', async ({ page }) => {
|
||||
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.locator('div').getByTestId("upload").setInputFiles([
|
||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||
]);
|
||||
await expect(page.locator('#cid')).toHaveValue("zDvZRwzkvwapyNeL4mzw5gBsZvyn7x8F8Y9n4RYSC7ETBssDYpGe")
|
||||
await page.locator('.uploadFile-infoRight .buttonIcon--small').click();
|
||||
await expect(page.locator('#cid')).toHaveValue("")
|
||||
await expect(page.locator('#cid')).not.toBeEmpty()
|
||||
await page.locator('.button-icon--small').click();
|
||||
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 page.locator('.uploadFile-infoRight > .buttonIcon').first().click();
|
||||
await page.locator('.uploadFile-infoRight > .buttonIcon').click();
|
||||
await page.locator('.upload-file .button-icon--small').first().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').nth(1)).not.toBeVisible();
|
||||
|
@ -42,7 +42,7 @@ test('drag and drop file', async ({ page }) => {
|
|||
const dataTransfer = await page.evaluateHandle((data) => {
|
||||
const dt = new DataTransfer();
|
||||
// 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);
|
||||
return dt;
|
||||
}, buffer);
|
||||
|
@ -63,7 +63,7 @@ test('drag and drop file', async ({ page }) => {
|
|||
// 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();
|
||||
// });
|
|
@ -41,7 +41,8 @@
|
|||
"postcss-nesting": "^13.0.1",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "5.5.4",
|
||||
"vite": "^5.4.7"
|
||||
"vite": "^5.4.7",
|
||||
"vitest": "^2.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
@ -1994,6 +1995,112 @@
|
|||
"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": {
|
||||
"version": "8.12.1",
|
||||
"dev": true,
|
||||
|
@ -2065,6 +2172,15 @@
|
|||
"dev": true,
|
||||
"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": {
|
||||
"version": "1.0.6",
|
||||
"dev": true,
|
||||
|
@ -2152,6 +2268,15 @@
|
|||
"optional": 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": {
|
||||
"version": "3.1.0",
|
||||
"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": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
|
@ -2194,6 +2335,15 @@
|
|||
"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": {
|
||||
"version": "3.6.0",
|
||||
"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": {
|
||||
"version": "0.1.4",
|
||||
"dev": true,
|
||||
|
@ -2672,6 +2831,15 @@
|
|||
"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": {
|
||||
"version": "2.0.3",
|
||||
"dev": true,
|
||||
|
@ -2680,6 +2848,15 @@
|
|||
"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": {
|
||||
"version": "3.1.3",
|
||||
"dev": true,
|
||||
|
@ -3103,6 +3280,12 @@
|
|||
"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": {
|
||||
"version": "5.1.1",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
|
@ -3289,6 +3481,21 @@
|
|||
"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": {
|
||||
"version": "1.1.0",
|
||||
"license": "ISC"
|
||||
|
@ -4070,6 +4277,12 @@
|
|||
"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": {
|
||||
"version": "1.2.1",
|
||||
"license": "BSD-3-Clause",
|
||||
|
@ -4077,6 +4290,18 @@
|
|||
"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": {
|
||||
"version": "6.0.1",
|
||||
"dev": true,
|
||||
|
@ -4124,6 +4349,45 @@
|
|||
"version": "1.0.3",
|
||||
"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": {
|
||||
"version": "2.0.0",
|
||||
"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": {
|
||||
"version": "0.21.5",
|
||||
"cpu": [
|
||||
|
@ -4765,6 +5050,71 @@
|
|||
"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": {
|
||||
"version": "0.6.2",
|
||||
"dev": true,
|
||||
|
@ -4784,6 +5134,22 @@
|
|||
"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": {
|
||||
"version": "1.3.3",
|
||||
"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",
|
||||
"preview": "vite preview --host 127.0.0.1 --port 5173",
|
||||
"format": "prettier --write ./src",
|
||||
"test": "npx playwright test"
|
||||
"test": "npx playwright test",
|
||||
"test:unit": "vitest run"
|
||||
},
|
||||
"keywords": [
|
||||
"Codex",
|
||||
|
@ -56,7 +57,8 @@
|
|||
"postcss-nesting": "^13.0.1",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "5.5.4",
|
||||
"vite": "^5.4.7"
|
||||
"vite": "^5.4.7",
|
||||
"vitest": "^2.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
|
|
@ -8,7 +8,7 @@ import { ReactElement, useEffect } from "react";
|
|||
import { useCodexConnection } from "../../hooks/useCodexConnection";
|
||||
import { NodesIcon } from "../Menu/NodesIcon";
|
||||
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 { SettingsIcon } from "../Menu/SettingsIcon";
|
||||
|
||||
|
@ -33,8 +33,8 @@ export function AppBar({ onIconClick }: Props) {
|
|||
const queryClient = useQueryClient();
|
||||
const codex = useCodexConnection();
|
||||
const persistence = usePersistence(codex.enabled);
|
||||
const router = useRouterState();
|
||||
const navigate = useNavigate({ from: router.location.pathname });
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate({ from: location.pathname });
|
||||
|
||||
useEffect(() => {
|
||||
queryClient.invalidateQueries({
|
||||
|
@ -43,13 +43,12 @@ export function AppBar({ onIconClick }: Props) {
|
|||
});
|
||||
}, [queryClient, codex.enabled]);
|
||||
|
||||
const onNodeClick = () => navigate({ to: "/dashboard/settings" });
|
||||
|
||||
const offline = !online || !codex.enabled;
|
||||
|
||||
const onNodeClick = () => navigate({ to: "/dashboard/settings" });
|
||||
|
||||
const title =
|
||||
router.location.pathname.split("/")[2] ||
|
||||
router.location.pathname.split("/")[1];
|
||||
location.pathname.split("/")[2] || location.pathname.split("/")[1];
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.cardNumber-dataContainer .buttonIcon {
|
||||
.cardNumber-dataContainer .button-icon {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export function ErrorCircleIcon() {
|
||||
return (
|
||||
<svg
|
||||
data-testid="icon-error"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
|
|
|
@ -3,16 +3,17 @@ import { useEffect, useState } from "react";
|
|||
import { useDebug } from "../../hooks/useDebug";
|
||||
import { usePersistence } from "../../hooks/usePersistence";
|
||||
import { usePortForwarding } from "../../hooks/usePortForwarding";
|
||||
import { CodexSdk } from "../../proxy";
|
||||
import { ErrorCircleIcon } from "../ErrorCircleIcon/ErrorCircleIcon";
|
||||
import { SuccessCheckIcon } from "../SuccessCheckIcon/SuccessCheckIcon";
|
||||
import { WarningIcon } from "../WarningIcon/WarningIcon";
|
||||
import { HealthCheckIcon } from "./HealthCheckIcon";
|
||||
import { Input } from "@codex-storage/marketplace-ui-components";
|
||||
import { classnames } from "../../utils/classnames";
|
||||
import { DebugUtils } from "../../utils/debug";
|
||||
import { RefreshIcon } from "../RefreshIcon/RefreshIcon";
|
||||
import "./HealthChecks.css";
|
||||
import { CodexSdk } from "../../sdk/codex";
|
||||
import { HealthCheckUtil } from "./health-check.util";
|
||||
import { PortForwardingUtil } from "../../hooks/port-forwarding.util";
|
||||
|
||||
type Props = {
|
||||
online: boolean;
|
||||
|
@ -29,45 +30,40 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
|||
const [isAddressInvalid, setIsAddressInvalid] = useState(false);
|
||||
const [isPortInvalid, setIsPortInvalid] = useState(false);
|
||||
const [address, setAddress] = useState(
|
||||
CodexSdk.url().split(":")[0] + ":" + CodexSdk.url().split(":")[1]
|
||||
);
|
||||
const [port, setPort] = useState(
|
||||
parseInt(CodexSdk.url().split(":")[2] || "80", 10)
|
||||
HealthCheckUtil.removePort(CodexSdk.url())
|
||||
);
|
||||
const [port, setPort] = useState(HealthCheckUtil.getPort(CodexSdk.url()));
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useEffect(() => {
|
||||
if (codex.isSuccess) {
|
||||
persistence.refetch();
|
||||
portForwarding.refetch().then(({ data }) => {
|
||||
onStepValid(data?.reachable || false);
|
||||
});
|
||||
} else {
|
||||
onStepValid(false);
|
||||
}
|
||||
}, [
|
||||
persistence.refetch,
|
||||
onStepValid,
|
||||
portForwarding.refetch,
|
||||
codex.isSuccess,
|
||||
]);
|
||||
useEffect(
|
||||
() => {
|
||||
if (codex.isSuccess) {
|
||||
persistence.refetch();
|
||||
portForwarding.refetch().then(({ data }) => {
|
||||
onStepValid(data?.reachable || false);
|
||||
});
|
||||
} else {
|
||||
onStepValid(false);
|
||||
}
|
||||
},
|
||||
// We really do not want to add persistence and portForwarding as
|
||||
// dependencies because it will cause a re-render every time.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[persistence.refetch, onStepValid, portForwarding.refetch, codex.isSuccess]
|
||||
);
|
||||
|
||||
const onAddressChange = (e: React.FormEvent<HTMLInputElement>) => {
|
||||
const element = e.currentTarget;
|
||||
const parts = e.currentTarget.value.split(":");
|
||||
const value = e.currentTarget.value;
|
||||
|
||||
setIsAddressInvalid(!element.checkValidity());
|
||||
|
||||
if (parts.length > 2) {
|
||||
const [protocol, addr, port] = parts;
|
||||
setAddress(protocol + ":" + addr);
|
||||
const address = HealthCheckUtil.removePort(value);
|
||||
setAddress(address);
|
||||
|
||||
const p = parseInt(port, 10);
|
||||
if (!isNaN(p)) {
|
||||
setPort(p);
|
||||
}
|
||||
} else {
|
||||
setAddress(parts.join(":"));
|
||||
if (HealthCheckUtil.containsPort(value)) {
|
||||
const p = HealthCheckUtil.getPort(value);
|
||||
setPort(p);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -80,11 +76,13 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
|||
};
|
||||
|
||||
const onSave = () => {
|
||||
if (isAddressInvalid || isPortInvalid) {
|
||||
const url = address + ":" + port;
|
||||
|
||||
if (HealthCheckUtil.isUrlInvalid(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CodexSdk.updateURL(address + ":" + port)
|
||||
CodexSdk.updateURL(url)
|
||||
.then(() => queryClient.invalidateQueries())
|
||||
.then(() => codex.refetch());
|
||||
};
|
||||
|
@ -92,7 +90,7 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
|||
let forwardingPortValue = defaultPort;
|
||||
|
||||
if (codex.isSuccess && codex.data) {
|
||||
const port = DebugUtils.getTcpPort(codex.data);
|
||||
const port = PortForwardingUtil.getTcpPort(codex.data);
|
||||
if (!port.error) {
|
||||
forwardingPortValue = port.data;
|
||||
}
|
||||
|
@ -135,7 +133,9 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
|||
</div>
|
||||
|
||||
<div className="refresh">
|
||||
<RefreshIcon onClick={onSave}></RefreshIcon>
|
||||
<RefreshIcon
|
||||
onClick={onSave}
|
||||
disabled={isAddressInvalid || isPortInvalid}></RefreshIcon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
})
|
|
@ -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 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.health-checks {
|
||||
.address {
|
||||
.refresh {
|
||||
top: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.get-started {
|
||||
|
|
|
@ -1,22 +1,14 @@
|
|||
import { Cell } from "@codex-storage/marketplace-ui-components";
|
||||
import { PeerPin } from "./types";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import "./PeerCountryCell.css";
|
||||
import { useEffect } from "react";
|
||||
import { PeerPin, PeerUtils } from "./peers.util";
|
||||
|
||||
export type Props = {
|
||||
address: string;
|
||||
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) {
|
||||
const { data } = useQuery({
|
||||
queryFn: () => {
|
||||
|
@ -61,7 +53,7 @@ export function PeerCountryCell({ address, onPinAdd }: Props) {
|
|||
<div className="peer-country">
|
||||
{data ? (
|
||||
<>
|
||||
<span> {!!data && getFlagEmoji(data.country_iso)}</span>
|
||||
<span> {!!data && PeerUtils.geCountryEmoji(data.country_iso)}</span>
|
||||
<span>{data?.country}</span>
|
||||
</>
|
||||
) : (
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
max-width: 1320px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-bottom: 32px;
|
||||
|
||||
> div {
|
||||
max-width: 1320px;
|
||||
|
@ -194,7 +195,7 @@
|
|||
|
||||
div:nth-child(1) {
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,78 +1,36 @@
|
|||
import {
|
||||
Cell,
|
||||
Row,
|
||||
Table,
|
||||
TabSortState,
|
||||
Row,
|
||||
Cell,
|
||||
Table,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { getMapJSON } from "dotted-map";
|
||||
import DottedMap from "dotted-map/without-countries";
|
||||
import { Promises } from "../../utils/promises";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { PeerCountryCell } from "../../components/Peers/PeerCountryCell";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { PeerPin } from "../../components/Peers/types";
|
||||
import "./peers.css";
|
||||
import { CodexSdk } from "../../sdk/codex";
|
||||
import { ErrorBoundary } from "@sentry/react";
|
||||
import { ErrorPlaceholder } from "../../components/ErrorPlaceholder/ErrorPlaceholder";
|
||||
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";
|
||||
import { useRef, useState, useCallback } from "react";
|
||||
import { ErrorCircleIcon } from "../ErrorCircleIcon/ErrorCircleIcon";
|
||||
import { PeersIcon } from "../Menu/PeersIcon";
|
||||
import { PeerCountryCell } from "./PeerCountryCell";
|
||||
import { SuccessCheckIcon } from "../SuccessCheckIcon/SuccessCheckIcon";
|
||||
import { useDebug } from "../../hooks/useDebug";
|
||||
import { getMapJSON } from "dotted-map";
|
||||
import "./Peers.css";
|
||||
import { PeerPin, PeerSortFn, PeerUtils } from "./peers.util";
|
||||
|
||||
// This function accepts the same arguments as DottedMap in the example above.
|
||||
const mapJsonString = getMapJSON({ height: 60, grid: "diagonal" });
|
||||
|
||||
type CustomCSSProperties = React.CSSProperties & {
|
||||
"--codex-peers-percent": number;
|
||||
"--codex-peers-degrees": number;
|
||||
};
|
||||
|
||||
type Node = {
|
||||
nodeId: string;
|
||||
peerId: string;
|
||||
record: string;
|
||||
address: string;
|
||||
seen: boolean;
|
||||
};
|
||||
const throwOnError = true;
|
||||
|
||||
type SortFn = (a: Node, b: Node) => number;
|
||||
|
||||
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 = () => {
|
||||
export const Peers = () => {
|
||||
const ips = useRef<Record<string, string>>({});
|
||||
const [pins, setPins] = useState<[PeerPin, number][]>([]);
|
||||
const [sortFn, setSortFn] = useState<SortFn | null>(() =>
|
||||
sortByBooleanValue("desc")
|
||||
const [sortFn, setSortFn] = useState<PeerSortFn | null>(() =>
|
||||
PeerUtils.sortByBoolean("desc")
|
||||
);
|
||||
const { data } = useQuery({
|
||||
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 { data } = useDebug(throwOnError);
|
||||
|
||||
const onPinAdd = useCallback(
|
||||
({
|
||||
|
@ -80,11 +38,7 @@ const Peers = () => {
|
|||
ip,
|
||||
...pin
|
||||
}: PeerPin & { countryIso: string; ip: string }) => {
|
||||
setPins((val) => {
|
||||
const [, quantity = 0] =
|
||||
val.find(([p]) => p.lat === pin.lat && p.lng == pin.lng) || [];
|
||||
return [...val, [pin, quantity + 1]];
|
||||
});
|
||||
setPins((val) => PeerUtils.incPin(val, pin));
|
||||
ips.current[ip] = countryIso;
|
||||
},
|
||||
[]
|
||||
|
@ -115,24 +69,16 @@ const Peers = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
setSortFn(() => (a: Node, b: Node) => {
|
||||
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);
|
||||
});
|
||||
setSortFn(() => PeerUtils.sortByCountry(state, ips.current));
|
||||
};
|
||||
|
||||
const onSortActive = (state: TabSortState) => {
|
||||
console.info("fdf");
|
||||
if (!state) {
|
||||
setSortFn(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setSortFn(() => sortByBooleanValue(state));
|
||||
setSortFn(() => PeerUtils.sortByBoolean(state));
|
||||
};
|
||||
|
||||
const headers = [
|
||||
|
@ -165,15 +111,14 @@ const Peers = () => {
|
|||
]}></Row>
|
||||
));
|
||||
|
||||
const actives = sorted.reduce((acc, cur) => acc + (cur.seen ? 1 : 0), 0) || 0;
|
||||
const total = data?.table.nodes.length || 1;
|
||||
const actives = PeerUtils.countActives(sorted);
|
||||
const degrees = PeerUtils.calculareDegrees(sorted);
|
||||
const good = actives > 0;
|
||||
|
||||
const styles: CustomCSSProperties = {
|
||||
"--codex-peers-percent": (actives / total) * 180,
|
||||
"--codex-peers-degrees": degrees,
|
||||
};
|
||||
|
||||
const good = actives > 0;
|
||||
|
||||
return (
|
||||
<div className="peers">
|
||||
<div>
|
||||
|
@ -217,14 +162,3 @@ const Peers = () => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Route = createLazyFileRoute("/dashboard/peers")({
|
||||
component: () => (
|
||||
<ErrorBoundary
|
||||
fallback={({ error }) => (
|
||||
<ErrorPlaceholder error={error} subtitle="Cannot retrieve the data." />
|
||||
)}>
|
||||
<Peers />
|
||||
</ErrorBoundary>
|
||||
),
|
||||
});
|
|
@ -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"), "🇫🇷")
|
||||
});
|
||||
})
|
|
@ -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 = {
|
||||
onClick?: () => void;
|
||||
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 (
|
||||
<svg
|
||||
{...attributes({ "aria-disabled": disabled })}
|
||||
className={className}
|
||||
onClick={onClick}
|
||||
width="24"
|
||||
|
@ -15,7 +20,7 @@ export function RefreshIcon({ className, onClick }: Props) {
|
|||
xmlns="http://www.w3.org/2000/svg">
|
||||
<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"
|
||||
fill="var(--codex-color-primary)"
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
|
|
@ -7,6 +7,7 @@ export function SuccessCheckIcon({ variant }: Props) {
|
|||
|
||||
return (
|
||||
<svg
|
||||
data-testid="icon-success"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
|
|
|
@ -54,6 +54,8 @@ export function UserInfo({ onNameChange }: Props) {
|
|||
"objects",
|
||||
"symbols",
|
||||
"flags",
|
||||
// The type does not allow a string array but the api yes
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
] as any
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -2,14 +2,14 @@ import { useDebug } from "../../hooks/useDebug";
|
|||
import { AlphaText } from "../AlphaText/AlphaText";
|
||||
import { AlphaIcon } from "../OnBoarding/AlphaIcon";
|
||||
import "./Versions.css";
|
||||
import { VersionsUtil } from "./versions.util";
|
||||
|
||||
const throwOnError = true;
|
||||
const throwOnError = false;
|
||||
|
||||
export function Versions() {
|
||||
const debug = useDebug(throwOnError);
|
||||
|
||||
const parts = debug.data?.codex.version.split("\n") || [""];
|
||||
const version = parts[parts.length - 1];
|
||||
const version = VersionsUtil.clientVersion(debug.data?.codex.version);
|
||||
|
||||
return (
|
||||
<div className="versions">
|
||||
|
@ -20,7 +20,7 @@ export function Versions() {
|
|||
</div>
|
||||
<div>
|
||||
<p>Vault</p>
|
||||
<small>VER. {import.meta.env.PACKAGE_VERSION}</small>
|
||||
<small>VER. {VersionsUtil.codexVersion()}</small>
|
||||
<AlphaText variant="failure" width={37}></AlphaText>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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")
|
||||
})
|
||||
})
|
|
@ -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> {
|
||||
if (info.addrs.length === 0) {
|
||||
return { error: true, data: new CodexError("Not existing address") }
|
||||
|
@ -20,4 +23,5 @@ export const DebugUtils = {
|
|||
|
||||
return { error: false, data: port }
|
||||
}
|
||||
|
||||
}
|
|
@ -27,6 +27,8 @@ export function useCodexConnection() {
|
|||
|
||||
// Cache is not useful for the spr endpoint
|
||||
gcTime: 0,
|
||||
|
||||
throwOnError: false,
|
||||
});
|
||||
|
||||
return { enabled: !isError && !!data, isFetching, refetch };
|
||||
|
|
|
@ -6,12 +6,13 @@ const report = false;
|
|||
|
||||
export function usePersistence(isCodexOnline: boolean) {
|
||||
const { data, isError, isFetching, refetch } = useQuery({
|
||||
queryKey: ["availabilities"],
|
||||
queryKey: [],
|
||||
queryFn: async () => {
|
||||
return CodexSdk.marketplace()
|
||||
.purchases()
|
||||
.then((data) => Promises.rejectOnError(data, report));
|
||||
},
|
||||
|
||||
refetchInterval: 5000,
|
||||
|
||||
// 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
|
||||
// to the UI after performing an operation in the terminal.
|
||||
refetchOnWindowFocus: true,
|
||||
|
||||
throwOnError: false,
|
||||
});
|
||||
|
||||
return { enabled: !isError && !!data, isFetching, refetch };
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Echo } from "../utils/echo";
|
||||
import { Errors } from "../utils/errors";
|
||||
import { CodexDebugInfo } from "@codex-storage/sdk-js";
|
||||
import { DebugUtils } from "../utils/debug";
|
||||
import { PortForwardingUtil } from "./port-forwarding.util";
|
||||
|
||||
type PortForwardingResponse = { reachable: boolean };
|
||||
|
||||
export function usePortForwarding(info: CodexDebugInfo | undefined) {
|
||||
const { data, isFetching, refetch } = useQuery({
|
||||
queryFn: (): Promise<PortForwardingResponse> => {
|
||||
const port = DebugUtils.getTcpPort(info!);
|
||||
const port = PortForwardingUtil.getTcpPort(info!);
|
||||
if (port.error) {
|
||||
Errors.report(port);
|
||||
return Promise.resolve({ reachable: false });
|
||||
} else {
|
||||
return Echo.portForwarding(port.data).catch((e) => Errors.report(e));
|
||||
return PortForwardingUtil.check(port.data).catch((e) =>
|
||||
Errors.report(e)
|
||||
);
|
||||
}
|
||||
},
|
||||
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
|
||||
// to the tab
|
||||
refetchOnWindowFocus: true,
|
||||
|
||||
throwOnError: false,
|
||||
});
|
||||
|
||||
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 DashboardRequestsImport } from './routes/dashboard/requests'
|
||||
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 DashboardLogsImport } from './routes/dashboard/logs'
|
||||
import { Route as DashboardHelpImport } from './routes/dashboard/help'
|
||||
|
@ -34,7 +35,6 @@ import { Route as DashboardAboutImport } from './routes/dashboard/about'
|
|||
|
||||
// Create Virtual Routes
|
||||
|
||||
const DashboardPeersLazyImport = createFileRoute('/dashboard/peers')()
|
||||
const DashboardAvailabilitiesLazyImport = createFileRoute(
|
||||
'/dashboard/availabilities',
|
||||
)()
|
||||
|
@ -71,14 +71,6 @@ const DashboardIndexRoute = DashboardIndexImport.update({
|
|||
getParentRoute: () => DashboardRoute,
|
||||
} 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 =
|
||||
DashboardAvailabilitiesLazyImport.update({
|
||||
id: '/availabilities',
|
||||
|
@ -112,6 +104,12 @@ const DashboardPurchasesRoute = DashboardPurchasesImport.update({
|
|||
getParentRoute: () => DashboardRoute,
|
||||
} as any)
|
||||
|
||||
const DashboardPeersRoute = DashboardPeersImport.update({
|
||||
id: '/peers',
|
||||
path: '/peers',
|
||||
getParentRoute: () => DashboardRoute,
|
||||
} as any)
|
||||
|
||||
const DashboardNodesRoute = DashboardNodesImport.update({
|
||||
id: '/nodes',
|
||||
path: '/nodes',
|
||||
|
@ -261,6 +259,13 @@ declare module '@tanstack/react-router' {
|
|||
preLoaderRoute: typeof DashboardNodesImport
|
||||
parentRoute: typeof DashboardImport
|
||||
}
|
||||
'/dashboard/peers': {
|
||||
id: '/dashboard/peers'
|
||||
path: '/peers'
|
||||
fullPath: '/dashboard/peers'
|
||||
preLoaderRoute: typeof DashboardPeersImport
|
||||
parentRoute: typeof DashboardImport
|
||||
}
|
||||
'/dashboard/purchases': {
|
||||
id: '/dashboard/purchases'
|
||||
path: '/purchases'
|
||||
|
@ -296,13 +301,6 @@ declare module '@tanstack/react-router' {
|
|||
preLoaderRoute: typeof DashboardAvailabilitiesLazyImport
|
||||
parentRoute: typeof DashboardImport
|
||||
}
|
||||
'/dashboard/peers': {
|
||||
id: '/dashboard/peers'
|
||||
path: '/peers'
|
||||
fullPath: '/dashboard/peers'
|
||||
preLoaderRoute: typeof DashboardPeersLazyImport
|
||||
parentRoute: typeof DashboardImport
|
||||
}
|
||||
'/dashboard/': {
|
||||
id: '/dashboard/'
|
||||
path: '/'
|
||||
|
@ -325,12 +323,12 @@ interface DashboardRouteChildren {
|
|||
DashboardHelpRoute: typeof DashboardHelpRoute
|
||||
DashboardLogsRoute: typeof DashboardLogsRoute
|
||||
DashboardNodesRoute: typeof DashboardNodesRoute
|
||||
DashboardPeersRoute: typeof DashboardPeersRoute
|
||||
DashboardPurchasesRoute: typeof DashboardPurchasesRoute
|
||||
DashboardRequestsRoute: typeof DashboardRequestsRoute
|
||||
DashboardSettingsRoute: typeof DashboardSettingsRoute
|
||||
DashboardWalletRoute: typeof DashboardWalletRoute
|
||||
DashboardAvailabilitiesLazyRoute: typeof DashboardAvailabilitiesLazyRoute
|
||||
DashboardPeersLazyRoute: typeof DashboardPeersLazyRoute
|
||||
DashboardIndexRoute: typeof DashboardIndexRoute
|
||||
}
|
||||
|
||||
|
@ -344,12 +342,12 @@ const DashboardRouteChildren: DashboardRouteChildren = {
|
|||
DashboardHelpRoute: DashboardHelpRoute,
|
||||
DashboardLogsRoute: DashboardLogsRoute,
|
||||
DashboardNodesRoute: DashboardNodesRoute,
|
||||
DashboardPeersRoute: DashboardPeersRoute,
|
||||
DashboardPurchasesRoute: DashboardPurchasesRoute,
|
||||
DashboardRequestsRoute: DashboardRequestsRoute,
|
||||
DashboardSettingsRoute: DashboardSettingsRoute,
|
||||
DashboardWalletRoute: DashboardWalletRoute,
|
||||
DashboardAvailabilitiesLazyRoute: DashboardAvailabilitiesLazyRoute,
|
||||
DashboardPeersLazyRoute: DashboardPeersLazyRoute,
|
||||
DashboardIndexRoute: DashboardIndexRoute,
|
||||
}
|
||||
|
||||
|
@ -371,12 +369,12 @@ export interface FileRoutesByFullPath {
|
|||
'/dashboard/help': typeof DashboardHelpRoute
|
||||
'/dashboard/logs': typeof DashboardLogsRoute
|
||||
'/dashboard/nodes': typeof DashboardNodesRoute
|
||||
'/dashboard/peers': typeof DashboardPeersRoute
|
||||
'/dashboard/purchases': typeof DashboardPurchasesRoute
|
||||
'/dashboard/requests': typeof DashboardRequestsRoute
|
||||
'/dashboard/settings': typeof DashboardSettingsRoute
|
||||
'/dashboard/wallet': typeof DashboardWalletRoute
|
||||
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
|
||||
'/dashboard/peers': typeof DashboardPeersLazyRoute
|
||||
'/dashboard/': typeof DashboardIndexRoute
|
||||
}
|
||||
|
||||
|
@ -393,12 +391,12 @@ export interface FileRoutesByTo {
|
|||
'/dashboard/help': typeof DashboardHelpRoute
|
||||
'/dashboard/logs': typeof DashboardLogsRoute
|
||||
'/dashboard/nodes': typeof DashboardNodesRoute
|
||||
'/dashboard/peers': typeof DashboardPeersRoute
|
||||
'/dashboard/purchases': typeof DashboardPurchasesRoute
|
||||
'/dashboard/requests': typeof DashboardRequestsRoute
|
||||
'/dashboard/settings': typeof DashboardSettingsRoute
|
||||
'/dashboard/wallet': typeof DashboardWalletRoute
|
||||
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
|
||||
'/dashboard/peers': typeof DashboardPeersLazyRoute
|
||||
'/dashboard': typeof DashboardIndexRoute
|
||||
}
|
||||
|
||||
|
@ -417,12 +415,12 @@ export interface FileRoutesById {
|
|||
'/dashboard/help': typeof DashboardHelpRoute
|
||||
'/dashboard/logs': typeof DashboardLogsRoute
|
||||
'/dashboard/nodes': typeof DashboardNodesRoute
|
||||
'/dashboard/peers': typeof DashboardPeersRoute
|
||||
'/dashboard/purchases': typeof DashboardPurchasesRoute
|
||||
'/dashboard/requests': typeof DashboardRequestsRoute
|
||||
'/dashboard/settings': typeof DashboardSettingsRoute
|
||||
'/dashboard/wallet': typeof DashboardWalletRoute
|
||||
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
|
||||
'/dashboard/peers': typeof DashboardPeersLazyRoute
|
||||
'/dashboard/': typeof DashboardIndexRoute
|
||||
}
|
||||
|
||||
|
@ -442,12 +440,12 @@ export interface FileRouteTypes {
|
|||
| '/dashboard/help'
|
||||
| '/dashboard/logs'
|
||||
| '/dashboard/nodes'
|
||||
| '/dashboard/peers'
|
||||
| '/dashboard/purchases'
|
||||
| '/dashboard/requests'
|
||||
| '/dashboard/settings'
|
||||
| '/dashboard/wallet'
|
||||
| '/dashboard/availabilities'
|
||||
| '/dashboard/peers'
|
||||
| '/dashboard/'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to:
|
||||
|
@ -463,12 +461,12 @@ export interface FileRouteTypes {
|
|||
| '/dashboard/help'
|
||||
| '/dashboard/logs'
|
||||
| '/dashboard/nodes'
|
||||
| '/dashboard/peers'
|
||||
| '/dashboard/purchases'
|
||||
| '/dashboard/requests'
|
||||
| '/dashboard/settings'
|
||||
| '/dashboard/wallet'
|
||||
| '/dashboard/availabilities'
|
||||
| '/dashboard/peers'
|
||||
| '/dashboard'
|
||||
id:
|
||||
| '__root__'
|
||||
|
@ -485,12 +483,12 @@ export interface FileRouteTypes {
|
|||
| '/dashboard/help'
|
||||
| '/dashboard/logs'
|
||||
| '/dashboard/nodes'
|
||||
| '/dashboard/peers'
|
||||
| '/dashboard/purchases'
|
||||
| '/dashboard/requests'
|
||||
| '/dashboard/settings'
|
||||
| '/dashboard/wallet'
|
||||
| '/dashboard/availabilities'
|
||||
| '/dashboard/peers'
|
||||
| '/dashboard/'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
|
@ -542,12 +540,12 @@ export const routeTree = rootRoute
|
|||
"/dashboard/help",
|
||||
"/dashboard/logs",
|
||||
"/dashboard/nodes",
|
||||
"/dashboard/peers",
|
||||
"/dashboard/purchases",
|
||||
"/dashboard/requests",
|
||||
"/dashboard/settings",
|
||||
"/dashboard/wallet",
|
||||
"/dashboard/availabilities",
|
||||
"/dashboard/peers",
|
||||
"/dashboard/"
|
||||
]
|
||||
},
|
||||
|
@ -593,6 +591,10 @@ export const routeTree = rootRoute
|
|||
"filePath": "dashboard/nodes.tsx",
|
||||
"parent": "/dashboard"
|
||||
},
|
||||
"/dashboard/peers": {
|
||||
"filePath": "dashboard/peers.tsx",
|
||||
"parent": "/dashboard"
|
||||
},
|
||||
"/dashboard/purchases": {
|
||||
"filePath": "dashboard/purchases.tsx",
|
||||
"parent": "/dashboard"
|
||||
|
@ -613,10 +615,6 @@ export const routeTree = rootRoute
|
|||
"filePath": "dashboard/availabilities.lazy.tsx",
|
||||
"parent": "/dashboard"
|
||||
},
|
||||
"/dashboard/peers": {
|
||||
"filePath": "dashboard/peers.lazy.tsx",
|
||||
"parent": "/dashboard"
|
||||
},
|
||||
"/dashboard/": {
|
||||
"filePath": "dashboard/index.tsx",
|
||||
"parent": "/dashboard"
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
import {
|
||||
createFileRoute,
|
||||
Outlet,
|
||||
useRouterState,
|
||||
} from "@tanstack/react-router";
|
||||
import { createFileRoute, Outlet } from "@tanstack/react-router";
|
||||
import "./layout.css";
|
||||
import { Menu } from "../components/Menu/Menu";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { AppBar } from "../components/AppBar/AppBar";
|
||||
import { Backdrop } from "@codex-storage/marketplace-ui-components";
|
||||
|
||||
const Layout = () => {
|
||||
const [hasMobileMenu, setHasMobileMenu] = useState(false);
|
||||
const router = useRouterState();
|
||||
|
||||
const onIconClick = () => {
|
||||
if (window.innerWidth <= 999) {
|
||||
|
@ -19,10 +14,6 @@ const Layout = () => {
|
|||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setHasMobileMenu(false);
|
||||
}, [router.location.pathname]);
|
||||
|
||||
const onClose = () => setHasMobileMenu(false);
|
||||
|
||||
const isMobileMenuDisplayed =
|
||||
|
|
|
@ -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 = {
|
||||
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: {
|
||||
manualChunks: {
|
||||
"@sentry/react": ["@sentry/react"],
|
||||
"emoji-picker-react": ["emoji-picker-react"]
|
||||
"emoji-picker-react": ["emoji-picker-react"],
|
||||
"dotted-map": ["dotted-map"],
|
||||
}
|
||||
},
|
||||
onwarn(warning, defaultHandler) {
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/// <reference types="vitest" />
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
include: ["src/**/*.test.ts"]
|
||||
},
|
||||
})
|
Loading…
Reference in New Issue