Compare commits

...

79 Commits

Author SHA1 Message Date
Hassan Ben Jobrane
7e37570587 Merge pull request #2133 from nhost/chore/ci-trigger-release-manually
chore(ci): add support for triggering release manually
2023-07-21 19:27:04 +01:00
Hassan Ben Jobrane
87d225a840 chore(ci): trigger release manually 2023-07-21 19:08:34 +01:00
Hassan Ben Jobrane
7b0de27c80 Merge pull request #2132 from nhost/changeset-release/main
chore: update versions
2023-07-21 17:43:45 +01:00
github-actions[bot]
564fc76195 chore: update versions 2023-07-21 16:24:17 +00:00
Hassan Ben Jobrane
2ed4f40c12 Merge pull request #2120 from nhost/feat/services
feat: services
2023-07-21 17:21:44 +01:00
Hassan Ben Jobrane
d67a023e21 feat(services): block services for free apps 2023-07-21 16:51:58 +01:00
Hassan Ben Jobrane
c99d117d1c chore: add changeset 2023-07-21 15:40:08 +01:00
Hassan Ben Jobrane
a497a6ba0a feat(services): mod port url if values are empty 2023-07-21 12:57:18 +01:00
Hassan Ben Jobrane
160cd08cc7 feat(services): fix e2e nav test 2023-07-21 10:35:51 +01:00
Hassan Ben Jobrane
120151c40c feat(services): run pnpm install 2023-07-20 19:14:47 +01:00
Hassan Ben Jobrane
9dc16f29b3 feat(services): add services pagination 2023-07-20 19:10:10 +01:00
Hassan Ben Jobrane
964fc5644a feat(services): put services behind a feature flag 2023-07-20 19:09:48 +01:00
Hassan Ben Jobrane
2f907fc68f feat(services): tweak service form on mobile 2023-07-20 12:41:35 +01:00
Hassan Ben Jobrane
fe6cadc2cd feat(services): tweak services list 2023-07-20 11:40:45 +01:00
Hassan Ben Jobrane
338c8e5a80 feat(services): fix command on initialData 2023-07-19 17:55:47 +01:00
Hassan Ben Jobrane
e6f3a1a39d feat(services): tweak compute section 2023-07-19 16:20:52 +01:00
Hassan Ben Jobrane
a168faeb69 feat(services): tweak service form 2023-07-19 14:00:25 +01:00
Hassan Ben Jobrane
b1628c59b5 feat(services): refactor command field 2023-07-19 13:36:37 +01:00
Hassan Ben Jobrane
32a2f5db9a feat(services): fix replicas form section 2023-07-19 11:57:10 +01:00
Hassan Ben Jobrane
818a48f74d feat(services): refactor edit service 2023-07-19 10:58:56 +01:00
Hassan Ben Jobrane
bed377d05f feat(services): add service details page 2023-07-18 16:19:48 +01:00
Hassan Ben Jobrane
709a616cfa feat(services): fix e2e nav item count 2023-07-17 19:59:24 +01:00
Hassan Ben Jobrane
860e2d877c feat(services): fix linter errors 2023-07-17 19:40:21 +01:00
Hassan Ben Jobrane
5c6b2f88b9 fix: export component properly 2023-07-17 18:43:41 +01:00
Hassan Ben Jobrane
f151a0e872 feat(services): tweaks + show URL for ports/image 2023-07-17 17:01:28 +01:00
Hassan Ben Jobrane
4a84bbb410 feat(service): tweaks + add service details page 2023-07-17 16:10:18 +01:00
Hassan Ben Jobrane
fa3a50e323 feat(service): add GiB to storage capacity field 2023-07-16 23:48:48 +01:00
Hassan Ben Jobrane
398152358c feat(service): tweak compute form section 2023-07-16 23:43:44 +01:00
Hassan Ben Jobrane
34ae9046f3 feat(services): add fetch and delete services 2023-07-16 23:32:24 +01:00
Hassan Ben Jobrane
a478689587 feat(services): fix compute section 2023-07-14 19:54:24 +01:00
Hassan Ben Jobrane
9dbc0607dc feat(services): hook up create service to the api 2023-07-14 19:43:08 +01:00
Hassan Ben Jobrane
7455efdd53 feat(services): tweak create service form 2023-07-14 15:20:14 +01:00
Hassan Ben Jobrane
d0aff6141f feat(services): add info tooltip to each section 2023-07-14 13:59:41 +01:00
Hassan Ben Jobrane
aed0c4f82a feat(services): add create service form 2023-07-14 12:20:34 +01:00
Hassan Ben Jobrane
74d4276c1a feat(services): add new page for services 2023-07-13 11:55:20 +01:00
Hassan Ben Jobrane
1e98130aa1 Merge pull request #2113 from nhost/changeset-release/main
chore: update versions
2023-07-12 20:34:35 +01:00
github-actions[bot]
52e9b510da chore: update versions 2023-07-12 19:22:34 +00:00
Hassan Ben Jobrane
ece197eb6b Merge pull request #2116 from nhost/renovate/prettier-plugin-tailwindcss-0.x
chore(deps): update dependency prettier-plugin-tailwindcss to ^0.4.0
2023-07-12 20:20:50 +01:00
Hassan Ben Jobrane
d14e112bff chore: add changeset 2023-07-12 17:29:12 +01:00
renovate[bot]
83884f04a5 chore(deps): update dependency prettier-plugin-tailwindcss to ^0.4.0 2023-07-12 16:10:24 +00:00
Hassan Ben Jobrane
977de21e86 Merge pull request #2117 from nhost/chore/add-hasura-auth-version
chore: add hasura-auth version 0.20.2
2023-07-12 17:07:27 +01:00
Hassan Ben Jobrane
462a60a8f8 chore: fix hasura-auth version 2023-07-12 16:45:01 +01:00
Hassan Ben Jobrane
9aa4371ef4 chore: add changeset 2023-07-12 16:45:01 +01:00
Hassan Ben Jobrane
f0feddd83f chore: add hasura-auth version 0.20.2 2023-07-12 16:45:01 +01:00
Hassan Ben Jobrane
0748cab125 Merge pull request #2087 from nhost/renovate/vite-plugin-dts-3.x
chore(deps): update dependency vite-plugin-dts to v3
2023-07-12 16:41:59 +01:00
Hassan Ben Jobrane
27885491ee chore: fix test project subdomain 2023-07-12 13:55:14 +01:00
Hassan Ben Jobrane
a36bdbf907 chore: uncomment setting preview URL 2023-07-12 13:53:42 +01:00
Hassan Ben Jobrane
d3e8bb94ae chore: add changeset 2023-07-11 16:39:26 +01:00
Hassan Ben Jobrane
645595ee43 Revert "chore: increase playwright timeout"
This reverts commit 72d1e94cb3.
2023-07-11 16:36:46 +01:00
Hassan Ben Jobrane
4d82bc5609 Revert "chore: playwright: increase number of workers"
This reverts commit b4c10f9f8a.
2023-07-11 16:17:30 +01:00
Hassan Ben Jobrane
fdf1e555d8 chore: ci: comment Fetch Dashboard Preview URL 2023-07-11 16:13:50 +01:00
Hassan Ben Jobrane
90c694cbba chore: ci: Comment step Set Dashboard Preview URL 2023-07-11 15:51:03 +01:00
Hassan Ben Jobrane
3262fa7b37 chore: teardown: run playwright in slowMo 2023-07-11 15:09:25 +01:00
Hassan Ben Jobrane
ab43fe567f chore: fix inserting sql in hasura page 2023-07-11 14:47:21 +01:00
Hassan Ben Jobrane
b4c10f9f8a chore: playwright: increase number of workers 2023-07-11 14:26:22 +01:00
Hassan Ben Jobrane
f4c6e7cfab chore: bring back raw_sql fill 2023-07-11 13:58:41 +01:00
Hassan Ben Jobrane
72d1e94cb3 chore: increase playwright timeout 2023-07-11 13:36:44 +01:00
Hassan Ben Jobrane
82d221a48d Revert "chore: increase CI e2e timeout"
This reverts commit 3fe46771b9.
2023-07-11 11:57:36 +01:00
Hassan Ben Jobrane
3fe46771b9 chore: increase CI e2e timeout 2023-07-11 11:06:09 +01:00
Hassan Ben Jobrane
a1c487aa21 chore: fix lock file 2023-07-11 01:55:39 +01:00
Hassan Ben Jobrane
cf455608e2 chore: fix e2e tests 2023-07-11 01:40:17 +01:00
Hassan Ben Jobrane
5dac12dd41 chore: use node v18 2023-07-10 18:15:22 +01:00
Hassan Ben Jobrane
2389b46e0d chore: update node to v18 2023-07-10 16:40:41 +01:00
renovate[bot]
6fe2d22d0e chore(deps): update dependency vite-plugin-dts to v3 2023-07-10 15:23:12 +00:00
Hassan Ben Jobrane
0b439149e4 Merge pull request #2106 from nhost/renovate/pluralize-0.x
chore(deps): update dependency @types/pluralize to ^0.0.30
2023-07-10 16:19:28 +01:00
Hassan Ben Jobrane
a9d7da8af7 chore: add changeset 2023-07-10 16:09:14 +01:00
renovate[bot]
3ecc21a45e chore(deps): update dependency @types/pluralize to ^0.0.30 2023-07-10 14:30:41 +00:00
Hassan Ben Jobrane
aa19e85cdc Merge pull request #2088 from nhost/renovate/turbo-monorepo
chore(deps): update dependency turbo to v1.10.7
2023-07-10 15:28:49 +01:00
Hassan Ben Jobrane
26c650227d Merge pull request #2111 from nhost/fix/tweak-config-warning
fix: tweak warning in dark mode
2023-07-10 15:16:25 +01:00
Hassan Ben Jobrane
face99ccde chore: add changeset 2023-07-10 15:07:25 +01:00
Hassan Ben Jobrane
49bcc525ad chore: bump turbo version in Dockerfile 2023-07-10 15:07:25 +01:00
renovate[bot]
533563c893 chore(deps): update dependency turbo to v1.10.7 2023-07-10 15:07:25 +01:00
Hassan Ben Jobrane
cfe527307e chore: add changeset 2023-07-10 15:05:22 +01:00
Hassan Ben Jobrane
1e36c6706d Revert "chore: use node 18 for GH actions"
This reverts commit 6e40b114fc.
2023-07-10 15:01:46 +01:00
Hassan Ben Jobrane
6e40b114fc chore: use node 18 for GH actions 2023-07-10 13:58:11 +01:00
Hassan Ben Jobrane
77acf1385d Revert "chore: increase ci timeout"
This reverts commit cec7edd2d5.
2023-07-10 12:23:22 +01:00
Hassan Ben Jobrane
cec7edd2d5 chore: increase ci timeout 2023-07-10 10:50:55 +01:00
Hassan Ben Jobrane
9dbbdb3121 fix: show only when a repo is connected 2023-07-07 19:24:09 +01:00
Hassan Ben Jobrane
79d2602648 fix: tweak warning in dark mode 2023-07-07 18:52:24 +01:00
52 changed files with 3156 additions and 184 deletions

View File

@@ -26,10 +26,10 @@ runs:
path: ${{ steps.pnpm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: ${{ runner.os }}-node-
- name: Use Node.js v16
- name: Use Node.js v18
uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18
- shell: bash
name: Install packages
run: pnpm install --frozen-lockfile

View File

@@ -10,6 +10,7 @@ on:
- '**.md'
- '!.changeset/**'
- 'LICENSE'
workflow_dispatch:
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}

View File

@@ -7,7 +7,8 @@ import baseLibConfig from './vite.lib.config'
export default defineConfig({
...baseLibConfig,
optimizeDeps: {
include: ['react/jsx-runtime']
include: ['react/jsx-runtime'],
exclude: ['react-hook-form']
},
plugins: [react({ jsxRuntime: 'classic' }), ...baseLibConfig.plugins]
})

View File

@@ -1,5 +1,22 @@
# @nhost/dashboard
## 0.20.0
### Minor Changes
- c99d117d1: feat(services): add support for custom services
## 0.19.2
### Patch Changes
- face99ccd: chore(deps): bump turbo version
- cfe527307: style: tweak pull config warning in dark mode
- a9d7da8af: chore(deps): update dependency @types/pluralize to ^0.0.30
- 9aa4371ef: chore: add hasura-auth version 0.21.2
- d14e112bf: chore(deps): update dependency prettier-plugin-tailwindcss to ^0.4.0
- d3e8bb94a: chore(deps): update dependency vite-plugin-dts to v3
## 0.19.1
### Patch Changes

View File

@@ -3,7 +3,7 @@ RUN apk add --no-cache libc6-compat
RUN apk update
WORKDIR /app
RUN yarn global add turbo@1.10.6
RUN yarn global add turbo@1.10.7
COPY . .
RUN turbo prune --scope="@nhost/dashboard" --docker

View File

@@ -9,7 +9,7 @@ import { openProject } from '@/e2e/utils';
import { chromium } from '@playwright/test';
async function globalTeardown() {
const browser = await chromium.launch();
const browser = await chromium.launch({ slowMo: 1000 });
const context = await browser.newContext({
baseURL: TEST_DASHBOARD_URL,
@@ -46,18 +46,23 @@ async function globalTeardown() {
await hasuraPage.locator('a', { hasText: /data/i }).click();
await hasuraPage.getByRole('link', { name: /sql/i }).click();
await hasuraPage.locator('#raw_sql > textarea').fill(`
DO $$ DECLARE
tablename text;
BEGIN
FOR tablename IN
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public'
LOOP
EXECUTE 'DROP TABLE IF EXISTS public.' || quote_ident(tablename) || ' CASCADE';
END LOOP;
END $$;
`);
// Set the value of the Ace code editor using JavaScript evaluation in the browser context
await hasuraPage.evaluate(() => {
const editor = ace.edit('raw_sql');
editor.setValue(`
DO $$ DECLARE
tablename text;
BEGIN
FOR tablename IN
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'public'
LOOP
EXECUTE 'DROP TABLE IF EXISTS public.' || quote_ident(tablename) || ' CASCADE';
END LOOP;
END $$;
`);
});
await hasuraPage.getByRole('button', { name: /run!/i }).click();
await hasuraPage.getByText(/sql executed!/i).waitFor();

View File

@@ -0,0 +1,5 @@
query InitQuery {
root {
enableServices
}
}

5
dashboard/hypertune.json Normal file
View File

@@ -0,0 +1,5 @@
{
"projectId": 2596,
"token": "U2FsdGVkX19+V8BJnVR0xLEC+42OW5qZl/A0i6beAaRmJoIhFh5Yf6eIKBzLbV9h",
"outputDirectoryPath": "src/hypertune"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "0.19.1",
"version": "0.20.0",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -54,6 +54,7 @@
"graphql-request": "^6.0.0",
"graphql-tag": "^2.12.6",
"graphql-ws": "^5.11.2",
"hypertune": "^1.4.4",
"just-kebab-case": "^4.1.1",
"lodash.debounce": "^4.0.8",
"next": "^12.3.1",
@@ -71,6 +72,7 @@
"react-syntax-highlighter": "^15.4.5",
"react-table": "^7.8.0",
"sharp": "^0.32.0",
"shell-quote": "^1.8.1",
"slugify": "^1.6.5",
"stripe": "^10.17.0",
"tailwind-merge": "^1.8.0",
@@ -101,13 +103,15 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/ace": "^0.0.48",
"@types/bcryptjs": "^2.4.2",
"@types/lodash.debounce": "^4.0.7",
"@types/node": "^16.11.7",
"@types/pluralize": "^0.0.29",
"@types/pluralize": "^0.0.30",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@types/react-table": "^7.7.12",
"@types/shell-quote": "^1.7.1",
"@types/testing-library__jest-dom": "^5.14.5",
"@types/validator": "^13.7.10",
"@typescript-eslint/eslint-plugin": "^5.43.0",
@@ -137,7 +141,7 @@
"postcss": "^8.4.19",
"prettier": "^2.7.1",
"prettier-plugin-organize-imports": "^3.2.0",
"prettier-plugin-tailwindcss": "^0.3.0",
"prettier-plugin-tailwindcss": "^0.4.0",
"react-date-fns-hooks": "^0.9.4",
"require-from-string": "^2.0.2",
"snake-case": "^3.0.4",

View File

@@ -15,6 +15,7 @@ export type PaginationProps = DetailedHTMLProps<
* Total number of pages.
*/
totalNrOfPages: number;
/**
* Number of total elements per page.
*/
@@ -23,6 +24,10 @@ export type PaginationProps = DetailedHTMLProps<
* Total number of elements.
*/
totalNrOfElements: number;
/**
* Label of the elements displayed ex: pages, users...
*/
itemsLabel: string;
/**
* Current page number.
*/
@@ -64,6 +69,7 @@ export default function Pagination({
elementsPerPage,
onPageChange,
totalNrOfElements,
itemsLabel,
...props
}: PaginationProps) {
return (
@@ -132,7 +138,7 @@ export default function Pagination({
{totalNrOfElements < currentPageNumber * elementsPerPage
? totalNrOfElements
: currentPageNumber * elementsPerPage}{' '}
of {totalNrOfElements} users
of {totalNrOfElements} {itemsLabel}
</Text>
</div>
</div>

View File

@@ -114,7 +114,7 @@ export default function SettingsContainer({
<Box
{...root}
className={twMerge(
'grid grid-flow-row gap-4 rounded-lg border-1 py-4',
'grid grid-flow-row gap-4 overflow-hidden rounded-lg border-1 py-4',
root?.className || rootClassName,
)}
>

View File

@@ -7,6 +7,7 @@ import { Alert } from '@/components/ui/v2/Alert';
import { Box } from '@/components/ui/v2/Box';
import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useTheme } from '@mui/material';
import { twMerge } from 'tailwind-merge';
export interface SettingsLayoutProps extends ProjectLayoutProps {
@@ -25,6 +26,7 @@ export default function SettingsLayout({
sidebarProps: { className: sidebarClassName, ...sidebarProps } = {},
...props
}: SettingsLayoutProps) {
const theme = useTheme();
const { currentProject } = useCurrentWorkspaceAndProject();
const hasGitRepo = !!currentProject?.githubRepository;
@@ -43,18 +45,25 @@ export default function SettingsLayout({
<Box
sx={{ backgroundColor: 'background.default' }}
className="flex w-full flex-auto flex-col overflow-scroll overflow-x-hidden"
className="flex flex-col flex-auto w-full overflow-scroll overflow-x-hidden"
>
<RetryableErrorBoundary>
{hasGitRepo && (
<Alert
severity="warning"
className="grid grid-flow-row place-content-center gap-2"
className="grid grid-flow-row gap-2 place-content-center"
>
<Text color="warning" className="text-sm ">
As you have a connected repository, make sure to synchronize
your changes with{' '}
<code className="rounded-md bg-slate-200 px-2 py-px text-slate-500">
<code
className={twMerge(
'rounded-md px-2 py-px',
theme.palette.mode === 'dark'
? 'bg-brown text-copper'
: 'bg-slate-200 text-slate-700',
)}
>
nhost config pull
</code>{' '}
or they may be reverted with the next push.

View File

@@ -0,0 +1,40 @@
import type { IconProps } from '@/components/ui/v2/icons';
function CubeIcon(props: IconProps) {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M14 11.0826V4.91742C14 4.8287 13.9764 4.74158 13.9316 4.665C13.8868 4.58841 13.8225 4.52513 13.7451 4.48163L8.24513 1.38788C8.17029 1.34578 8.08587 1.32367 8 1.32367C7.91413 1.32367 7.82971 1.34578 7.75487 1.38788L2.25487 4.48163C2.17754 4.52513 2.11318 4.58841 2.0684 4.665C2.02361 4.74158 2 4.8287 2 4.91742V11.0826C2 11.1713 2.02361 11.2584 2.0684 11.335C2.11318 11.4116 2.17754 11.4749 2.25487 11.5184L7.75487 14.6121C7.82971 14.6542 7.91413 14.6763 8 14.6763C8.08587 14.6763 8.17029 14.6542 8.24513 14.6121L13.7451 11.5184C13.8225 11.4749 13.8868 11.4116 13.9316 11.335C13.9764 11.2584 14 11.1713 14 11.0826Z"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M13.9311 4.66414L8.0594 8.00001L2.06934 4.66357"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M8.05916 8L8.00049 14.6763"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
CubeIcon.displayName = 'NhostCubeIcon';
export default CubeIcon;

View File

@@ -0,0 +1 @@
export { default as CubeIcon } from './CubeIcon';

View File

@@ -0,0 +1,27 @@
import type { IconProps } from '@/components/ui/v2/icons';
import { SvgIcon } from '@/components/ui/v2/icons/SvgIcon';
function ServicesIcon(props: IconProps) {
return (
<SvgIcon
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-label="Services"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.89295 4.15125H9.21701C9.28097 4.15125 9.33291 4.09959 9.33326 4.03565V2.8556C9.33291 2.79163 9.28097 2.73999 9.21701 2.73999H7.89295C7.82909 2.73999 7.77734 2.79174 7.77734 2.8556V4.03562C7.77734 4.09948 7.82911 4.15125 7.89295 4.15125ZM5.53406 5.84862H4.21001C4.14594 5.84826 4.09411 5.79643 4.09375 5.73236V4.55298C4.09411 4.48902 4.14606 4.43738 4.21001 4.43738H5.53406C5.5979 4.43738 5.64967 4.48912 5.64967 4.55298V5.73236C5.64967 5.79631 5.59801 5.84826 5.53406 5.84862ZM14.6307 6.48419C15.4316 6.48419 15.8114 6.77094 15.8521 6.80325L16 6.92016L15.9386 7.09971C15.8408 7.34738 15.69 7.57067 15.4968 7.75398C15.2062 8.04139 14.6791 8.38436 13.8221 8.38436H13.6839C13.337 9.26145 12.8707 10.2484 12.0879 11.1345C11.6196 11.6644 11.0689 12.1152 10.457 12.4696C9.71438 12.8901 8.90665 13.1835 8.06725 13.3376C7.4634 13.45 6.85036 13.5056 6.23616 13.5036C4.87658 13.5036 3.67717 13.2453 2.93893 12.7932C2.28012 12.3908 1.77374 11.7333 1.43337 10.8407C1.13576 10.0277 0.989105 9.1673 1.00063 8.30169C1.00204 8.04363 1.21146 7.83507 1.46954 7.83472H11.3503C11.471 7.8302 12.0678 7.77917 12.4399 7.57185C12.1318 7.08484 12.0446 6.51519 12.188 5.9087C12.2639 5.59123 12.3932 5.28898 12.5703 5.01479L12.7118 4.81068L12.9268 4.93471L12.9269 4.93473C12.9668 4.9583 13.8447 5.47632 13.9996 6.53843C14.2082 6.50325 14.4192 6.48511 14.6307 6.48419ZM3.7092 7.54529H2.38514C2.32128 7.54529 2.26953 7.49353 2.26953 7.42967V6.25029V6.24964C2.26953 6.1858 2.32128 6.13403 2.38514 6.13403H3.7092H3.70985C3.77369 6.13439 3.82516 6.18643 3.8248 6.25029V7.42969C3.8248 7.49353 3.77306 7.54529 3.7092 7.54529ZM4.21003 7.54529H5.53409C5.59794 7.54529 5.64969 7.49353 5.64969 7.42969V6.25029C5.65005 6.18643 5.59858 6.13439 5.53472 6.13403H5.53407H4.21001C4.14579 6.13403 4.09375 6.18607 4.09375 6.25029V7.42967C4.09413 7.49363 4.14606 7.54529 4.21003 7.54529ZM7.38597 7.54529H6.06191C5.99808 7.54529 5.94631 7.49353 5.94629 7.42967V6.25029V6.24964C5.94629 6.1858 5.99803 6.13403 6.06189 6.13403H7.38595H7.3866C7.45046 6.13439 7.50193 6.18643 7.50157 6.25029V7.42969C7.50157 7.49353 7.44983 7.54529 7.38597 7.54529ZM7.89295 7.54529H9.21701C9.28097 7.54529 9.33291 7.49365 9.33326 7.42969V6.25029C9.33326 6.18607 9.28122 6.13403 9.21701 6.13403H7.89295C7.82909 6.13403 7.77734 6.1858 7.77734 6.24964V6.25029V7.42967C7.77734 7.49353 7.82911 7.54529 7.89295 7.54529ZM6.06189 5.84862H7.38595C7.4499 5.84826 7.50156 5.79631 7.50156 5.73236V4.55298C7.50156 4.48912 7.44979 4.43738 7.38595 4.43738H6.06189C5.99804 4.43738 5.94629 4.48915 5.94629 4.55298V5.73236C5.94629 5.79631 5.99795 5.84826 6.06189 5.84862ZM9.21701 5.84862H7.89295C7.82901 5.84826 7.77734 5.79631 7.77734 5.73236V4.55298C7.77734 4.48915 7.82909 4.43738 7.89295 4.43738H9.21701C9.28097 4.43738 9.33291 4.48902 9.33326 4.55298V5.73236C9.33291 5.79643 9.28108 5.84826 9.21701 5.84862ZM11.0637 7.54529H9.73963C9.67579 7.54529 9.62402 7.49353 9.62402 7.42967V6.25029V6.24964C9.62402 6.1858 9.67579 6.13403 9.73963 6.13403H11.0637C11.1279 6.13403 11.1799 6.18607 11.1799 6.25029V7.42969C11.1796 7.49365 11.1277 7.54529 11.0637 7.54529Z"
fill="currentColor"
/>
</SvgIcon>
);
}
ServicesIcon.displayName = 'NhostServicesIcon';
export default ServicesIcon;

View File

@@ -0,0 +1 @@
export { default as ServicesIcon } from './ServicesIcon';

View File

@@ -29,6 +29,7 @@ export type AuthServiceVersionFormValues = Yup.InferType<
>;
const AVAILABLE_AUTH_VERSIONS = [
'0.21.2',
'0.20.1',
'0.20.0',
'0.19.3',

View File

@@ -8,11 +8,13 @@ import { GraphQLIcon } from '@/components/ui/v2/icons/GraphQLIcon';
import { HasuraIcon } from '@/components/ui/v2/icons/HasuraIcon';
import { HomeIcon } from '@/components/ui/v2/icons/HomeIcon';
import { RocketIcon } from '@/components/ui/v2/icons/RocketIcon';
import { ServicesIcon } from '@/components/ui/v2/icons/ServicesIcon';
import { StorageIcon } from '@/components/ui/v2/icons/StorageIcon';
import type { SvgIconProps } from '@/components/ui/v2/icons/SvgIcon';
import { UserIcon } from '@/components/ui/v2/icons/UserIcon';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useHypertune } from '@/hooks/useHypertune';
import type { ReactElement } from 'react';
export interface ProjectRoute {
@@ -56,8 +58,26 @@ export interface ProjectRoute {
export default function useProjectRoutes() {
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const { currentProject, loading: currentProjectLoading } =
useCurrentWorkspaceAndProject();
const {
currentWorkspace,
currentProject,
loading: currentProjectLoading,
} = useCurrentWorkspaceAndProject();
const hypertune = useHypertune();
const enableServices =
currentWorkspace &&
hypertune
.root({
context: {
workSpace: {
id: currentWorkspace.id,
},
},
})
.enableServices({})
.get(false);
const nhostRoutes: ProjectRoute[] = [
{
@@ -98,7 +118,7 @@ export default function useProjectRoutes() {
},
];
const allRoutes: ProjectRoute[] = [
let allRoutes: ProjectRoute[] = [
{
relativePath: '/',
exact: true,
@@ -136,9 +156,19 @@ export default function useProjectRoutes() {
label: 'Storage',
icon: <StorageIcon />,
},
...nhostRoutes,
];
if (enableServices) {
allRoutes.push({
relativePath: '/services',
exact: false,
label: 'Run',
icon: <ServicesIcon />,
});
}
allRoutes = [...allRoutes, ...nhostRoutes];
return {
nhostRoutes,
allRoutes,

View File

@@ -48,6 +48,21 @@ export const MIN_SERVICE_VCPU = 0.25 * RESOURCE_VCPU_MULTIPLIER;
*/
export const MAX_SERVICE_VCPU = 7 * RESOURCE_VCPU_MULTIPLIER;
/**
* Best resource utilization ration for CPU-Memory.
*/
export const MEM_CPU_RATIO = 2.048;
/**
* Minimum storage capacity (Gib)
*/
export const MIN_STORAGE_CAPACITY = 1;
/**
* Maximum storage capacity (Gib)
*/
export const MAX_STORAGE_CAPACITY = 1000;
/**
* The minimum amount of memory that has to be allocated per service.
*/
@@ -135,3 +150,8 @@ export const resourceSettingsValidationSchema = Yup.object({
export type ResourceSettingsFormValues = Yup.InferType<
typeof resourceSettingsValidationSchema
>;
export const MIN_SERVICES_CPU = Math.floor(128 / MEM_CPU_RATIO);
export const MIN_SERVICES_MEM = 128;
export const MAX_SERVICES_CPU = 7000;
export const MAX_SERVICES_MEM = Math.floor(MAX_SERVICES_CPU * MEM_CPU_RATIO);

View File

@@ -0,0 +1,376 @@
import { useDialog } from '@/components/common/DialogProvider';
import { Form } from '@/components/form/Form';
import { Alert } from '@/components/ui/v2/Alert';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { InfoCard } from '@/features/projects/overview/components/InfoCard';
import {
MAX_SERVICE_REPLICAS,
MAX_SERVICES_CPU,
MAX_SERVICES_MEM,
MIN_SERVICES_CPU,
MIN_SERVICES_MEM,
} from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection';
import { EnvironmentFormSection } from '@/features/services/components/ServiceForm/components/EnvironmentFormSection';
import { PortsFormSection } from '@/features/services/components/ServiceForm/components/PortsFormSection';
import { ReplicasFormSection } from '@/features/services/components/ServiceForm/components/ReplicasFormSection';
import { StorageFormSection } from '@/features/services/components/ServiceForm/components/StorageFormSection';
import type { DialogFormProps } from '@/types/common';
import {
useInsertRunServiceConfigMutation,
useInsertRunServiceMutation,
useReplaceRunServiceConfigMutation,
type ConfigRunServiceConfigInsertInput,
} from '@/utils/__generated__/graphql';
import { getToastStyleProps } from '@/utils/constants/settings';
import type { ApolloError } from '@apollo/client';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import { parse } from 'shell-quote';
import * as Yup from 'yup';
export enum PortTypes {
HTTP = 'http',
TCP = 'tcp',
UDP = 'udp',
}
export const validationSchema = Yup.object({
name: Yup.string().required('The name is required.'),
image: Yup.string().label('Image to run'),
command: Yup.string().required(),
environment: Yup.array().of(
Yup.object().shape({
name: Yup.string().required(),
value: Yup.string().required(),
}),
),
compute: Yup.object({
cpu: Yup.number().min(MIN_SERVICES_CPU).max(MAX_SERVICES_CPU).required(),
memory: Yup.number().min(MIN_SERVICES_MEM).max(MAX_SERVICES_MEM).required(),
}),
replicas: Yup.number().min(1).max(MAX_SERVICE_REPLICAS).required(),
ports: Yup.array().of(
Yup.object().shape({
port: Yup.number().required(),
type: Yup.mixed<PortTypes>().oneOf(Object.values(PortTypes)).required(),
publish: Yup.boolean().default(false),
}),
),
storage: Yup.array().of(
Yup.object()
.shape({
name: Yup.string().required(),
path: Yup.string().required(),
capacity: Yup.number().nonNullable().required(),
})
.required(),
),
});
export type ServiceFormValues = Yup.InferType<typeof validationSchema>;
export interface ServiceFormProps extends DialogFormProps {
/**
* To use in conjunction with initialData to allow for updating the service
*/
serviceID?: string;
/**
* if there is initialData then it's an update operation
*/
initialData?: ServiceFormValues;
/**
* Function to be called when the operation is cancelled.
*/
onCancel?: VoidFunction;
/**
* Function to be called when the submit is successful.
*/
onSubmit?: VoidFunction | ((args?: any) => Promise<any>);
}
export default function ServiceForm({
serviceID,
initialData,
onSubmit,
onCancel,
location,
}: ServiceFormProps) {
const { onDirtyStateChange } = useDialog();
const [insertRunService] = useInsertRunServiceMutation();
const { currentProject } = useCurrentWorkspaceAndProject();
const [insertRunServiceConfig] = useInsertRunServiceConfigMutation();
const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation();
const [createServiceFormError, setCreateServiceFormError] =
useState<Error | null>(null);
const form = useForm<ServiceFormValues>({
defaultValues: initialData ?? {
compute: {
cpu: 62,
memory: 128,
},
replicas: 1,
},
reValidateMode: 'onSubmit',
resolver: yupResolver(validationSchema),
});
const {
watch,
register,
formState: { errors, isSubmitting, dirtyFields },
} = form;
const serviceImage = watch('image');
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, location);
}, [isDirty, location, onDirtyStateChange]);
const createOrUpdateService = async (values: ServiceFormValues) => {
const config: ConfigRunServiceConfigInsertInput = {
name: values.name,
image: {
image: values.image,
},
command: parse(values.command).map((item) => item.toString()),
resources: {
compute: {
cpu: values.compute.cpu,
memory: values.compute.memory,
},
storage: values.storage.map((item) => ({
name: item.name,
path: item.path,
capacity: item.capacity,
})),
replicas: values.replicas,
},
environment: values.environment.map((item) => ({
name: item.name,
value: item.value,
})),
ports: values.ports.map((item) => ({
port: item.port,
type: item.type,
publish: item.publish,
})),
};
if (initialData) {
// Update service config
await replaceRunServiceConfig({
variables: {
appID: currentProject.id,
serviceID,
config,
},
});
} else {
// Insert service config
const {
data: {
insertRunService: { id: newServiceID },
},
} = await insertRunService({
variables: {
object: {
appID: currentProject.id,
},
},
});
await insertRunServiceConfig({
variables: {
appID: currentProject.id,
serviceID: newServiceID,
config: {
...config,
image: {
// If the image field left empty then we auto-populate following this format
// registry.<region>.<nhost_domain>/<service_id>
image:
values.image.length > 0
? values.image
: `registry.${currentProject.region.awsName}.${currentProject.region.domain}/${newServiceID}`,
},
},
},
});
}
};
const handleSubmit = async (values: ServiceFormValues) => {
try {
await toast.promise(
createOrUpdateService(values),
{
loading: 'Configuring the service...',
success: `The service has been configured successfully.`,
error: (arg: ApolloError) => {
// we need to get the internal error message from the GraphQL error
const { internal } = arg.graphQLErrors[0]?.extensions || {};
const { message } = (internal as Record<string, any>)?.error || {};
// we use the default Apollo error message if we can't find the
// internal error message
return (
message ||
arg.message ||
'An error occurred while configuring the service. Please try again.'
);
},
},
getToastStyleProps(),
);
// await refetchWorkspaceAndProject();
// refestch the services
onSubmit?.();
} catch {
// Note: The toast will handle the error.
}
};
return (
<FormProvider {...form}>
<Form
onSubmit={handleSubmit}
className="grid grid-flow-row gap-4 px-6 pb-6"
>
<Input
{...register('name')}
id="name"
label={
<Box className="flex flex-row items-center space-x-2">
<Text>Name</Text>
<Tooltip title="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
</Box>
}
placeholder="Service name"
hideEmptyHelperText
error={!!errors.name}
helperText={errors?.name?.message}
fullWidth
autoComplete="off"
autoFocus
/>
<Input
{...register('image')}
id="image"
label={
<Box className="flex flex-row items-center space-x-2">
<Text>Image</Text>
<Tooltip title="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
</Box>
}
placeholder="To automatically fill the private registry, leave it blank."
hideEmptyHelperText
error={!!errors.image}
helperText={errors?.image?.message}
fullWidth
autoComplete="off"
/>
{/* This shows only when trying to edit a service */}
{serviceID && serviceImage && (
<InfoCard
title="Private registry"
value={`registry.${currentProject.region.awsName}.${currentProject.region.domain}/${serviceID}`}
/>
)}
<Input
{...register('command')}
id="command"
label={
<Box className="flex flex-row items-center space-x-2">
<Text>Command</Text>
<Tooltip title="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
</Box>
}
placeholder="$ npm start"
hideEmptyHelperText
error={!!errors.command}
helperText={errors?.command?.message}
fullWidth
autoComplete="off"
/>
<ComputeFormSection />
<ReplicasFormSection />
<EnvironmentFormSection />
<PortsFormSection />
<StorageFormSection />
{createServiceFormError && (
<Alert
severity="error"
className="grid items-center justify-between grid-flow-col px-4 py-3"
>
<span className="text-left">
<strong>Error:</strong> {createServiceFormError.message}
</span>
<Button
variant="borderless"
color="error"
size="small"
onClick={() => {
setCreateServiceFormError(null);
}}
>
Clear
</Button>
</Alert>
)}
<div className="grid grid-flow-row gap-2">
<Button type="submit" disabled={isSubmitting}>
{initialData ? 'Update' : 'Create'}
</Button>
<Button variant="outlined" color="secondary" onClick={onCancel}>
Cancel
</Button>
</div>
</Form>
</FormProvider>
);
}

View File

@@ -0,0 +1,87 @@
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { ArrowLeftIcon } from '@/components/ui/v2/icons/ArrowLeftIcon';
import { ArrowRightIcon } from '@/components/ui/v2/icons/ArrowRightIcon';
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { Slider } from '@/components/ui/v2/Slider';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import {
MAX_SERVICES_MEM,
MEM_CPU_RATIO,
MIN_SERVICES_MEM,
} from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import type { ServiceFormValues } from '@/features/services/components/ServiceForm';
import { useFormContext, useWatch } from 'react-hook-form';
export default function ComputeFormSection() {
const { setValue } = useFormContext<ServiceFormValues>();
const formValues = useWatch<ServiceFormValues>();
const handleSliderUpdate = (value: string) => {
const updatedMem = parseFloat(value);
if (Number.isNaN(updatedMem) || updatedMem < MIN_SERVICES_MEM) {
return;
}
setValue('compute.memory', Math.floor(updatedMem), { shouldDirty: true });
setValue('compute.cpu', Math.floor(updatedMem / MEM_CPU_RATIO), {
shouldDirty: true,
});
};
const incrementCompute = () => {
const newMemoryValue = formValues.compute.memory + 128;
setValue('compute.memory', newMemoryValue);
setValue('compute.cpu', Math.floor(newMemoryValue / MEM_CPU_RATIO));
};
const decrementCompute = () => {
const newMemoryValue = formValues.compute.memory - 128;
setValue('compute.memory', newMemoryValue);
setValue('compute.cpu', Math.floor(newMemoryValue / MEM_CPU_RATIO));
};
return (
<Box className="p-4 space-y-4 rounded border-1">
<Box className="flex flex-row items-center space-x-2">
<Text variant="h4" className="font-semibold">
CPU: {formValues.compute.cpu} / Memory: {formValues.compute.memory}
</Text>
<Tooltip title="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s">
<InfoIcon aria-label="Info" className="w-4 h-4" color="primary" />
</Tooltip>
</Box>
<Box className="flex flex-row items-center justify-between space-x-4">
<Button
disabled={formValues.compute.memory <= MIN_SERVICES_MEM}
variant="outlined"
onClick={decrementCompute}
>
<ArrowLeftIcon className="w-4 h-4" />
</Button>
<Slider
value={Number(formValues.compute.memory)}
onChange={(_event, value) => handleSliderUpdate(value.toString())}
max={MAX_SERVICES_MEM}
min={MIN_SERVICES_MEM}
step={256}
aria-label="Compute resources"
marks
/>
<Button
disabled={formValues.compute.memory >= MAX_SERVICES_MEM}
variant="outlined"
onClick={incrementCompute}
>
<ArrowRightIcon className="w-4 h-4" />
</Button>
</Box>
</Box>
);
}

View File

@@ -0,0 +1 @@
export { default as ComputeFormSection } from './ComputeFormSection';

View File

@@ -0,0 +1,85 @@
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { TrashIcon } from '@/components/ui/v2/icons/TrashIcon';
import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import type { ServiceFormValues } from '@/features/services/components/ServiceForm';
import { useFieldArray, useFormContext } from 'react-hook-form';
export default function EnvironmentFormSection() {
const {
register,
formState: { errors },
} = useFormContext<ServiceFormValues>();
const { fields, append, remove } = useFieldArray({
name: 'environment',
});
return (
<Box className="p-4 space-y-4 rounded border-1">
<Box className="flex flex-row items-center justify-between ">
<Box className="flex flex-row items-center space-x-2">
<Text variant="h4" className="font-semibold">
Environment
</Text>
<Tooltip title="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s">
<InfoIcon aria-label="Info" className="w-4 h-4" color="primary" />
</Tooltip>
</Box>
<Button
variant="borderless"
onClick={() => append({ name: '', value: '' })}
>
<PlusIcon className="w-5 h-5" />
</Button>
</Box>
<Box className="flex flex-col space-y-4">
{fields.map((field, index) => (
<Box
key={field.id}
className="flex w-full flex-col space-y-2 xs+:flex-row xs+:space-y-0 xs+:space-x-2"
>
<Input
{...register(`environment.${index}.name`)}
id={`${field.id}-name`}
label={!index && 'Name'}
placeholder={`Key ${index}`}
className="w-full"
hideEmptyHelperText
error={!!errors?.environment?.at(index)}
helperText={errors?.environment?.at(index)?.message}
fullWidth
autoComplete="off"
/>
<Input
{...register(`environment.${index}.value`)}
id={`${field.id}-value`}
label={!index && 'Value'}
placeholder={`Value ${index}`}
className="w-full"
hideEmptyHelperText
error={!!errors?.environment?.at(index)}
helperText={errors?.environment?.at(index)?.message}
fullWidth
autoComplete="off"
/>
<Button
variant="borderless"
className=""
color="error"
onClick={() => remove(index)}
>
<TrashIcon className="w-4 h-4" />
</Button>
</Box>
))}
</Box>
</Box>
);
}

View File

@@ -0,0 +1,2 @@
// eslint-disable-next-line import/no-cycle
export { default as EnvironmentFormSection } from './EnvironmentFormSection';

View File

@@ -0,0 +1,141 @@
import { ControlledSwitch } from '@/components/form/ControlledSwitch';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { TrashIcon } from '@/components/ui/v2/icons/TrashIcon';
import { Input } from '@/components/ui/v2/Input';
import { Option } from '@/components/ui/v2/Option';
import { Select } from '@/components/ui/v2/Select';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { InfoCard } from '@/features/projects/overview/components/InfoCard';
import {
PortTypes,
type ServiceFormValues,
} from '@/features/services/components/ServiceForm';
import { useFieldArray, useFormContext, useWatch } from 'react-hook-form';
export default function PortsFormSection() {
const form = useFormContext<ServiceFormValues>();
const { currentProject } = useCurrentWorkspaceAndProject();
const {
register,
setValue,
formState: { errors },
} = form;
const { fields, append, remove } = useFieldArray({
name: 'ports',
});
const formValues = useWatch<ServiceFormValues>();
const onChangePortType = (value: string | undefined, index: number) =>
setValue(`ports.${index}.type`, value as PortTypes);
const showURL = (index: number) =>
formValues.ports[index]?.type === PortTypes.HTTP &&
formValues.ports[index]?.publish;
const getPortURL = (_port: string | number, _name: string) => {
const port = Number(_port) > 0 ? Number(_port) : '[port]';
const name = _name && _name.length > 0 ? _name : '[name]';
return `https://${currentProject.subdomain}-${name}-${port}.svc.${currentProject.region.awsName}.${currentProject.region.domain}`;
};
return (
<Box className="space-y-4 rounded border-1 p-4">
<Box className="flex flex-row items-center justify-between ">
<Box className="flex flex-row items-center space-x-2">
<Text variant="h4" className="font-semibold">
Ports
</Text>
<Tooltip title="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s">
<InfoIcon aria-label="Info" className="h-4 w-4" color="primary" />
</Tooltip>
</Box>
<Button
variant="borderless"
onClick={() => append({ port: null, type: null, publish: false })}
>
<PlusIcon className="h-5 w-5" />
</Button>
</Box>
<Box className="flex flex-col space-y-4">
{fields.map((field, index) => (
<Box key={field.id} className="flex flex-col space-y-2">
<Box className="flex w-full flex-col space-y-2 xs+:flex-row xs+:space-x-2 xs+:space-y-0">
<Input
{...register(`ports.${index}.port`)}
id={`${field.id}-port`}
placeholder="Port"
className="w-full"
hideEmptyHelperText
error={!!errors?.ports?.at(index)}
helperText={errors?.ports?.at(index)?.message}
fullWidth
autoComplete="off"
/>
<Select
fullWidth
value={formValues.ports.at(index)?.type || ''}
onChange={(_event, inputValue) =>
onChangePortType(inputValue as string, index)
}
placeholder="Select port type"
slotProps={{
listbox: { className: 'min-w-0 w-full' },
popper: {
disablePortal: false,
className: 'z-[10000] w-[270px] w-full',
},
}}
>
{['http', 'tcp', 'udp']?.map((portType) => (
<Option key={portType} value={portType}>
{portType}
</Option>
))}
</Select>
<ControlledSwitch
{...register(`ports.${index}.publish`)}
disabled={false} // TODO turn off and disable if the port is not http
label={
<Text variant="subtitle1" component="span">
Publish
</Text>
}
/>
<Button
variant="borderless"
className=""
color="error"
onClick={() => remove(index)}
>
<TrashIcon className="h-4 w-4" />
</Button>
</Box>
{showURL(index) && (
<InfoCard
title="URL"
value={getPortURL(
formValues.ports[index]?.port,
formValues.name,
)}
/>
)}
</Box>
))}
</Box>
</Box>
);
}

View File

@@ -0,0 +1,2 @@
/* eslint-disable import/no-cycle */
export { default as PortsFormSection } from './PortsFormSection';

View File

@@ -0,0 +1,44 @@
import { Box } from '@/components/ui/v2/Box';
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { Slider } from '@/components/ui/v2/Slider';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import { MAX_SERVICE_REPLICAS } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import type { ServiceFormValues } from '@/features/services/components/ServiceForm';
import { useFormContext, useWatch } from 'react-hook-form';
export default function ReplicasFormSection() {
const { setValue } = useFormContext<ServiceFormValues>();
const { replicas } = useWatch<ServiceFormValues>();
const handleReplicasChange = (value: string) => {
const updatedReplicas = parseInt(value, 10);
setValue('replicas', updatedReplicas, { shouldDirty: true });
// TODO Trigger revalidate storage
};
return (
<Box className="space-y-4 rounded border-1 p-4">
<Box className="flex flex-row items-center space-x-2">
<Text variant="h4" className="font-semibold">
Replicas ({replicas})
</Text>
<Tooltip title="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s">
<InfoIcon aria-label="Info" className="h-4 w-4" color="primary" />
</Tooltip>
</Box>
<Slider
value={replicas}
onChange={(_event, value) => handleReplicasChange(value.toString())}
min={0}
max={MAX_SERVICE_REPLICAS}
step={1}
aria-label="Replicas"
marks
/>
</Box>
);
}

View File

@@ -0,0 +1 @@
export { default as ReplicasFormSection } from './ReplicasFormSection';

View File

@@ -0,0 +1,130 @@
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { TrashIcon } from '@/components/ui/v2/icons/TrashIcon';
import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import {
MAX_STORAGE_CAPACITY,
MIN_STORAGE_CAPACITY,
} from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import type { ServiceFormValues } from '@/features/services/components/ServiceForm';
import { useFieldArray, useFormContext } from 'react-hook-form';
export default function StorageFormSection() {
const {
register,
setValue,
formState: { errors },
} = useFormContext<ServiceFormValues>();
const { fields, append, remove } = useFieldArray({
name: 'storage',
});
const checkBounds = (value: string, index: number) => {
const storageCapacity = parseInt(value, 10);
if (Number.isNaN(storageCapacity)) {
setValue(`storage.${index}.capacity`, 1);
}
if (storageCapacity > MAX_STORAGE_CAPACITY) {
setValue(`storage.${index}.capacity`, MAX_STORAGE_CAPACITY);
}
if (storageCapacity < MIN_STORAGE_CAPACITY) {
setValue(`storage.${index}.capacity`, MIN_STORAGE_CAPACITY);
}
};
return (
<Box className="p-4 space-y-4 rounded border-1">
<Box className="flex flex-row items-center justify-between ">
<Box className="flex flex-row items-center space-x-2">
<Text variant="h4" className="font-semibold">
Storage
</Text>
<Tooltip title="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s">
<InfoIcon aria-label="Info" className="w-4 h-4" color="primary" />
</Tooltip>
</Box>
<Button
variant="borderless"
onClick={() => append({ name: '', capacity: 1, path: '' })}
>
<PlusIcon className="w-5 h-5" />
</Button>
</Box>
<Box className="flex flex-col space-y-4">
{fields.map((field, index) => (
<Box
key={field.id}
className="flex w-full flex-col space-y-2 xs+:flex-row xs+:space-y-0 xs+:space-x-2"
>
<Input
{...register(`storage.${index}.name`)}
id={`${field.id}-name`}
label={!index && 'Name'}
placeholder="Name"
className="w-full"
hideEmptyHelperText
error={!!errors?.storage?.at(index)}
helperText={errors?.storage?.at(index)?.message}
fullWidth
autoComplete="off"
/>
<Input
{...register(`storage.${index}.capacity`, {
onBlur: (event) => checkBounds(event.target.value, index),
})}
id={`${field.id}-capacity`}
label={!index && 'Capacity'}
type="number"
placeholder="Capacity"
className="w-full"
hideEmptyHelperText
error={!!errors?.storage?.at(index)}
helperText={errors?.storage?.at(index)?.message}
fullWidth
autoComplete="off"
endAdornment={
<Text sx={{ color: 'grey.500' }} className="pr-2">
GiB
</Text>
}
/>
<Input
{...register(`storage.${index}.path`)}
id={`${field.id}-path`}
label={!index && 'Path'}
placeholder="Path"
className="w-full"
hideEmptyHelperText
error={!!errors?.storage?.at(index)}
helperText={errors?.storage?.at(index)?.message}
fullWidth
autoComplete="off"
/>
<Button
variant="borderless"
className=""
color="error"
onClick={() => remove(index)}
>
<TrashIcon className="w-4 h-4" />
</Button>
</Box>
))}
</Box>
</Box>
);
}

View File

@@ -0,0 +1 @@
export { default as StorageFormSection } from './StorageFormSection';

View File

@@ -0,0 +1,2 @@
export * from './ServiceForm';
export { default as ServiceForm } from './ServiceForm';

View File

@@ -0,0 +1,223 @@
import { useDialog } from '@/components/common/DialogProvider';
import { Box } from '@/components/ui/v2/Box';
import { Divider } from '@/components/ui/v2/Divider';
import { Dropdown } from '@/components/ui/v2/Dropdown';
import { IconButton } from '@/components/ui/v2/IconButton';
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
import { CubeIcon } from '@/components/ui/v2/icons/CubeIcon';
import { DotsHorizontalIcon } from '@/components/ui/v2/icons/DotsHorizontalIcon';
import { TrashIcon } from '@/components/ui/v2/icons/TrashIcon';
import { UserIcon } from '@/components/ui/v2/icons/UserIcon';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import {
ServiceForm,
type PortTypes,
} from '@/features/services/components/ServiceForm';
import { getToastStyleProps } from '@/utils/constants/settings';
import { copy } from '@/utils/copy';
import {
useDeleteRunServiceConfigMutation,
useDeleteRunServiceMutation,
} from '@/utils/__generated__/graphql';
import type { ApolloError } from '@apollo/client';
import { formatDistanceToNow } from 'date-fns';
import type { RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
import { toast } from 'react-hot-toast';
interface ServicesListProps {
/**
* The run services fetched from entering the users page.
*/
services: RunService[];
/**
* Function to be called after a submitting the form for either creating or updating a service.
*
* @example onDelete={() => refetch()}
*/
onCreateOrUpdate?: () => Promise<any>;
/**
* Function to be called after a successful delete action.
*
*/
onDelete?: () => Promise<any>;
}
export default function ServicesList({
services,
onCreateOrUpdate,
onDelete,
}: ServicesListProps) {
const { openDrawer } = useDialog();
const [deleteRunService] = useDeleteRunServiceMutation();
const { currentProject } = useCurrentWorkspaceAndProject();
const [deleteRunServiceConfig] = useDeleteRunServiceConfigMutation();
const deleteServiceAndConfig = async (appID: string, serviceID: string) => {
await deleteRunService({ variables: { serviceID } });
await deleteRunServiceConfig({ variables: { appID, serviceID } });
await onDelete?.();
};
const viewService = async (service: RunService) => {
const {
image,
command,
ports,
resources: { compute, replicas, storage },
} = service.config;
openDrawer({
title: (
<Box className="flex flex-row items-center space-x-2">
<CubeIcon className="h-5 w-5" />
<Text>Edit {service.config.name}</Text>
</Box>
),
component: (
<ServiceForm
serviceID={service.id}
initialData={{
...service.config,
image: image.image,
command: command?.join(' '),
ports: ports.map((item) => ({
port: item.port,
type: item.type as PortTypes,
publish: item.publish,
})),
compute,
replicas,
storage,
}}
onSubmit={() => onCreateOrUpdate()}
/>
),
});
};
const deleteService = async (serviceID: string) => {
await toast.promise(
deleteServiceAndConfig(currentProject.id, serviceID),
{
loading: 'Deleteing the service...',
success: `The service has been deleted successfully.`,
error: (arg: ApolloError) => {
// we need to get the internal error message from the GraphQL error
const { internal } = arg.graphQLErrors[0]?.extensions || {};
const { message } = (internal as Record<string, any>)?.error || {};
// we use the default Apollo error message if we can't find the
// internal error message
return (
message ||
arg.message ||
'An error occurred while deleting the service. Please try again.'
);
},
},
getToastStyleProps(),
);
};
return (
<Box className="flex flex-col">
{services.map((service) => (
<Box
key={service.id}
className="flex h-[64px] w-full cursor-pointer items-center justify-between space-x-4 border-b-1 px-4 py-2 transition-colors"
sx={{
[`&:hover`]: {
backgroundColor: 'action.hover',
},
}}
>
<Box
onClick={() => viewService(service)}
className="flex w-full flex-row justify-between"
sx={{
backgroundColor: 'transparent',
}}
>
<div className="flex flex-1 flex-row items-center space-x-4">
<CubeIcon className="h-5 w-5" />
<div className="flex flex-col">
<Text variant="h4" className="font-semibold">
{service.config.name}
</Text>
<Tooltip title={service.updatedAt}>
<span className="hidden cursor-pointer text-sm text-slate-500 xs+:flex">
Deployed {formatDistanceToNow(new Date(service.updatedAt))}{' '}
ago
</span>
</Tooltip>
</div>
</div>
<div className="hidden flex-row items-center space-x-2 md:flex">
<Text variant="subtitle1" className="font-mono text-xs">
{service.id}
</Text>
<IconButton
variant="borderless"
color="secondary"
onClick={(event) => {
copy(service.id, 'Service Id');
event.stopPropagation();
}}
aria-label="Service Id"
>
<CopyIcon className="h-4 w-4" />
</IconButton>
</div>
</Box>
<Dropdown.Root>
<Dropdown.Trigger
asChild
hideChevron
onClick={(event) => event.stopPropagation()}
>
<IconButton
variant="borderless"
color="secondary"
aria-label="More options"
onClick={(event) => event.stopPropagation()}
>
<DotsHorizontalIcon />
</IconButton>
</Dropdown.Trigger>
<Dropdown.Content
menu
PaperProps={{ className: 'w-52' }}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
>
<Dropdown.Item
onClick={() => viewService(service)}
className="z-50 grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
>
<UserIcon className="h-4 w-4" />
<Text className="font-medium">View Service</Text>
</Dropdown.Item>
<Divider component="li" />
<Dropdown.Item
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
sx={{ color: 'error.main' }}
onClick={() => deleteService(service.id)}
>
<TrashIcon className="h-4 w-4" />
<Text className="font-medium" color="error">
Delete Service
</Text>
</Dropdown.Item>
</Dropdown.Content>
</Dropdown.Root>
</Box>
))}
</Box>
);
}

View File

@@ -0,0 +1 @@
export { default as ServicesList } from './ServicesList';

View File

@@ -0,0 +1,5 @@
mutation deleteRunService($serviceID: uuid!) {
deleteRunService(id: $serviceID) {
id
}
}

View File

@@ -0,0 +1,5 @@
mutation deleteRunServiceConfig($appID: uuid!, $serviceID: uuid!) {
deleteRunServiceConfig(appID: $appID, serviceID: $serviceID) {
name
}
}

View File

@@ -0,0 +1,33 @@
query getRunService($id: uuid!, $resolve: Boolean!) {
runService(id: $id) {
id
config(resolve: $resolve) {
name
image {
image
}
command
resources {
compute {
cpu
memory
}
storage {
name
path
capacity
}
replicas
}
environment {
name
value
}
ports {
port
type
publish
}
}
}
}

View File

@@ -0,0 +1,48 @@
query getRunServices(
$appID: uuid!
$resolve: Boolean!
$limit: Int!
$offset: Int!
) {
app(id: $appID) {
runServices(limit: $limit, offset: $offset) {
id
createdAt
updatedAt
config(resolve: $resolve) {
name
image {
image
}
command
resources {
compute {
cpu
memory
}
storage {
name
path
capacity
}
replicas
}
environment {
name
value
}
ports {
port
type
publish
}
}
}
runServices_aggregate {
aggregate {
count
}
}
}
}

View File

@@ -0,0 +1,6 @@
mutation insertRunService($object: run_service_insert_input!) {
insertRunService(object: $object) {
id
appID
}
}

View File

@@ -0,0 +1,13 @@
mutation insertRunServiceConfig(
$appID: uuid!
$serviceID: uuid!
$config: ConfigRunServiceConfigInsertInput!
) {
insertRunServiceConfig(
appID: $appID
serviceID: $serviceID
config: $config
) {
name
}
}

View File

@@ -0,0 +1,13 @@
mutation replaceRunServiceConfig(
$appID: uuid!
$serviceID: uuid!
$config: ConfigRunServiceConfigInsertInput!
) {
replaceRunServiceConfig(
appID: $appID
serviceID: $serviceID
config: $config
) {
__typename
}
}

View File

@@ -0,0 +1,13 @@
mutation updateRunServiceConfig(
$appID: uuid!
$serviceID: uuid!
$config: ConfigRunServiceConfigUpdateInput!
) {
updateRunServiceConfig(
appID: $appID
serviceID: $serviceID
config: $config
) {
name
}
}

View File

@@ -0,0 +1 @@
export { default as useHypertune } from './useHypertune';

View File

@@ -0,0 +1,14 @@
import hypertune from '@/hypertune/hypertune';
import { useEffect, useState } from 'react';
export default function useHypertune() {
const [, setIsInitialized] = useState<boolean>(hypertune.isInitialized());
useEffect(() => {
hypertune.waitForInitialization().then(() => {
setIsInitialized(true);
});
}, []);
return hypertune;
}

View File

@@ -0,0 +1,5 @@
import { initializeHypertune } from './project_2596';
const hypertune = initializeHypertune({});
export default hypertune;

View File

@@ -0,0 +1,107 @@
/* eslint-disable */
import * as sdk from "hypertune";
const projectId = 2596;
const businessToken = `U2FsdGVkX19+V8BJnVR0xLEC+42OW5qZl/A0i6beAaRmJoIhFh5Yf6eIKBzLbV9h`;
const queryCode = `query InitQuery {
root {
enableServices
}
}
`;
const query = {"Query":{"objectTypeName":"Query","selection":{"root":{"fieldArguments":{"__isPartialObject__":true},"fieldQuery":{"Root":{"objectTypeName":"Root","selection":{"enableServices":{"fieldArguments":{},"fieldQuery":null}}}}}}}};
const fallbackInitData: sdk.FallbackInitData & { [key: string]: unknown } = {"commitId":3297,"reducedExpression":{"id":"caxyeQqTKX3UGOXClvbnW","logs":{"events":{},"exposures":{},"evaluations":{}},"type":"ObjectExpression","fields":{"root":{"id":"PoMWxsy7KbW9fCq5XXvx4","body":{"id":"IUICRjZ7iSnh9k0cWBmnd","logs":{"events":{},"exposures":{},"evaluations":{}},"type":"ObjectExpression","fields":{"enableServices":{"id":"7WZWy2AIy_q9Vbz4cn9KB","logs":{"evaluations":{"XNOtHkUBpglrY1nkYa_bf":1},"events":{},"exposures":{}},"type":"BooleanExpression","value":true,"valueType":{"type":"BooleanValueType"}}},"valueType":{"type":"ObjectValueType","objectTypeName":"Root"},"objectTypeName":"Root"},"logs":{"events":{},"exposures":{},"evaluations":{}},"type":"FunctionExpression","valueType":{"type":"FunctionValueType","returnValueType":{"type":"ObjectValueType","objectTypeName":"Root"},"parameterValueTypes":[{"type":"ObjectValueType","objectTypeName":"Query_root_args"}]},"parameters":[{"id":"Ygjhl2LqjiwcousTABFQz","name":"rootArgs"}]}},"metadata":{"permissions":{"user":{},"group":{"team":{"write":"allow"}}}},"valueType":{"type":"ObjectValueType","objectTypeName":"Query"},"objectTypeName":"Query"},"splits":{},"eventTypes":{},"commitConfig":{"splitConfig":{}},"initLogId":0,"commitHash":"4178461588049503","sdkConfig":{"hashPollInterval":1000,"flushLogsInterval":1000,"maxLogsPerFlush":1},"query":{"Query":{"objectTypeName":"Query","selection":{"root":{"fieldArguments":{"__isPartialObject__":true},"fieldQuery":{"Root":{"objectTypeName":"Root","selection":{"enableServices":{"fieldArguments":{},"fieldQuery":null}}}}}}}}};
export function initializeHypertune(
variableValues: Rec,
options: sdk.InitializeOptions = {}
): QueryNode {
const defaultOptions = { businessToken, query, fallbackInitData };
return sdk.initialize(
QueryNode,
projectId,
queryCode,
variableValues,
{ ...defaultOptions, ...options }
);
}
// Enum types
// Input object types
export type Rec = {
//
};
export type Rec2 = {
context: Rec3;
//
};
export type Rec3 = {
workSpace: Rec4;
//
};
export type Rec4 = {
id: string;
//
};
// Enum node classes
// Fragment node classes
export class QueryNode extends sdk.Node {
typeName = "Query" as const;
root(args: Rec2): RootNode {
const props0 = this.getField("root", args);
const expression0 = props0.expression;
if (
expression0 &&
expression0.type === "ObjectExpression"
&& expression0.objectTypeName === "Root"
) {
return new RootNode(props0);
}
const node = new RootNode(props0);
node._logUnexpectedTypeError();
return node;
}
}
export class RootNode extends sdk.Node {
typeName = "Root" as const;
enableServices(args: Rec): sdk.BooleanNode {
const props0 = this.getField("enableServices", args);
const expression0 = props0.expression;
if (
expression0 &&
expression0.type === "BooleanExpression"
) {
return new sdk.BooleanNode(props0);
}
const node = new sdk.BooleanNode(props0);
node._logUnexpectedTypeError();
return node;
}
}

View File

@@ -0,0 +1,190 @@
import { useDialog } from '@/components/common/DialogProvider';
import { Pagination } from '@/components/common/Pagination';
import { Container } from '@/components/layout/Container';
import { ProjectLayout } from '@/components/layout/ProjectLayout';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { CubeIcon } from '@/components/ui/v2/icons/CubeIcon';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { ServicesIcon } from '@/components/ui/v2/icons/ServicesIcon';
import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import type { GetRunServicesQuery } from '@/utils/__generated__/graphql';
import { useGetRunServicesQuery } from '@/utils/__generated__/graphql';
import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification';
import { ServiceForm } from '@/features/services/components/ServiceForm';
import ServicesList from '@/features/services/components/ServicesList/ServicesList';
import { useRouter } from 'next/router';
import { useEffect, useMemo, useRef, useState, type ReactElement } from 'react';
export type RunService = Omit<
GetRunServicesQuery['app']['runServices'][0],
'__typename'
>;
export default function ServicesPage() {
const limit = useRef(25);
const router = useRouter();
const { openDrawer } = useDialog();
const { currentProject } = useCurrentWorkspaceAndProject();
const isPlanFree = currentProject.plan.isFree;
const [currentPage, setCurrentPage] = useState(
parseInt(router.query.page as string, 10) || 1,
);
const [nrOfPages, setNrOfPages] = useState(0);
const offset = useMemo(() => currentPage - 1, [currentPage]);
const {
data,
loading,
refetch: refetchServices,
} = useGetRunServicesQuery({
variables: {
appID: currentProject.id,
resolve: false,
limit: limit.current,
offset,
},
});
useEffect(() => {
if (loading) {
return;
}
const userCount = data?.app?.runServices_aggregate.aggregate.count ?? 0;
setNrOfPages(Math.ceil(userCount / limit.current));
}, [data, loading]);
const services = useMemo(
() => data?.app?.runServices.map((service) => service) ?? [],
[data],
);
const openCreateServiceDialog = () => {
openDrawer({
title: (
<Box className="flex flex-row items-center space-x-2">
<CubeIcon className="h-5 w-5" />
<Text>Create a new service</Text>
</Box>
),
component: <ServiceForm onSubmit={refetchServices} />,
});
};
if (isPlanFree) {
return (
<Container>
<UpgradeNotification
message="Unlock Nhost Run by upgrading your project to the Pro plan."
className="mt-4"
/>
</Container>
);
}
if (data?.app.runServices.length === 0 && !loading) {
return (
<Container className="mx-auto max-w-9xl space-y-5 overflow-x-hidden">
<div className="flex flex-row place-content-end">
<Button
variant="contained"
color="primary"
onClick={openCreateServiceDialog}
startIcon={<PlusIcon className="h-4 w-4" />}
>
Add service
</Button>
</div>
<Box className="flex flex-col items-center justify-center space-y-5 rounded-lg border px-48 py-12 shadow-sm">
<ServicesIcon className="h-10 w-10" />
<div className="flex flex-col space-y-1">
<Text className="text-center font-medium" variant="h3">
No custom services are available
</Text>
<Text variant="subtitle1" className="text-center">
All your projects custom services will be listed here.
</Text>
</div>
<div className="flex flex-row place-content-between rounded-lg ">
<Button
variant="contained"
color="primary"
className="w-full"
onClick={openCreateServiceDialog}
startIcon={<PlusIcon className="h-4 w-4" />}
>
Add service
</Button>
</div>
</Box>
</Container>
);
}
return (
<div className="flex flex-col">
<Box className="flex flex-row place-content-end border-b-1 p-4">
<Button
variant="contained"
color="primary"
onClick={openCreateServiceDialog}
startIcon={<PlusIcon className="h-4 w-4" />}
>
Add service
</Button>
</Box>
<Box className="space-y-4">
<ServicesList
services={services}
onDelete={() => refetchServices()}
onCreateOrUpdate={() => refetchServices()}
/>
<Pagination
className="px-2"
totalNrOfPages={nrOfPages}
currentPageNumber={currentPage}
totalNrOfElements={
data?.app?.runServices_aggregate.aggregate.count ?? 0
}
itemsLabel="services"
elementsPerPage={limit.current}
onPrevPageClick={async () => {
setCurrentPage((page) => page - 1);
if (currentPage - 1 !== 1) {
await router.push({
pathname: router.pathname,
query: { ...router.query, page: currentPage - 1 },
});
}
}}
onNextPageClick={async () => {
setCurrentPage((page) => page + 1);
await router.push({
pathname: router.pathname,
query: { ...router.query, page: currentPage + 1 },
});
}}
onPageChange={async (page) => {
setCurrentPage(page);
await router.push({
pathname: router.pathname,
query: { ...router.query, page },
});
}}
/>
</Box>
</div>
);
}
ServicesPage.getLayout = function getLayout(page: ReactElement) {
return <ProjectLayout>{page}</ProjectLayout>;
};

View File

@@ -30,7 +30,7 @@ export default function UsersPage() {
const remoteProjectGQLClient = useRemoteApplicationGQLClient();
const [searchString, setSearchString] = useState<string>('');
const limit = useRef(25);
const limit = useRef(1);
const router = useRouter();
const [nrOfPages, setNrOfPages] = useState(
parseInt(router.query.page as string, 10) || 1,
@@ -347,6 +347,7 @@ export default function UsersPage() {
.count
: dataRemoteAppUsers?.usersAggregate?.aggregate?.count
}
itemsLabel="users"
elementsPerPage={
searchString
? dataRemoteAppUsers?.filteredUsersAggreggate.aggregate

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,8 @@ module.exports = {
extend: {
colors: {
github: '#24292E;',
brown: '#382D22',
copper: '#DD792D',
},
boxShadow: {
outline: 'inset 0 0 0 2px rgba(0, 82, 205, 0.6)',

View File

@@ -18,6 +18,7 @@
"noImplicitAny": false,
"baseUrl": "./src",
"useUnknownInCatchVariables": false,
"types": ["@types/ace"],
"paths": {
"@/tests/*": ["tests/*"],
"@/e2e/*": ["../e2e/*"],
@@ -28,7 +29,8 @@
"@/styles/*": ["styles/*"],
"@/data/*": ["data/*"],
"@/generated/*": ["utils/__generated__/*"],
"@/features/*": ["features/*"]
"@/features/*": ["features/*"],
"@/hypertune/*": ["hypertune/*"]
},
"incremental": true
},

View File

@@ -76,11 +76,11 @@
"husky": "^8.0.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"turbo": "1.10.6",
"turbo": "1.10.7",
"typedoc": "^0.22.18",
"typescript": "4.9.5",
"vite": "^4.3.8",
"vite-plugin-dts": "^2.3.0",
"vite-plugin-dts": "^3.0.0",
"vite-tsconfig-paths": "^4.2.0",
"vitest": "^0.32.0"
},

445
pnpm-lock.yaml generated
View File

@@ -87,8 +87,8 @@ importers:
specifier: ^2.7.1
version: 2.7.1
turbo:
specifier: 1.10.6
version: 1.10.6
specifier: 1.10.7
version: 1.10.7
typedoc:
specifier: ^0.22.18
version: 0.22.18(typescript@4.9.5)
@@ -99,8 +99,8 @@ importers:
specifier: ^4.3.8
version: 4.3.8(@types/node@16.18.11)
vite-plugin-dts:
specifier: ^2.3.0
version: 2.3.0(@types/node@16.18.11)(vite@4.3.8)
specifier: ^3.0.0
version: 3.0.0(@types/node@16.18.11)(typescript@4.9.5)
vite-tsconfig-paths:
specifier: ^4.2.0
version: 4.2.0(typescript@4.9.5)(vite@4.3.8)
@@ -218,6 +218,9 @@ importers:
graphql-ws:
specifier: ^5.11.2
version: 5.11.2(graphql@16.7.1)
hypertune:
specifier: ^1.4.4
version: 1.4.4(encoding@0.1.13)
just-kebab-case:
specifier: ^4.1.1
version: 4.1.1
@@ -269,6 +272,9 @@ importers:
sharp:
specifier: ^0.32.0
version: 0.32.0
shell-quote:
specifier: ^1.8.1
version: 1.8.1
slugify:
specifier: ^1.6.5
version: 1.6.5
@@ -354,6 +360,9 @@ importers:
'@testing-library/user-event':
specifier: ^14.4.3
version: 14.4.3(@testing-library/dom@9.0.0)
'@types/ace':
specifier: ^0.0.48
version: 0.0.48
'@types/bcryptjs':
specifier: ^2.4.2
version: 2.4.2
@@ -364,8 +373,8 @@ importers:
specifier: ^16.11.7
version: 16.18.11
'@types/pluralize':
specifier: ^0.0.29
version: 0.0.29
specifier: ^0.0.30
version: 0.0.30
'@types/react':
specifier: ^18.2.14
version: 18.2.14
@@ -375,6 +384,9 @@ importers:
'@types/react-table':
specifier: ^7.7.12
version: 7.7.12
'@types/shell-quote':
specifier: ^1.7.1
version: 1.7.1
'@types/testing-library__jest-dom':
specifier: ^5.14.5
version: 5.14.5
@@ -463,8 +475,8 @@ importers:
specifier: ^3.2.0
version: 3.2.0(prettier@2.7.1)(typescript@4.9.5)
prettier-plugin-tailwindcss:
specifier: ^0.3.0
version: 0.3.0(prettier-plugin-organize-imports@3.2.0)(prettier@2.7.1)
specifier: ^0.4.0
version: 0.4.0(prettier-plugin-organize-imports@3.2.0)(prettier@2.7.1)
react-date-fns-hooks:
specifier: ^0.9.4
version: 0.9.4(date-fns@2.29.3)(react-dom@18.2.0)(react@18.2.0)
@@ -2193,10 +2205,10 @@ packages:
'@babel/helper-compilation-targets': 7.21.4(@babel/core@7.20.5)
'@babel/helper-module-transforms': 7.21.2
'@babel/helpers': 7.21.0
'@babel/parser': 7.21.4
'@babel/parser': 7.21.8
'@babel/template': 7.20.7
'@babel/traverse': 7.21.4
'@babel/types': 7.21.4
'@babel/types': 7.21.5
convert-source-map: 1.9.0
debug: 4.3.4
gensync: 1.0.0-beta.2
@@ -2268,7 +2280,7 @@ packages:
resolution: {integrity: sha512-u1dMdBUmA7Z0rBB97xh8pIhviK7oItYOkjbsCxTWMknyvbQRBwX7/gn4JXurRdirWMFh+ZtYARqkA6ydogVZpg==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.21.4
'@babel/types': 7.21.5
'@jridgewell/gen-mapping': 0.3.3
jsesc: 2.5.2
@@ -2276,7 +2288,7 @@ packages:
resolution: {integrity: sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.21.4
'@babel/types': 7.21.5
'@jridgewell/gen-mapping': 0.3.3
jsesc: 2.5.2
@@ -2311,7 +2323,7 @@ packages:
resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.21.4
'@babel/types': 7.21.5
/@babel/helper-builder-binary-assignment-operator-visitor@7.18.9:
resolution: {integrity: sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==}
@@ -2540,7 +2552,7 @@ packages:
resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/types': 7.21.4
'@babel/types': 7.21.5
/@babel/helper-module-imports@7.21.4:
resolution: {integrity: sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==}
@@ -2559,7 +2571,7 @@ packages:
'@babel/helper-validator-identifier': 7.19.1
'@babel/template': 7.20.7
'@babel/traverse': 7.21.4
'@babel/types': 7.21.4
'@babel/types': 7.21.5
transitivePeerDependencies:
- supports-color
@@ -2574,7 +2586,7 @@ packages:
'@babel/helper-validator-identifier': 7.19.1
'@babel/template': 7.20.7
'@babel/traverse': 7.21.4
'@babel/types': 7.21.4
'@babel/types': 7.21.5
transitivePeerDependencies:
- supports-color
@@ -2693,6 +2705,7 @@ packages:
/@babel/helper-string-parser@7.19.4:
resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
engines: {node: '>=6.9.0'}
dev: true
/@babel/helper-string-parser@7.21.5:
resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==}
@@ -2723,7 +2736,7 @@ packages:
dependencies:
'@babel/template': 7.20.7
'@babel/traverse': 7.21.4
'@babel/types': 7.21.4
'@babel/types': 7.21.5
transitivePeerDependencies:
- supports-color
@@ -2733,7 +2746,7 @@ packages:
dependencies:
'@babel/template': 7.20.7
'@babel/traverse': 7.21.4
'@babel/types': 7.21.4
'@babel/types': 7.21.5
transitivePeerDependencies:
- supports-color
@@ -2770,14 +2783,14 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
'@babel/types': 7.21.4
'@babel/types': 7.21.5
/@babel/parser@7.20.3:
resolution: {integrity: sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==}
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
'@babel/types': 7.21.4
'@babel/types': 7.21.5
/@babel/parser@7.21.4:
resolution: {integrity: sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==}
@@ -4819,8 +4832,8 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.21.4
'@babel/parser': 7.21.4
'@babel/types': 7.21.4
'@babel/parser': 7.21.8
'@babel/types': 7.21.5
/@babel/traverse@7.20.1:
resolution: {integrity: sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==}
@@ -4832,8 +4845,8 @@ packages:
'@babel/helper-function-name': 7.21.0
'@babel/helper-hoist-variables': 7.18.6
'@babel/helper-split-export-declaration': 7.18.6
'@babel/parser': 7.21.4
'@babel/types': 7.21.4
'@babel/parser': 7.21.8
'@babel/types': 7.21.5
debug: 4.3.4
globals: 11.12.0
transitivePeerDependencies:
@@ -4877,7 +4890,7 @@ packages:
resolution: {integrity: sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-string-parser': 7.19.4
'@babel/helper-string-parser': 7.21.5
'@babel/helper-validator-identifier': 7.19.1
to-fast-properties: 2.0.0
@@ -4885,7 +4898,7 @@ packages:
resolution: {integrity: sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-string-parser': 7.19.4
'@babel/helper-string-parser': 7.21.5
'@babel/helper-validator-identifier': 7.19.1
to-fast-properties: 2.0.0
@@ -4902,7 +4915,7 @@ packages:
resolution: {integrity: sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-string-parser': 7.19.4
'@babel/helper-string-parser': 7.21.5
'@babel/helper-validator-identifier': 7.19.1
to-fast-properties: 2.0.0
@@ -7482,7 +7495,7 @@ packages:
json-to-pretty-yaml: 1.2.2
listr2: 4.0.5
log-symbols: 4.1.0
shell-quote: 1.7.3
shell-quote: 1.8.1
string-env-interpolation: 1.0.1
ts-log: 2.2.4
ts-node: 10.9.1(@types/node@16.18.11)(typescript@4.9.5)
@@ -9261,26 +9274,26 @@ packages:
/@mdx-js/util@1.6.22:
resolution: {integrity: sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==}
/@microsoft/api-extractor-model@7.27.0(@types/node@16.18.11):
resolution: {integrity: sha512-wHqIMiwSARmiuVLn/zmVpiRncq6hvBfC5GF+sjrN3w4FqVkqFYk7DetvfRNdy/3URdqqmYGrhJlcU9HpLnHOPg==}
/@microsoft/api-extractor-model@7.27.4(@types/node@16.18.11):
resolution: {integrity: sha512-HjqQFmuGPOS20rtnu+9Jj0QrqZyR59E+piUWXPMZTTn4jaZI+4UmsHSf3Id8vyueAhOBH2cgwBuRTE5R+MfSMw==}
dependencies:
'@microsoft/tsdoc': 0.14.2
'@microsoft/tsdoc-config': 0.16.2
'@rushstack/node-core-library': 3.59.1(@types/node@16.18.11)
'@rushstack/node-core-library': 3.59.5(@types/node@16.18.11)
transitivePeerDependencies:
- '@types/node'
dev: true
/@microsoft/api-extractor@7.35.0(@types/node@16.18.11):
resolution: {integrity: sha512-yBGfPJeEtzk8sg2hE2/vOPRvnJBvstbWNGeyGV1jIEUSgytzQ0QPgPEkOsP2n7nBfnyRXmZaBa2vJPGOzVWy+g==}
/@microsoft/api-extractor@7.36.1(@types/node@16.18.11):
resolution: {integrity: sha512-2SPp1jq6wDY5IOsRLUv/4FxngslctBZJlztAJ3uWpCAwqKQG7ESdL3DhEza+StbYLtBQmu1Pk6q1Vkhl7qD/bg==}
hasBin: true
dependencies:
'@microsoft/api-extractor-model': 7.27.0(@types/node@16.18.11)
'@microsoft/api-extractor-model': 7.27.4(@types/node@16.18.11)
'@microsoft/tsdoc': 0.14.2
'@microsoft/tsdoc-config': 0.16.2
'@rushstack/node-core-library': 3.59.1(@types/node@16.18.11)
'@rushstack/rig-package': 0.3.19
'@rushstack/ts-command-line': 4.13.3
'@rushstack/node-core-library': 3.59.5(@types/node@16.18.11)
'@rushstack/rig-package': 0.4.0
'@rushstack/ts-command-line': 4.15.1
colors: 1.2.5
lodash: 4.17.21
resolve: 1.22.1
@@ -10887,7 +10900,7 @@ packages:
rollup:
optional: true
dependencies:
'@rollup/pluginutils': 5.0.2
'@rollup/pluginutils': 5.0.2(rollup@3.23.0)
magic-string: 0.26.7
dev: true
@@ -10899,7 +10912,7 @@ packages:
picomatch: 2.3.1
dev: true
/@rollup/pluginutils@5.0.2:
/@rollup/pluginutils@5.0.2(rollup@3.23.0):
resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==}
engines: {node: '>=14.0.0'}
peerDependencies:
@@ -10911,6 +10924,7 @@ packages:
'@types/estree': 1.0.1
estree-walker: 2.0.2
picomatch: 2.3.1
rollup: 3.23.0
dev: true
/@rushstack/eslint-patch@1.1.3:
@@ -10921,8 +10935,8 @@ packages:
resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==}
dev: true
/@rushstack/node-core-library@3.59.1(@types/node@16.18.11):
resolution: {integrity: sha512-iy/xaEhXGpX+DY1ZzAtNA+QPw+9+TJh773Im+JxG4R1fu00/vWq470UOEj6upxlUxmp0JxhnmNRxzfptHrn/Uw==}
/@rushstack/node-core-library@3.59.5(@types/node@16.18.11):
resolution: {integrity: sha512-1IpV7LufrI1EoVO8hYsb3t6L8L+yp40Sa0OaOV2CIu1zx4e6ZeVNaVIEXFgMXBKdGXkAh21MnCaIzlDNpG6ZQw==}
peerDependencies:
'@types/node': '*'
peerDependenciesMeta:
@@ -10939,15 +10953,15 @@ packages:
z-schema: 5.0.5
dev: true
/@rushstack/rig-package@0.3.19:
resolution: {integrity: sha512-2d0/Gn+qjOYneZbiHjn4SjyDwq9I0WagV37z0F1V71G+yONgH7wlt3K/UoNiDkhA8gTHYPRo2jz3CvttybwSag==}
/@rushstack/rig-package@0.4.0:
resolution: {integrity: sha512-FnM1TQLJYwSiurP6aYSnansprK5l8WUK8VG38CmAaZs29ZeL1msjK0AP1VS4ejD33G0kE/2cpsPsS9jDenBMxw==}
dependencies:
resolve: 1.22.1
strip-json-comments: 3.1.1
dev: true
/@rushstack/ts-command-line@4.13.3:
resolution: {integrity: sha512-6aQIv/o1EgsC/+SpgUyRmzg2QIAL6sudEzw3sWzJKwWuQTc5XRsyZpyldfE7WAmIqMXDao9QG35/NYORjHm5Zw==}
/@rushstack/ts-command-line@4.15.1:
resolution: {integrity: sha512-EL4jxZe5fhb1uVL/P/wQO+Z8Rc8FMiWJ1G7VgnPDvdIt5GVjRfK7vwzder1CZQiX3x0PY6uxENYLNGTFd1InRQ==}
dependencies:
'@types/argparse': 1.0.38
argparse: 1.0.10
@@ -13184,15 +13198,6 @@ packages:
engines: {node: '>=10.13.0'}
dev: false
/@ts-morph/common@0.19.0:
resolution: {integrity: sha512-Unz/WHmd4pGax91rdIKWi51wnVUW11QttMEPpBiBgIewnc9UQIX7UDLxr5vRlqeByXCwhkF6VabSsI0raWcyAQ==}
dependencies:
fast-glob: 3.2.12
minimatch: 7.4.6
mkdirp: 2.1.6
path-browserify: 1.0.1
dev: true
/@tsconfig/docusaurus@2.0.0:
resolution: {integrity: sha512-X5wptT7pXA/46/IRFTW76oR5GNjoy9qjNM/1JGhFV4QAsmLh3YUpJJA+Vpx7Ds6eEBxSxz1QrgoNEBy6rLVs8w==}
dev: true
@@ -13209,6 +13214,10 @@ packages:
/@tsconfig/node16@1.0.2:
resolution: {integrity: sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==}
/@types/ace@0.0.48:
resolution: {integrity: sha512-esV6hOWiDOZ6d7w5S11iLu6LQsPGe/9RPzhri7gNNLdrK1LFpO9/m7IZhQL6dat0JHICJ7l51zvHAiCgnPLLHA==}
dev: true
/@types/argparse@1.0.38:
resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==}
dev: true
@@ -13534,8 +13543,8 @@ packages:
/@types/parse5@5.0.3:
resolution: {integrity: sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==}
/@types/pluralize@0.0.29:
resolution: {integrity: sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA==}
/@types/pluralize@0.0.30:
resolution: {integrity: sha512-kVww6xZrW/db5BR9OqiT71J9huRdQ+z/r+LbDuT7/EK50mCmj5FoaIARnVv0rvjUS/YpDox0cDU9lpQT011VBA==}
dev: true
/@types/pngjs@6.0.1:
@@ -13617,6 +13626,10 @@ packages:
'@types/node': 18.16.14
dev: false
/@types/retry@0.12.0:
resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
dev: false
/@types/retry@0.12.1:
resolution: {integrity: sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==}
dev: false
@@ -13656,6 +13669,10 @@ packages:
'@types/node': 18.16.14
dev: true
/@types/shell-quote@1.7.1:
resolution: {integrity: sha512-SWZ2Nom1pkyXCDohRSrkSKvDh8QOG9RfAsrt5/NsPQC4UQJ55eG0qClA40I+Gkez4KTQ0uDUT8ELRXThf3J5jw==}
dev: true
/@types/sockjs@0.3.33:
resolution: {integrity: sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==}
dependencies:
@@ -14390,6 +14407,17 @@ packages:
wonka: 6.1.1
dev: false
/@vercel/edge-config-fs@0.1.0:
resolution: {integrity: sha512-NRIBwfcS0bUoUbRWlNGetqjvLSwgYH/BqKqDN7vK1g32p7dN96k0712COgaz6VFizAm9b0g6IG6hR6+hc0KCPg==}
dev: false
/@vercel/edge-config@0.2.1:
resolution: {integrity: sha512-847kYqJEbga4PGgNrctQ9XsD+2Kw/S+UjzZnIFpebQ9VbdtB0MX4anq33WetcYZYPfhZd2L0uXVnY/BcjI5dOw==}
engines: {node: '>=14.6'}
dependencies:
'@vercel/edge-config-fs': 0.1.0
dev: false
/@vitejs/plugin-react@3.0.0(vite@4.0.2):
resolution: {integrity: sha512-1mvyPc0xYW5G8CHQvJIJXLoMjl5Ct3q2g5Y2s6Ccfgwm45y48LBvsla7az+GkkAtYikWQ4Lxqcsq5RHLcZgtNQ==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -14567,18 +14595,36 @@ packages:
'@volar/source-map': 0.38.9
dev: true
/@volar/language-core@1.8.0:
resolution: {integrity: sha512-ZHTvZPM3pEbOOuaq+ybNz5TQlHUqPQPK0G1+SonvApGq0e3qgGijjhtL5T7hsCtUEmxfix8FrAuCH14tMBOhTg==}
dependencies:
'@volar/source-map': 1.8.0
dev: true
/@volar/source-map@0.38.9:
resolution: {integrity: sha512-ba0UFoHDYry+vwKdgkWJ6xlQT+8TFtZg1zj9tSjj4PykW1JZDuM0xplMotLun4h3YOoYfY9K1huY5gvxmrNLIw==}
dev: true
/@volar/source-map@1.8.0:
resolution: {integrity: sha512-d35aV0yFkIrkynRSKgrN5hgbMv6ekkFvcJsJGmOZ8UEjqLStto9zq7RSvpp6/PZ7/pa4Gn1f6K1qDt0bq0oUew==}
dependencies:
muggle-string: 0.3.1
dev: true
/@volar/typescript@1.8.0:
resolution: {integrity: sha512-T/U1XLLhXv6tNr40Awznfc6QZWizSL99t6M0DeXtIMbnvSCqjjCVRnwlsq+DK9C1RlO3k8+i0Z8iJn7O1GGtoA==}
dependencies:
'@volar/language-core': 1.8.0
dev: true
/@volar/vue-code-gen@0.38.9:
resolution: {integrity: sha512-tzj7AoarFBKl7e41MR006ncrEmNPHALuk8aG4WdDIaG387X5//5KhWC5Ff3ZfB2InGSeNT+CVUd74M0gS20rjA==}
dependencies:
'@volar/code-gen': 0.38.9
'@volar/source-map': 0.38.9
'@vue/compiler-core': 3.2.41
'@vue/compiler-dom': 3.2.41
'@vue/shared': 3.2.41
'@vue/compiler-core': 3.3.4
'@vue/compiler-dom': 3.3.4
'@vue/shared': 3.3.4
dev: true
/@volar/vue-typescript@0.38.9:
@@ -14588,7 +14634,7 @@ packages:
'@volar/source-map': 0.38.9
'@volar/vue-code-gen': 0.38.9
'@vue/compiler-sfc': 3.2.41
'@vue/reactivity': 3.2.37
'@vue/reactivity': 3.3.4
dev: true
/@vue/apollo-composable@4.0.0-alpha.18(@apollo/client@3.6.9)(graphql@16.7.1)(typescript@4.8.3)(vue@3.2.40):
@@ -14649,6 +14695,15 @@ packages:
estree-walker: 2.0.2
source-map: 0.6.1
/@vue/compiler-core@3.3.4:
resolution: {integrity: sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==}
dependencies:
'@babel/parser': 7.21.8
'@vue/shared': 3.3.4
estree-walker: 2.0.2
source-map-js: 1.0.2
dev: true
/@vue/compiler-dom@3.2.40:
resolution: {integrity: sha512-OZCNyYVC2LQJy4H7h0o28rtk+4v+HMQygRTpmibGoG9wZyomQiS5otU7qo3Wlq5UfHDw2RFwxb9BJgKjVpjrQw==}
dependencies:
@@ -14661,6 +14716,13 @@ packages:
'@vue/compiler-core': 3.2.41
'@vue/shared': 3.2.41
/@vue/compiler-dom@3.3.4:
resolution: {integrity: sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==}
dependencies:
'@vue/compiler-core': 3.3.4
'@vue/shared': 3.3.4
dev: true
/@vue/compiler-sfc@3.2.40:
resolution: {integrity: sha512-tzqwniIN1fu1PDHC3CpqY/dPCfN/RN1thpBC+g69kJcrl7mbGiHKNwbA6kJ3XKKy8R6JLKqcpVugqN4HkeBFFg==}
dependencies:
@@ -14678,7 +14740,7 @@ packages:
/@vue/compiler-sfc@3.2.41:
resolution: {integrity: sha512-+1P2m5kxOeaxVmJNXnBskAn3BenbTmbxBxWOtBq3mQTCokIreuMULFantBUclP0+KnzNCMOvcnKinqQZmiOF8w==}
dependencies:
'@babel/parser': 7.21.4
'@babel/parser': 7.21.8
'@vue/compiler-core': 3.2.41
'@vue/compiler-dom': 3.2.41
'@vue/compiler-ssr': 3.2.41
@@ -14708,6 +14770,25 @@ packages:
/@vue/devtools-api@6.4.5:
resolution: {integrity: sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ==}
/@vue/language-core@1.8.4(typescript@4.9.5):
resolution: {integrity: sha512-pnNtNcJVfkGYluW0vsVO+Y1gyX+eA0voaS7+1JOhCp5zKeCaL/PAmGYOgfvwML62neL+2H8pnhY7sffmrGpEhw==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@volar/language-core': 1.8.0
'@volar/source-map': 1.8.0
'@vue/compiler-dom': 3.3.4
'@vue/reactivity': 3.3.4
'@vue/shared': 3.3.4
minimatch: 9.0.0
muggle-string: 0.3.1
typescript: 4.9.5
vue-template-compiler: 2.7.14
dev: true
/@vue/reactivity-transform@3.2.40:
resolution: {integrity: sha512-HQUCVwEaacq6fGEsg2NUuGKIhUveMCjOk8jGHqLXPI2w6zFoPrlQhwWEaINTv5kkZDXKEnCijAp+4gNEHG03yw==}
dependencies:
@@ -14726,12 +14807,6 @@ packages:
estree-walker: 2.0.2
magic-string: 0.25.9
/@vue/reactivity@3.2.37:
resolution: {integrity: sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==}
dependencies:
'@vue/shared': 3.2.37
dev: true
/@vue/reactivity@3.2.40:
resolution: {integrity: sha512-N9qgGLlZmtUBMHF9xDT4EkD9RdXde1Xbveb+niWMXuHVWQP5BzgRmE3SFyUBBcyayG4y1lhoz+lphGRRxxK4RA==}
dependencies:
@@ -14742,6 +14817,12 @@ packages:
dependencies:
'@vue/shared': 3.2.41
/@vue/reactivity@3.3.4:
resolution: {integrity: sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==}
dependencies:
'@vue/shared': 3.3.4
dev: true
/@vue/runtime-core@3.2.40:
resolution: {integrity: sha512-U1+rWf0H8xK8aBUZhnrN97yoZfHbjgw/bGUzfgKPJl69/mXDuSg8CbdBYBn6VVQdR947vWneQBFzdhasyzMUKg==}
dependencies:
@@ -14786,16 +14867,16 @@ packages:
'@vue/shared': 3.2.41
vue: 3.2.41
/@vue/shared@3.2.37:
resolution: {integrity: sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==}
dev: true
/@vue/shared@3.2.40:
resolution: {integrity: sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ==}
/@vue/shared@3.2.41:
resolution: {integrity: sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw==}
/@vue/shared@3.3.4:
resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==}
dev: true
/@vue/test-utils@2.0.2(vue@3.2.40):
resolution: {integrity: sha512-E2P4oXSaWDqTZNbmKZFVLrNN/siVN78YkEqs7pHryWerrlZR9bBFLWdJwRoguX45Ru6HxIflzKl4vQvwRMwm5g==}
peerDependencies:
@@ -14804,6 +14885,15 @@ packages:
vue: 3.2.40
dev: true
/@vue/typescript@1.8.4(typescript@4.9.5):
resolution: {integrity: sha512-sioQfIY5xcmEAz+cPLvv6CtzGPtGhIdR0Za87zB8M4mPe4OSsE3MBGkXcslf+EzQgF+fm6Gr1SRMSX8r5ZmzDA==}
dependencies:
'@volar/typescript': 1.8.0
'@vue/language-core': 1.8.4(typescript@4.9.5)
transitivePeerDependencies:
- typescript
dev: true
/@vuetify/loader-shared@1.7.0(vue@3.2.41)(vuetify@3.0.0-beta.10):
resolution: {integrity: sha512-Db4K67wMhduDsbvdRBYkrYuomti+j0E/1vlz1lnDng5F9LYYBcXa60qypIazVGI6GX/CuY1vshN6XGtGQI4FKg==}
peerDependencies:
@@ -16865,7 +16955,6 @@ packages:
/cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
dev: true
/cacache@12.0.4:
resolution: {integrity: sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==}
@@ -17382,10 +17471,6 @@ packages:
resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
engines: {node: '>=6'}
/code-block-writer@12.0.0:
resolution: {integrity: sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==}
dev: true
/codemirror-graphql@2.0.9(@codemirror/language@6.3.1)(codemirror@5.65.9)(graphql@16.7.1):
resolution: {integrity: sha512-gl1LR6XSBgZtl7Dr2q4jjRNfhxMF8vn+rnjZTZPf/l+VrQgavY8l3G//hW7s3hWy73iiqkq5LZ4KE1tdaxB/vQ==}
peerDependencies:
@@ -17730,7 +17815,6 @@ packages:
/core-js@3.30.2:
resolution: {integrity: sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg==}
requiresBuild: true
dev: true
/core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
@@ -17956,6 +18040,14 @@ packages:
transitivePeerDependencies:
- encoding
/cross-fetch@4.0.0(encoding@0.1.13):
resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
dependencies:
node-fetch: 2.6.12(encoding@0.1.13)
transitivePeerDependencies:
- encoding
dev: false
/cross-spawn@5.1.0:
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
dependencies:
@@ -18679,6 +18771,10 @@ packages:
time-zone: 1.0.0
dev: true
/de-indent@1.0.2:
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
dev: true
/debounce@1.2.1:
resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
dev: true
@@ -20984,7 +21080,6 @@ packages:
/extract-files@9.0.0:
resolution: {integrity: sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==}
engines: {node: ^10.17.0 || ^12.0.0 || >= 13.7.0}
dev: true
/fast-decode-uri-component@1.0.1:
resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==}
@@ -21490,7 +21585,6 @@ packages:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: true
/form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
@@ -22282,7 +22376,6 @@ packages:
graphql: 16.7.1
transitivePeerDependencies:
- encoding
dev: true
/graphql-request@6.0.0(encoding@0.1.13)(graphql@16.7.1):
resolution: {integrity: sha512-2BmHTuglonjZvmNVw6ZzCfFlW/qkIPds0f+Qdi/Lvjsl3whJg2uvHmSvHnLWhUTEw6zcxPYAHiZoPvSVKOZ7Jw==}
@@ -22859,6 +22952,28 @@ packages:
hasBin: true
dev: true
/hypertune@1.4.4(encoding@0.1.13):
resolution: {integrity: sha512-QzKG88XRH1zWJd3pcnicLxDx/sH0qN0EElpgFiPq/ahl85LIAdn0n1ly/UoYQsbTanglPJFE1yjdBPc/9ACe1A==}
hasBin: true
dependencies:
'@vercel/edge-config': 0.2.1
cac: 6.7.14
core-js: 3.30.2
cross-fetch: 4.0.0(encoding@0.1.13)
dotenv: 16.1.3
graphql: 16.7.1
graphql-request: 5.1.0(encoding@0.1.13)(graphql@16.7.1)
graphql-tag: 2.12.6(graphql@16.7.1)
joycon: 3.1.1
json-stable-stringify: 1.0.2
nanoid: 3.3.6
p-retry: 4.6.2
regenerator-runtime: 0.13.11
zod: 3.21.4
transitivePeerDependencies:
- encoding
dev: false
/iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
@@ -23857,6 +23972,11 @@ packages:
resolution: {integrity: sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==}
dev: false
/joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
dev: false
/jpeg-js@0.4.4:
resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==}
dev: true
@@ -24045,6 +24165,12 @@ packages:
jsonify: 0.0.0
dev: true
/json-stable-stringify@1.0.2:
resolution: {integrity: sha512-eunSSaEnxV12z+Z73y/j5N37/In40GK4GmsSy+tEHJMxknvqnA7/djeYtAgW0GsWHUfg+847WJjKaEylk2y09g==}
dependencies:
jsonify: 0.0.1
dev: false
/json-to-pretty-yaml@1.2.2:
resolution: {integrity: sha512-rvm6hunfCcqegwYaG5T4yKJWxc9FXFgBVrcTZ4XfSVRwa5HA/Xs+vB/Eo9treYYHCeNM0nrSUr82V/M31Urc7A==}
engines: {node: '>= 0.2.0'}
@@ -24106,6 +24232,10 @@ packages:
resolution: {integrity: sha512-trvBk1ki43VZptdBI5rIlG4YOzyeH/WefQt5rj1grasPn4iiZWKet8nkgc4GlsAylaztn0qZfUYOiTsASJFdNA==}
dev: true
/jsonify@0.0.1:
resolution: {integrity: sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==}
dev: false
/jsonwebtoken@8.5.1:
resolution: {integrity: sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==}
engines: {node: '>=4', npm: '>=1.4.28'}
@@ -24659,13 +24789,6 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/magic-string@0.29.0:
resolution: {integrity: sha512-WcfidHrDjMY+eLjlU+8OvwREqHwpgCeKVBUpQ3OhYYuvfaYCUgcbuBzappNzZvg/v8onU3oQj+BYpkOJe9Iw4Q==}
engines: {node: '>=12'}
dependencies:
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/magic-string@0.30.0:
resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==}
engines: {node: '>=12'}
@@ -25168,13 +25291,6 @@ packages:
dependencies:
brace-expansion: 2.0.1
/minimatch@7.4.6:
resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==}
engines: {node: '>=10'}
dependencies:
brace-expansion: 2.0.1
dev: true
/minimatch@9.0.0:
resolution: {integrity: sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==}
engines: {node: '>=16 || 14 >=14.17'}
@@ -25278,12 +25394,6 @@ packages:
engines: {node: '>=10'}
hasBin: true
/mkdirp@2.1.6:
resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==}
engines: {node: '>=10'}
hasBin: true
dev: true
/mlly@1.2.0:
resolution: {integrity: sha512-+c7A3CV0KGdKcylsI6khWyts/CYrGTrRVo4R/I7u/cUsy0Conxa6LUhiEzVKIw14lc2L5aiO4+SeVe4TeGRKww==}
dependencies:
@@ -25410,6 +25520,10 @@ packages:
- supports-color
dev: true
/muggle-string@0.3.1:
resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==}
dev: true
/multicast-dns@7.2.4:
resolution: {integrity: sha512-XkCYOU+rr2Ft3LI6w4ye51M3VK31qJXFIxu0XLw169PtKG0Zx47OrXeVW/GCYOfpC9s1yyyf1S+L8/4LY0J9Zw==}
hasBin: true
@@ -25674,6 +25788,19 @@ packages:
resolution: {integrity: sha512-Jf1IQZdovUIv9E+5avmN6Sf+bND+rnMlODnBQhdE2VRyuWP9WgqZb/KEgPekh19DAN1X2C4vbS1VCOaz2OH19g==}
dev: true
/node-fetch@2.6.12(encoding@0.1.13):
resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==}
engines: {node: 4.x || >=6.0.0}
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
dependencies:
encoding: 0.1.13
whatwg-url: 5.0.0
dev: false
/node-fetch@2.6.7(encoding@0.1.13):
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
engines: {node: 4.x || >=6.0.0}
@@ -26246,6 +26373,14 @@ packages:
retry: 0.13.1
dev: false
/p-retry@4.6.2:
resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==}
engines: {node: '>=8'}
dependencies:
'@types/retry': 0.12.0
retry: 0.13.1
dev: false
/p-timeout@3.2.0:
resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==}
engines: {node: '>=8'}
@@ -27448,8 +27583,8 @@ packages:
typescript: 4.9.5
dev: true
/prettier-plugin-tailwindcss@0.3.0(prettier-plugin-organize-imports@3.2.0)(prettier@2.7.1):
resolution: {integrity: sha512-009/Xqdy7UmkcTBpwlq7jsViDqXAYSOMLDrHAdTMlVZOrKfM2o9Ci7EMWTMZ7SkKBFTG04UM9F9iM2+4i6boDA==}
/prettier-plugin-tailwindcss@0.4.0(prettier-plugin-organize-imports@3.2.0)(prettier@2.7.1):
resolution: {integrity: sha512-Rna0sDPETA0KNhMHlN8wxKNgfSa8mTl2hPPAGxnbv6tUcHT6J4RQmQ8TLXyhB7Dm5Von4iHloBxTyClYM6wT0A==}
engines: {node: '>=12.17.0'}
peerDependencies:
'@ianvs/prettier-plugin-sort-imports': '*'
@@ -27457,7 +27592,7 @@ packages:
'@shopify/prettier-plugin-liquid': '*'
'@shufo/prettier-plugin-blade': '*'
'@trivago/prettier-plugin-sort-imports': '*'
prettier: '>=2.2.0'
prettier: ^2.2 || ^3.0
prettier-plugin-astro: '*'
prettier-plugin-css-order: '*'
prettier-plugin-import-sort: '*'
@@ -27934,7 +28069,7 @@ packages:
prompts: 2.4.2
react-error-overlay: 6.0.11
recursive-readdir: 2.2.2
shell-quote: 1.7.3
shell-quote: 1.8.1
strip-ansi: 6.0.1
text-table: 0.2.0
typescript: 4.8.4
@@ -29372,6 +29507,10 @@ packages:
/shell-quote@1.7.3:
resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==}
dev: true
/shell-quote@1.8.1:
resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==}
/shelljs@0.8.5:
resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==}
@@ -30866,13 +31005,6 @@ packages:
resolution: {integrity: sha512-DEQrfv6l7IvN2jlzc/VTdZJYsWUnQNCsueYjMkC/iXoEoi5fNan6MjeDqkvhfzbmHgdz9UxDUluX3V5HdjTydQ==}
dev: true
/ts-morph@18.0.0:
resolution: {integrity: sha512-Kg5u0mk19PIIe4islUI/HWRvm9bC1lHejK4S0oh1zaZ77TMZAEmQC0sHQYiu2RgCQFZKXz1fMVi/7nOOeirznA==}
dependencies:
'@ts-morph/common': 0.19.0
code-block-writer: 12.0.0
dev: true
/ts-node-dev@2.0.0(@types/node@18.11.9)(typescript@4.7.4):
resolution: {integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==}
engines: {node: '>=0.8.0'}
@@ -31230,65 +31362,65 @@ packages:
safe-buffer: 5.2.1
dev: false
/turbo-darwin-64@1.10.6:
resolution: {integrity: sha512-s2Gc7i9Ud+H9GDcrGJjPIyscJfzDGQ6il4Sl2snfvwngJs4/TaqKuBoX3HNt/7F4NiFRs7ZhlLV1/Yu9zGBRhw==}
/turbo-darwin-64@1.10.7:
resolution: {integrity: sha512-N2MNuhwrl6g7vGuz4y3fFG2aR1oCs0UZ5HKl8KSTn/VC2y2YIuLGedQ3OVbo0TfEvygAlF3QGAAKKtOCmGPNKA==}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/turbo-darwin-arm64@1.10.6:
resolution: {integrity: sha512-tgl70t5PBLyRcNTdP9N6NjvdvQ5LUk8Z60JGUhBhnc+oCOdA4pltrDJNPyel3tQAXXt1dDpl8pp9vUrbwoVyGg==}
/turbo-darwin-arm64@1.10.7:
resolution: {integrity: sha512-WbJkvjU+6qkngp7K4EsswOriO3xrNQag7YEGRtfLoDdMTk4O4QTeU6sfg2dKfDsBpTidTvEDwgIYJhYVGzrz9Q==}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/turbo-linux-64@1.10.6:
resolution: {integrity: sha512-h7eyAA3xtAVpamcYJYUwe0xm0LWdbv7/I7QiM09AZ67TTNpyUgqW8giFN3h793BHEQ2Rcnk9FNkpIbjWBbyamg==}
/turbo-linux-64@1.10.7:
resolution: {integrity: sha512-x1CF2CDP1pDz/J8/B2T0hnmmOQI2+y11JGIzNP0KtwxDM7rmeg3DDTtDM/9PwGqfPotN9iVGgMiMvBuMFbsLhg==}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/turbo-linux-arm64@1.10.6:
resolution: {integrity: sha512-8cZhOeLqu3QZ27yLd6bw4FNaB8y5pLdWeRLJeiWHkIb/cptKnRKJFP+keBJzJi8ovaMqdBpabrxiBRN2lhau5Q==}
/turbo-linux-arm64@1.10.7:
resolution: {integrity: sha512-JtnBmaBSYbs7peJPkXzXxsRGSGBmBEIb6/kC8RRmyvPAMyqF8wIex0pttsI+9plghREiGPtRWv/lfQEPRlXnNQ==}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/turbo-windows-64@1.10.6:
resolution: {integrity: sha512-qx5jcfCJodN1Mh0KtSVQau7pK8CxDvtif7+joPHI2HbQPAADgdUl0LHfA5tFHh6aWgfvhxbvIXqJd6v7Mqkj9g==}
/turbo-windows-64@1.10.7:
resolution: {integrity: sha512-7A/4CByoHdolWS8dg3DPm99owfu1aY/W0V0+KxFd0o2JQMTQtoBgIMSvZesXaWM57z3OLsietFivDLQPuzE75w==}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/turbo-windows-arm64@1.10.6:
resolution: {integrity: sha512-vTQaRG3/s2XTreOBr6J9HKFtjzusvwGQg0GtuW2+9Z7fizdzP8MuhaDbN6FhKHcWC81PQPD61TBIKTVTsYOEZg==}
/turbo-windows-arm64@1.10.7:
resolution: {integrity: sha512-D36K/3b6+hqm9IBAymnuVgyePktwQ+F0lSXr2B9JfAdFPBktSqGmp50JNC7pahxhnuCLj0Vdpe9RqfnJw5zATA==}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/turbo@1.10.6:
resolution: {integrity: sha512-0/wbjw4HvmPP1abVWHTdeFRfCA9cn5oxCPP5bDixagLzvDgGWE3xfdlsyGmq779Ekr9vjtDPgC2Y4JlXEhyryw==}
/turbo@1.10.7:
resolution: {integrity: sha512-xm0MPM28TWx1e6TNC3wokfE5eaDqlfi0G24kmeHupDUZt5Wd0OzHFENEHMPqEaNKJ0I+AMObL6nbSZonZBV2HA==}
hasBin: true
requiresBuild: true
optionalDependencies:
turbo-darwin-64: 1.10.6
turbo-darwin-arm64: 1.10.6
turbo-linux-64: 1.10.6
turbo-linux-arm64: 1.10.6
turbo-windows-64: 1.10.6
turbo-windows-arm64: 1.10.6
turbo-darwin-64: 1.10.7
turbo-darwin-arm64: 1.10.7
turbo-linux-64: 1.10.7
turbo-linux-arm64: 1.10.7
turbo-windows-64: 1.10.7
turbo-windows-arm64: 1.10.7
dev: true
/type-check@0.3.2:
@@ -32143,27 +32275,31 @@ packages:
- terser
dev: true
/vite-plugin-dts@2.3.0(@types/node@16.18.11)(vite@4.3.8):
resolution: {integrity: sha512-WbJgGtsStgQhdm3EosYmIdTGbag5YQpZ3HXWUAPCDyoXI5qN6EY0V7NXq0lAmnv9hVQsvh0htbYcg0Or5Db9JQ==}
/vite-plugin-dts@3.0.0(@types/node@16.18.11)(typescript@4.9.5):
resolution: {integrity: sha512-3UfEnH7DtokNZfEWEODzKWJOVTpNJL26ok121VGgxa2Iu6/uX5wgMiYPnadUuQsowXFibbWijbT4MQy8Ig/+fg==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
vite: '>=2.9.0'
typescript: '*'
dependencies:
'@babel/parser': 7.21.8
'@microsoft/api-extractor': 7.35.0(@types/node@16.18.11)
'@rollup/pluginutils': 5.0.2
'@rushstack/node-core-library': 3.59.1(@types/node@16.18.11)
'@microsoft/api-extractor': 7.36.1(@types/node@16.18.11)
'@rollup/pluginutils': 5.0.2(rollup@3.23.0)
'@rushstack/node-core-library': 3.59.5(@types/node@16.18.11)
'@vue/language-core': 1.8.4(typescript@4.9.5)
debug: 4.3.4
fast-glob: 3.2.12
fs-extra: 10.1.0
kolorist: 1.8.0
magic-string: 0.29.0
ts-morph: 18.0.0
typescript: 4.9.5
vue-tsc: 1.8.4(typescript@4.9.5)
optionalDependencies:
rollup: 3.23.0
vite: 4.3.8(@types/node@16.18.11)
transitivePeerDependencies:
- '@types/node'
- rollup
- less
- sass
- stylus
- sugarss
- supports-color
- terser
dev: true
/vite-plugin-pages@0.28.0(vite@4.0.2):
@@ -32665,6 +32801,13 @@ packages:
'@vue/devtools-api': 6.4.5
vue: 3.2.41
/vue-template-compiler@2.7.14:
resolution: {integrity: sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==}
dependencies:
de-indent: 1.0.2
he: 1.2.0
dev: true
/vue-tsc@0.38.9(typescript@4.8.3):
resolution: {integrity: sha512-Yoy5phgvGqyF98Fb4mYqboR4Q149jrdcGv5kSmufXJUq++RZJ2iMVG0g6zl+v3t4ORVWkQmRpsV4x2szufZ0LQ==}
hasBin: true
@@ -32685,6 +32828,18 @@ packages:
typescript: 4.9.4
dev: true
/vue-tsc@1.8.4(typescript@4.9.5):
resolution: {integrity: sha512-+hgpOhIx11vbi8/AxEdaPj3fiRwN9wy78LpsNNw2V995/IWa6TMyQxHbaw2ZKUpdwjySSHgrT6ohDEhUgFxGYw==}
hasBin: true
peerDependencies:
typescript: '*'
dependencies:
'@vue/language-core': 1.8.4(typescript@4.9.5)
'@vue/typescript': 1.8.4(typescript@4.9.5)
semver: 7.5.1
typescript: 4.9.5
dev: true
/vue@3.2.40:
resolution: {integrity: sha512-1mGHulzUbl2Nk3pfvI5aXYYyJUs1nm4kyvuz38u4xlQkLUn1i2R7nDbI4TufECmY8v1qNBHYy62bCaM+3cHP2A==}
dependencies:
@@ -33651,5 +33806,9 @@ packages:
/zen-observable@0.8.15:
resolution: {integrity: sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==}
/zod@3.21.4:
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
dev: false
/zwitch@1.0.5:
resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==}