Compare commits
126 Commits
@nhost/das
...
@nhost/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cc7704555 | ||
|
|
c0954dec09 | ||
|
|
6c25480a7a | ||
|
|
da03bf390c | ||
|
|
3b513be9f2 | ||
|
|
e450e9d636 | ||
|
|
ed1ee10879 | ||
|
|
349aac369e | ||
|
|
5a84362c80 | ||
|
|
f59a77b1c8 | ||
|
|
30686bc4ce | ||
|
|
0c4ac8d368 | ||
|
|
7da0e5e256 | ||
|
|
8229101efe | ||
|
|
afad1778f8 | ||
|
|
28fc7b84c7 | ||
|
|
3f478a4e3c | ||
|
|
aa54666941 | ||
|
|
20fb69faba | ||
|
|
1caeb2a548 | ||
|
|
6356c5a2c8 | ||
|
|
6ec1dd3248 | ||
|
|
8a4b5031dc | ||
|
|
4790fee41f | ||
|
|
0a8033812d | ||
|
|
2b56ffc29e | ||
|
|
aa9b926cd7 | ||
|
|
575404ad62 | ||
|
|
3f6dfc7bcd | ||
|
|
682e64d7a3 | ||
|
|
30cee4f86c | ||
|
|
29dcc8c63e | ||
|
|
d926f15676 | ||
|
|
d4a0aad2dd | ||
|
|
1030813279 | ||
|
|
917a14aa40 | ||
|
|
6381d1b095 | ||
|
|
8dbdc0bf50 | ||
|
|
8c072a4c6e | ||
|
|
fe341519f7 | ||
|
|
ea09384064 | ||
|
|
49b9972885 | ||
|
|
98c541ee52 | ||
|
|
757c888656 | ||
|
|
7c13eb5f9b | ||
|
|
a84608e086 | ||
|
|
e43c079b9c | ||
|
|
3f396a9ebb | ||
|
|
6ed605beb8 | ||
|
|
edd223d29c | ||
|
|
15a985e079 | ||
|
|
8ff00a4258 | ||
|
|
7e27d7c0a1 | ||
|
|
49f9b8372a | ||
|
|
925bf0f13f | ||
|
|
30d35f9607 | ||
|
|
755aa56f12 | ||
|
|
4c7e7c57a9 | ||
|
|
36708e2853 | ||
|
|
90c6031189 | ||
|
|
f044dbdb10 | ||
|
|
c2f3bce5f9 | ||
|
|
22d9877b97 | ||
|
|
628e96dcc3 | ||
|
|
3e9d3c42b6 | ||
|
|
a1e7b87c38 | ||
|
|
1bd800359e | ||
|
|
54a204a34e | ||
|
|
b17e8d6f3c | ||
|
|
12e2855f01 | ||
|
|
c1080d9e63 | ||
|
|
e4972b8307 | ||
|
|
2e7ec0697e | ||
|
|
2d9baec9d4 | ||
|
|
7a7750be0b | ||
|
|
0f34f0c6b9 | ||
|
|
d05253183a | ||
|
|
65df016bbc | ||
|
|
3e6ee1ae97 | ||
|
|
6042ed101f | ||
|
|
384bce59bf | ||
|
|
8da291ad4d | ||
|
|
f94eb3c467 | ||
|
|
9baf3f4ac7 | ||
|
|
9c406548e3 | ||
|
|
1c08cd1949 | ||
|
|
adc828a582 | ||
|
|
f1ec6b9a93 | ||
|
|
233b7e383e | ||
|
|
7ea469a1e3 | ||
|
|
ebd218c180 | ||
|
|
5ab1626f73 | ||
|
|
444c3b86ca | ||
|
|
7238412341 | ||
|
|
f6639ae05c | ||
|
|
d8ceccec5d | ||
|
|
6db257d4c7 | ||
|
|
93dab2d183 | ||
|
|
dfc18368be | ||
|
|
f7c6e80bf2 | ||
|
|
573cac1431 | ||
|
|
d72ae3f362 | ||
|
|
49ec7ec385 | ||
|
|
7d2b4083c2 | ||
|
|
696b493745 | ||
|
|
15a117a861 | ||
|
|
e7ff1f79f8 | ||
|
|
33c7368a2e | ||
|
|
664c182c8e | ||
|
|
c1ab4e0a77 | ||
|
|
4a4bd61757 | ||
|
|
b6d05289be | ||
|
|
5857458ca5 | ||
|
|
2fb1145fe0 | ||
|
|
546d710102 | ||
|
|
ed66769688 | ||
|
|
a0298e0bdb | ||
|
|
3fd94b1cdf | ||
|
|
61d5f7d616 | ||
|
|
cde9a0a715 | ||
|
|
eae6349b04 | ||
|
|
211b930b84 | ||
|
|
4ae463074b | ||
|
|
1c5a4746f7 | ||
|
|
d6ae1fa44a | ||
|
|
a3abb81b37 |
@@ -14,7 +14,7 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: pnpm/action-setup@v2.2.4
|
- uses: pnpm/action-setup@v2.2.4
|
||||||
with:
|
with:
|
||||||
version: 7.17.0
|
version: 8.4.0
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm cache directory
|
- name: Get pnpm cache directory
|
||||||
id: pnpm-cache-dir
|
id: pnpm-cache-dir
|
||||||
|
|||||||
16
.github/stale.yml
vendored
Normal file
16
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Configuration for probot-stale - https://github.com/probot/stale
|
||||||
|
|
||||||
|
daysUntilStale: 180
|
||||||
|
daysUntilClose: 7
|
||||||
|
limitPerRun: 30
|
||||||
|
onlyLabels: []
|
||||||
|
exemptLabels: []
|
||||||
|
|
||||||
|
exemptProjects: false
|
||||||
|
exemptMilestones: false
|
||||||
|
exemptAssignees: false
|
||||||
|
staleLabel: stale
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
for your contributions.
|
||||||
@@ -36,6 +36,7 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
target: 'es2019',
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
lib: {
|
lib: {
|
||||||
entry,
|
entry,
|
||||||
|
|||||||
@@ -1,5 +1,84 @@
|
|||||||
# @nhost/dashboard
|
# @nhost/dashboard
|
||||||
|
|
||||||
|
## 0.16.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [da03bf39]
|
||||||
|
- @nhost/react-apollo@5.0.21
|
||||||
|
- @nhost/nextjs@1.13.23
|
||||||
|
|
||||||
|
## 0.16.9
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 349aac36: fix(settings): use region domain when constructing the postgres connection string
|
||||||
|
|
||||||
|
## 0.16.8
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 20fb69fa: chore(projects): change the way how API URLs are constructed
|
||||||
|
|
||||||
|
## 0.16.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 49f9b837: chore(docker): bump `pnpm` to `v8.4.0` and `turbo` to `v1.9.3`
|
||||||
|
- 3f478a4e: chore(deps): bump `vitest` to `v0.31.0`, `@types/react` to `v18.2.6` and `@types/react-dom` to `v18.2.4`
|
||||||
|
|
||||||
|
## 0.16.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- d926f156: fix(projects): redirect to 404 when an invalid project is opened
|
||||||
|
- 49b99728: fix(projects): disable features for non-owner members of workspaces
|
||||||
|
|
||||||
|
## 0.16.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 12e2855f: chore(deps): bump `jsdom` to v22
|
||||||
|
- e4972b83: feat(metrics): add Grafana page
|
||||||
|
|
||||||
|
## 0.16.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 3f396a9e: fix(projects): unpause after upgrading a paused project to pro
|
||||||
|
- 3f396a9e: fix(projects): don't redirect to 404 page after project creation
|
||||||
|
|
||||||
|
## 0.16.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [90c60311]
|
||||||
|
- @nhost/react-apollo@5.0.20
|
||||||
|
- @nhost/nextjs@1.13.22
|
||||||
|
|
||||||
|
## 0.16.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 0f34f0c6: fix(projects): disallow downgrading to free plan
|
||||||
|
- 8da291ad: chore(deps): bump `@types/react` to v18.2.0 and `@types/react-dom` to v18.2.1
|
||||||
|
|
||||||
|
## 0.16.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- adc828a5: fix(gql): don't enter an infinite loop when fetching remote app data
|
||||||
|
|
||||||
|
## 0.16.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 2fb1145f: feat(compute): add support for replicas
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- d8ceccec: chore(env): remove deprecated `NHOST_BACKEND_URL` environment variable
|
||||||
|
|
||||||
## 0.15.2
|
## 0.15.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ RUN apk add --no-cache libc6-compat
|
|||||||
RUN apk update
|
RUN apk update
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN yarn global add turbo@1.8.6
|
RUN yarn global add turbo@1.9.3
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN turbo prune --scope="@nhost/dashboard" --docker
|
RUN turbo prune --scope="@nhost/dashboard" --docker
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ ENV NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL __NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL_
|
|||||||
ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL __NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__
|
ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL __NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__
|
||||||
ENV NEXT_PUBLIC_NHOST_HASURA_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__
|
ENV NEXT_PUBLIC_NHOST_HASURA_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__
|
||||||
|
|
||||||
RUN yarn global add pnpm@7.17.0
|
RUN yarn global add pnpm@8.4.0
|
||||||
COPY .gitignore .gitignore
|
COPY .gitignore .gitignore
|
||||||
COPY --from=pruner /app/out/json/ .
|
COPY --from=pruner /app/out/json/ .
|
||||||
COPY --from=pruner /app/out/pnpm-*.yaml .
|
COPY --from=pruner /app/out/pnpm-*.yaml .
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ test('should show a sidebar with menu items', async () => {
|
|||||||
const navLocator = page.getByRole('navigation', { name: /main navigation/i });
|
const navLocator = page.getByRole('navigation', { name: /main navigation/i });
|
||||||
await expect(navLocator).toBeVisible();
|
await expect(navLocator).toBeVisible();
|
||||||
await expect(navLocator.getByRole('list').getByRole('listitem')).toHaveCount(
|
await expect(navLocator.getByRole('list').getByRole('listitem')).toHaveCount(
|
||||||
10,
|
11,
|
||||||
);
|
);
|
||||||
await expect(
|
await expect(
|
||||||
navLocator.getByRole('link', { name: /overview/i }),
|
navLocator.getByRole('link', { name: /overview/i }),
|
||||||
@@ -53,6 +53,9 @@ test('should show a sidebar with menu items', async () => {
|
|||||||
navLocator.getByRole('link', { name: /backups/i }),
|
navLocator.getByRole('link', { name: /backups/i }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
await expect(navLocator.getByRole('link', { name: /logs/i })).toBeVisible();
|
await expect(navLocator.getByRole('link', { name: /logs/i })).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
navLocator.getByRole('link', { name: /metrics/i }),
|
||||||
|
).toBeVisible();
|
||||||
await expect(
|
await expect(
|
||||||
navLocator.getByRole('link', { name: /settings/i }),
|
navLocator.getByRole('link', { name: /settings/i }),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/dashboard",
|
"name": "@nhost/dashboard",
|
||||||
"version": "0.15.2",
|
"version": "0.16.10",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
@@ -105,15 +105,15 @@
|
|||||||
"@types/lodash.debounce": "^4.0.7",
|
"@types/lodash.debounce": "^4.0.7",
|
||||||
"@types/node": "^16.11.7",
|
"@types/node": "^16.11.7",
|
||||||
"@types/pluralize": "^0.0.29",
|
"@types/pluralize": "^0.0.29",
|
||||||
"@types/react": "18.0.37",
|
"@types/react": "18.2.6",
|
||||||
"@types/react-dom": "18.0.11",
|
"@types/react-dom": "18.2.4",
|
||||||
"@types/react-table": "^7.7.12",
|
"@types/react-table": "^7.7.12",
|
||||||
"@types/testing-library__jest-dom": "^5.14.5",
|
"@types/testing-library__jest-dom": "^5.14.5",
|
||||||
"@types/validator": "^13.7.10",
|
"@types/validator": "^13.7.10",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.43.0",
|
"@typescript-eslint/eslint-plugin": "^5.43.0",
|
||||||
"@typescript-eslint/parser": "^5.43.0",
|
"@typescript-eslint/parser": "^5.43.0",
|
||||||
"@vitejs/plugin-react": "^4.0.0",
|
"@vitejs/plugin-react": "^4.0.0",
|
||||||
"@vitest/coverage-c8": "^0.30.0",
|
"@vitest/coverage-c8": "^0.31.0",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"babel-loader": "^8.3.0",
|
"babel-loader": "^8.3.0",
|
||||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
"eslint-plugin-jsx-a11y": "^6.6.1",
|
"eslint-plugin-jsx-a11y": "^6.6.1",
|
||||||
"eslint-plugin-react": "^7.31.11",
|
"eslint-plugin-react": "^7.31.11",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"jsdom": "^21.0.0",
|
"jsdom": "^22.0.0",
|
||||||
"lint-staged": ">=13",
|
"lint-staged": ">=13",
|
||||||
"msw": "^1.0.1",
|
"msw": "^1.0.1",
|
||||||
"msw-storybook-addon": "^1.6.3",
|
"msw-storybook-addon": "^1.6.3",
|
||||||
@@ -147,8 +147,7 @@
|
|||||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
||||||
"vite": "^4.0.2",
|
"vite": "^4.0.2",
|
||||||
"vite-tsconfig-paths": "^4.0.3",
|
"vite-tsconfig-paths": "^4.0.3",
|
||||||
"vitest": "^0.30.0",
|
"vitest": "^0.31.0"
|
||||||
"webpack": "^5.75.0"
|
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|||||||
1
dashboard/public/assets/grafana.svg
Normal file
1
dashboard/public/assets/grafana.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.3 KiB |
@@ -1,6 +1,6 @@
|
|||||||
import FeedbackForm from '@/components/common/FeedbackForm';
|
import FeedbackForm from '@/components/common/FeedbackForm';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { useInterval } from '@/hooks/useInterval';
|
import { useInterval } from '@/hooks/useInterval';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import { Dropdown } from '@/ui/v2/Dropdown';
|
import { Dropdown } from '@/ui/v2/Dropdown';
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import FeedbackForm from '@/components/common/FeedbackForm';
|
import FeedbackForm from '@/components/common/FeedbackForm';
|
||||||
import Container from '@/components/layout/Container';
|
import Container from '@/components/layout/Container';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCurrentUserOwner';
|
||||||
import { useAppCreatedAt } from '@/hooks/useAppCreatedAt';
|
import { useAppCreatedAt } from '@/hooks/useAppCreatedAt';
|
||||||
import { useCurrentDate } from '@/hooks/useCurrentDate';
|
import { useCurrentDate } from '@/hooks/useCurrentDate';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import type { ApplicationState } from '@/types/application';
|
import type { ApplicationState } from '@/types/application';
|
||||||
import { ApplicationStatus } from '@/types/application';
|
import { ApplicationStatus } from '@/types/application';
|
||||||
import { Modal } from '@/ui/Modal';
|
import { Modal } from '@/ui/Modal';
|
||||||
@@ -56,9 +57,7 @@ export default function ApplicationErrored() {
|
|||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const { currentDate } = useCurrentDate();
|
const { currentDate } = useCurrentDate();
|
||||||
const user = useUserData();
|
const user = useUserData();
|
||||||
const isOwner = currentWorkspace.workspaceMembers.some(
|
const isOwner = useIsCurrentUserOwner();
|
||||||
({ id, type }) => id === user?.id && type === 'owner',
|
|
||||||
);
|
|
||||||
|
|
||||||
const { appCreatedAt } = useAppCreatedAt();
|
const { appCreatedAt } = useAppCreatedAt();
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetAllWorkspacesAndProjectsDocument,
|
GetAllWorkspacesAndProjectsDocument,
|
||||||
useDeleteApplicationMutation,
|
useDeleteApplicationMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import ArrowRightIcon from '@/ui/v2/icons/ArrowRightIcon';
|
|
||||||
import Link from '@/ui/v2/Link';
|
import Link from '@/ui/v2/Link';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
|
import ArrowRightIcon from '@/ui/v2/icons/ArrowRightIcon';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { getApplicationStatusString } from '@/utils/helpers';
|
import { getApplicationStatusString } from '@/utils/helpers';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
@@ -46,6 +46,10 @@ export default function ApplicationInfo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!currentProject) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-4 grid grid-flow-row gap-4">
|
<div className="mt-4 grid grid-flow-row gap-4">
|
||||||
<div className="grid grid-flow-row justify-center gap-0.5">
|
<div className="grid grid-flow-row justify-center gap-0.5">
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ import { ChangePlanModal } from '@/components/applications/ChangePlanModal';
|
|||||||
import { StagingMetadata } from '@/components/applications/StagingMetadata';
|
import { StagingMetadata } from '@/components/applications/StagingMetadata';
|
||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import Container from '@/components/layout/Container';
|
import Container from '@/components/layout/Container';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCurrentUserOwner';
|
||||||
import {
|
import {
|
||||||
GetAllWorkspacesAndProjectsDocument,
|
GetAllWorkspacesAndProjectsDocument,
|
||||||
useGetFreeAndActiveProjectsQuery,
|
useGetFreeAndActiveProjectsQuery,
|
||||||
useUnpauseApplicationMutation,
|
useUnpauseApplicationMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import { Modal } from '@/ui';
|
import { Modal } from '@/ui';
|
||||||
import { Alert } from '@/ui/Alert';
|
import { Alert } from '@/ui/Alert';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
@@ -26,15 +27,11 @@ import { RemoveApplicationModal } from './RemoveApplicationModal';
|
|||||||
|
|
||||||
export default function ApplicationPaused() {
|
export default function ApplicationPaused() {
|
||||||
const { openDialog } = useDialog();
|
const { openDialog } = useDialog();
|
||||||
const {
|
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||||
currentWorkspace,
|
useCurrentWorkspaceAndProject();
|
||||||
currentProject,
|
const isOwner = useIsCurrentUserOwner();
|
||||||
refetch: refetchWorkspaceAndProject,
|
|
||||||
} = useCurrentWorkspaceAndProject();
|
|
||||||
const user = useUserData();
|
const user = useUserData();
|
||||||
const isOwner = currentWorkspace.workspaceMembers.some(
|
|
||||||
({ id, type }) => id === user?.id && type === 'owner',
|
|
||||||
);
|
|
||||||
const [showDeletingModal, setShowDeletingModal] = useState(false);
|
const [showDeletingModal, setShowDeletingModal] = useState(false);
|
||||||
const [unpauseApplication, { loading: changingApplicationStateLoading }] =
|
const [unpauseApplication, { loading: changingApplicationStateLoading }] =
|
||||||
useUnpauseApplicationMutation({
|
useUnpauseApplicationMutation({
|
||||||
@@ -120,20 +117,22 @@ export default function ApplicationPaused() {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box className="grid grid-flow-row gap-2">
|
<Box className="grid grid-flow-row gap-2">
|
||||||
<Button
|
{isOwner && (
|
||||||
className="mx-auto w-full max-w-[280px]"
|
<Button
|
||||||
onClick={() => {
|
className="mx-auto w-full max-w-[280px]"
|
||||||
openDialog({
|
onClick={() => {
|
||||||
component: <ChangePlanModal />,
|
openDialog({
|
||||||
props: {
|
component: <ChangePlanModal />,
|
||||||
PaperProps: { className: 'p-0' },
|
props: {
|
||||||
maxWidth: 'lg',
|
PaperProps: { className: 'p-0' },
|
||||||
},
|
maxWidth: 'lg',
|
||||||
});
|
},
|
||||||
}}
|
});
|
||||||
>
|
}}
|
||||||
Upgrade to Pro
|
>
|
||||||
</Button>
|
Upgrade to Pro
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="grid grid-flow-row gap-2">
|
<div className="grid grid-flow-row gap-2">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Container from '@/components/layout/Container';
|
import Container from '@/components/layout/Container';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { useCheckProvisioning } from '@/hooks/useCheckProvisioning';
|
import { useCheckProvisioning } from '@/hooks/useCheckProvisioning';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import { ApplicationStatus } from '@/types/application';
|
import { ApplicationStatus } from '@/types/application';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
@@ -27,7 +27,7 @@ export default function ApplicationProvisioning() {
|
|||||||
{currentProjectState.state === ApplicationStatus.Empty ? (
|
{currentProjectState.state === ApplicationStatus.Empty ? (
|
||||||
<div className="grid grid-flow-row gap-1">
|
<div className="grid grid-flow-row gap-1">
|
||||||
<Text variant="h3" component="h1">
|
<Text variant="h3" component="h1">
|
||||||
Setting Up {currentProject.name}
|
Setting Up {currentProject?.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text>This normally takes around 2 minutes</Text>
|
<Text>This normally takes around 2 minutes</Text>
|
||||||
<ActivityIndicator className="mx-auto" />
|
<ActivityIndicator className="mx-auto" />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Container from '@/components/layout/Container';
|
import Container from '@/components/layout/Container';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { useCheckProvisioning } from '@/hooks/useCheckProvisioning';
|
import { useCheckProvisioning } from '@/hooks/useCheckProvisioning';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import { ApplicationStatus } from '@/types/application';
|
import { ApplicationStatus } from '@/types/application';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
@@ -26,7 +26,7 @@ export default function ApplicationRestoring() {
|
|||||||
{currentProjectState.state === ApplicationStatus.Empty ? (
|
{currentProjectState.state === ApplicationStatus.Empty ? (
|
||||||
<div className="grid grid-flow-row gap-1">
|
<div className="grid grid-flow-row gap-1">
|
||||||
<Text variant="h3" component="h1">
|
<Text variant="h3" component="h1">
|
||||||
Setting Up {currentProject.name}
|
Setting Up {currentProject?.name}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text>This normally takes around 2 minutes</Text>
|
<Text>This normally takes around 2 minutes</Text>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import FeedbackForm from '@/components/common/FeedbackForm';
|
import FeedbackForm from '@/components/common/FeedbackForm';
|
||||||
import Container from '@/components/layout/Container';
|
import Container from '@/components/layout/Container';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCurrentUserOwner';
|
||||||
import { Modal } from '@/ui/Modal';
|
import { Modal } from '@/ui/Modal';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import { Dropdown } from '@/ui/v2/Dropdown';
|
import { Dropdown } from '@/ui/v2/Dropdown';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
import { useUserData } from '@nhost/nextjs';
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import ApplicationInfo from './ApplicationInfo';
|
import ApplicationInfo from './ApplicationInfo';
|
||||||
@@ -13,12 +13,9 @@ import { RemoveApplicationModal } from './RemoveApplicationModal';
|
|||||||
import { StagingMetadata } from './StagingMetadata';
|
import { StagingMetadata } from './StagingMetadata';
|
||||||
|
|
||||||
export default function ApplicationUnknown() {
|
export default function ApplicationUnknown() {
|
||||||
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
const user = useUserData();
|
const isOwner = useIsCurrentUserOwner();
|
||||||
const isOwner = currentWorkspace.workspaceMembers.some(
|
|
||||||
({ id, type }) => id === user?.id && type === 'owner',
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Container from '@/components/layout/Container';
|
import Container from '@/components/layout/Container';
|
||||||
import useProjectRedirectWhenReady from '@/hooks/common/useProjectRedirectWhenReady';
|
import { useProjectRedirectWhenReady } from '@/features/projects/common/hooks/useProjectRedirectWhenReady';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { AppLoader } from './AppLoader';
|
import { AppLoader } from './AppLoader';
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,30 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { BillingPaymentMethodForm } from '@/components/workspace/BillingPaymentMethodForm';
|
import { BillingPaymentMethodForm } from '@/components/workspace/BillingPaymentMethodForm';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
refetchGetApplicationPlanQuery,
|
refetchGetApplicationPlanQuery,
|
||||||
useGetAppPlanAndGlobalPlansQuery,
|
useGetAppPlanAndGlobalPlansQuery,
|
||||||
useGetPaymentMethodsQuery,
|
useGetPaymentMethodsQuery,
|
||||||
useUpdateApplicationMutation,
|
useUpdateApplicationMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import useApplicationState from '@/hooks/useApplicationState';
|
||||||
|
import { ApplicationStatus } from '@/types/application';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import Checkbox from '@/ui/v2/Checkbox';
|
import Checkbox from '@/ui/v2/Checkbox';
|
||||||
import { BaseDialog } from '@/ui/v2/Dialog';
|
import { BaseDialog } from '@/ui/v2/Dialog';
|
||||||
|
import Link from '@/ui/v2/Link';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
import { planDescriptions } from '@/utils/planDescriptions';
|
import { planDescriptions } from '@/utils/planDescriptions';
|
||||||
import getServerError from '@/utils/settings/getServerError/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
|
|
||||||
function Plan({
|
function Plan({ planName, price, setPlan, planId, selectedPlanId }: any) {
|
||||||
planName,
|
|
||||||
price,
|
|
||||||
setPlan,
|
|
||||||
planId,
|
|
||||||
selectedPlanId,
|
|
||||||
currentPlan,
|
|
||||||
}: any) {
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -49,7 +45,7 @@ function Plan({
|
|||||||
component="p"
|
component="p"
|
||||||
className="self-center text-left font-medium"
|
className="self-center text-left font-medium"
|
||||||
>
|
>
|
||||||
{currentPlan.price > price ? 'Downgrade' : 'Upgrade'} to {planName}
|
Upgrade to {planName}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -59,7 +55,7 @@ function Plan({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Text variant="h3" component="p">
|
<Text variant="h3" component="p">
|
||||||
$ {price}/mo
|
${price}/mo
|
||||||
</Text>
|
</Text>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
@@ -68,12 +64,14 @@ function Plan({
|
|||||||
export function ChangePlanModalWithData({ app, plans, close }: any) {
|
export function ChangePlanModalWithData({ app, plans, close }: any) {
|
||||||
const [selectedPlanId, setSelectedPlanId] = useState('');
|
const [selectedPlanId, setSelectedPlanId] = useState('');
|
||||||
const { closeAlertDialog } = useDialog();
|
const { closeAlertDialog } = useDialog();
|
||||||
|
const [pollingCurrentProject, setPollingCurrentProject] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
currentWorkspace,
|
currentWorkspace,
|
||||||
currentProject,
|
currentProject,
|
||||||
refetch: refetchWorkspaceAndProject,
|
refetch: refetchWorkspaceAndProject,
|
||||||
} = useCurrentWorkspaceAndProject();
|
} = useCurrentWorkspaceAndProject();
|
||||||
|
const { state } = useApplicationState();
|
||||||
|
|
||||||
const { data } = useGetPaymentMethodsQuery({
|
const { data } = useGetPaymentMethodsQuery({
|
||||||
variables: {
|
variables: {
|
||||||
@@ -88,7 +86,28 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
|
|||||||
const currentPlan = plans.find((plan) => plan.id === app.plan.id);
|
const currentPlan = plans.find((plan) => plan.id === app.plan.id);
|
||||||
const selectedPlan = plans.find((plan) => plan.id === selectedPlanId);
|
const selectedPlan = plans.find((plan) => plan.id === selectedPlanId);
|
||||||
|
|
||||||
const isDowngrade = currentPlan.price > selectedPlan?.price;
|
useEffect(() => {
|
||||||
|
if (!pollingCurrentProject || state === ApplicationStatus.Paused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
close?.();
|
||||||
|
closeAlertDialog();
|
||||||
|
setShowPaymentModal(false);
|
||||||
|
setPollingCurrentProject(false);
|
||||||
|
}, [state, pollingCurrentProject, close, closeAlertDialog]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!pollingCurrentProject) {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
refetchWorkspaceAndProject();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [pollingCurrentProject, refetchWorkspaceAndProject, currentProject]);
|
||||||
|
|
||||||
const [updateApp] = useUpdateApplicationMutation({
|
const [updateApp] = useUpdateApplicationMutation({
|
||||||
refetchQueries: [
|
refetchQueries: [
|
||||||
@@ -107,6 +126,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
|
|||||||
appId: app.id,
|
appId: app.id,
|
||||||
app: {
|
app: {
|
||||||
planId: selectedPlan.id,
|
planId: selectedPlan.id,
|
||||||
|
desiredState: 5,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -120,11 +140,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
|
|||||||
getToastStyleProps(),
|
getToastStyleProps(),
|
||||||
);
|
);
|
||||||
|
|
||||||
await refetchWorkspaceAndProject();
|
setPollingCurrentProject(true);
|
||||||
|
|
||||||
close?.();
|
|
||||||
closeAlertDialog();
|
|
||||||
setShowPaymentModal(false);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Note: Error is handled by the toast.
|
// Note: Error is handled by the toast.
|
||||||
}
|
}
|
||||||
@@ -142,12 +158,96 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await handleUpdateAppPlan();
|
await handleUpdateAppPlan();
|
||||||
|
|
||||||
setShowPaymentModal(false);
|
|
||||||
close?.();
|
|
||||||
closeAlertDialog();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (pollingCurrentProject) {
|
||||||
|
return (
|
||||||
|
<Box className="mx-auto w-full max-w-xl rounded-lg p-6 text-left">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="mx-auto">
|
||||||
|
<Image
|
||||||
|
src="/assets/upgrade.svg"
|
||||||
|
alt="Nhost Logo"
|
||||||
|
width={72}
|
||||||
|
height={72}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Text variant="h3" component="h2" className="mt-2 text-center">
|
||||||
|
Successfully upgraded to {currentPlan.name}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<ActivityIndicator
|
||||||
|
label="We are unpausing your project. This may take some time..."
|
||||||
|
className="mx-auto mt-2"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
className="mx-auto mt-4 w-full max-w-sm"
|
||||||
|
onClick={() => {
|
||||||
|
if (close) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
closeAlertDialog();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.plan.id !== plans.find((plan) => plan.isFree)?.id) {
|
||||||
|
return (
|
||||||
|
<Box className="mx-auto w-full max-w-xl rounded-lg p-6 text-left">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="mx-auto">
|
||||||
|
<Image
|
||||||
|
src="/assets/upgrade.svg"
|
||||||
|
alt="Nhost Logo"
|
||||||
|
width={72}
|
||||||
|
height={72}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Text variant="h3" component="h2" className="mt-2 text-center">
|
||||||
|
Downgrade is not available
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text className="mt-1 text-center">
|
||||||
|
You can't downgrade from a paid plan to a free plan here.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text className="text-center">
|
||||||
|
Please contact us at{' '}
|
||||||
|
<Link href="mailto:info@nhost.io">info@nhost.io</Link> if you want
|
||||||
|
to downgrade.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<div className="mt-6 grid grid-flow-row gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
className="mx-auto w-full max-w-sm"
|
||||||
|
onClick={() => {
|
||||||
|
if (close) {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
closeAlertDialog();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="w-full max-w-xl rounded-lg p-6 text-left">
|
<Box className="w-full max-w-xl rounded-lg p-6 text-left">
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
@@ -176,7 +276,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
|
|||||||
You're currently on the <strong>{app.plan.name}</strong> plan.
|
You're currently on the <strong>{app.plan.name}</strong> plan.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<div className="mt-5">
|
<div className="mt-2">
|
||||||
{plans
|
{plans
|
||||||
.filter((plan) => plan.id !== app.plan.id)
|
.filter((plan) => plan.id !== app.plan.id)
|
||||||
.map((plan) => (
|
.map((plan) => (
|
||||||
@@ -194,11 +294,13 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 grid grid-flow-row gap-2">
|
<div className="mt-2 grid grid-flow-row gap-2">
|
||||||
<Button onClick={handleChangePlanClick} disabled={!selectedPlan}>
|
<Button
|
||||||
{!selectedPlan && 'Change Plan'}
|
onClick={handleChangePlanClick}
|
||||||
{selectedPlan && isDowngrade && 'Downgrade'}
|
disabled={!selectedPlan}
|
||||||
{selectedPlan && !isDowngrade && 'Upgrade'}
|
loading={pollingCurrentProject}
|
||||||
|
>
|
||||||
|
Upgrade
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { LoadingScreen } from '@/components/common/LoadingScreen';
|
import { LoadingScreen } from '@/components/common/LoadingScreen';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import IconButton from '@/ui/v2/IconButton';
|
import IconButton from '@/ui/v2/IconButton';
|
||||||
|
import Text from '@/ui/v2/Text';
|
||||||
import ArrowSquareOutIcon from '@/ui/v2/icons/ArrowSquareOutIcon';
|
import ArrowSquareOutIcon from '@/ui/v2/icons/ArrowSquareOutIcon';
|
||||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||||
import Text from '@/ui/v2/Text';
|
|
||||||
import generateAppServiceUrl, {
|
import generateAppServiceUrl, {
|
||||||
defaultLocalBackendSlugs,
|
defaultLocalBackendSlugs,
|
||||||
defaultRemoteBackendSlugs,
|
defaultRemoteBackendSlugs,
|
||||||
@@ -33,7 +33,7 @@ export function HasuraData({ close }: HasuraDataProps) {
|
|||||||
? `${getHasuraConsoleServiceUrl()}`
|
? `${getHasuraConsoleServiceUrl()}`
|
||||||
: generateAppServiceUrl(
|
: generateAppServiceUrl(
|
||||||
currentProject?.subdomain,
|
currentProject?.subdomain,
|
||||||
currentProject?.region.awsName,
|
currentProject?.region,
|
||||||
'hasura',
|
'hasura',
|
||||||
defaultLocalBackendSlugs,
|
defaultLocalBackendSlugs,
|
||||||
{ ...defaultRemoteBackendSlugs, hasura: '/console' },
|
{ ...defaultRemoteBackendSlugs, hasura: '/console' },
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import Checkbox from '@/ui/v2/Checkbox';
|
import Checkbox from '@/ui/v2/Checkbox';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import Checkbox from '@/ui/v2/Checkbox';
|
import Checkbox from '@/ui/v2/Checkbox';
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ChangePlanModal } from '@/components/applications/ChangePlanModal';
|
import { ChangePlanModal } from '@/components/applications/ChangePlanModal';
|
||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCurrentUserOwner';
|
||||||
import { Alert } from '@/ui/Alert';
|
import { Alert } from '@/ui/Alert';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
@@ -20,25 +21,36 @@ export function UnlockFeatureByUpgrading({
|
|||||||
...props
|
...props
|
||||||
}: UnlockFeatureByUpgradingProps) {
|
}: UnlockFeatureByUpgradingProps) {
|
||||||
const { openDialog } = useDialog();
|
const { openDialog } = useDialog();
|
||||||
|
const isOwner = useIsCurrentUserOwner();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={twMerge('flex', className)} {...props}>
|
<div className={twMerge('flex', className)} {...props}>
|
||||||
<Alert className="grid w-full grid-flow-col place-content-between items-center gap-2">
|
<Alert className="grid w-full grid-flow-col place-content-between items-center gap-2">
|
||||||
<Text className="text-left">{message}</Text>
|
<Text className="grid grid-flow-row justify-items-start gap-0.5">
|
||||||
|
<Text component="span">{message}</Text>
|
||||||
|
|
||||||
<Button
|
{!isOwner && (
|
||||||
variant="borderless"
|
<Text component="span" color="secondary" className="text-sm">
|
||||||
onClick={() => {
|
Ask an owner of this workspace to upgrade the project.
|
||||||
openDialog({
|
</Text>
|
||||||
component: <ChangePlanModal />,
|
)}
|
||||||
props: {
|
</Text>
|
||||||
PaperProps: { className: 'p-0 max-w-xl w-full' },
|
|
||||||
},
|
{isOwner && (
|
||||||
});
|
<Button
|
||||||
}}
|
variant="borderless"
|
||||||
>
|
onClick={() => {
|
||||||
Upgrade
|
openDialog({
|
||||||
</Button>
|
component: <ChangePlanModal />,
|
||||||
|
props: {
|
||||||
|
PaperProps: { className: 'p-0 max-w-xl w-full' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Upgrade
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Alert>
|
</Alert>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { ConnectGithubModalState } from '@/components/applications/ConnectGithubModal';
|
import type { ConnectGithubModalState } from '@/components/applications/ConnectGithubModal';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { EditRepositorySettingsModal } from './EditRepositorySettingsModal';
|
import { EditRepositorySettingsModal } from './EditRepositorySettingsModal';
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import type { EditRepositorySettingsFormData } from '@/components/applications/g
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import ErrorBoundaryFallback from '@/components/common/ErrorBoundaryFallback';
|
import ErrorBoundaryFallback from '@/components/common/ErrorBoundaryFallback';
|
||||||
import GithubIcon from '@/components/icons/GithubIcon';
|
import GithubIcon from '@/components/icons/GithubIcon';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { useUpdateApplicationMutation } from '@/generated/graphql';
|
import { useUpdateApplicationMutation } from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
import { discordAnnounce } from '@/utils/discordAnnounce';
|
import { discordAnnounce } from '@/utils/discordAnnounce';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Option from '@/ui/v2/Option';
|
import Option from '@/ui/v2/Option';
|
||||||
import Select from '@/ui/v2/Select';
|
import Select from '@/ui/v2/Select';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import NavLink from '@/components/common/NavLink';
|
import NavLink from '@/components/common/NavLink';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import type { BoxProps } from '@/ui/v2/Box';
|
import type { BoxProps } from '@/ui/v2/Box';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import AudioPreview from '@/components/icons/AudioPreview';
|
|||||||
import { FileIcon } from '@/components/icons/FileIcon';
|
import { FileIcon } from '@/components/icons/FileIcon';
|
||||||
import PDFPreview from '@/components/icons/PDFPreview';
|
import PDFPreview from '@/components/icons/PDFPreview';
|
||||||
import VideoPreview from '@/components/icons/VideoPreview';
|
import VideoPreview from '@/components/icons/VideoPreview';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { useAppClient } from '@/hooks/useAppClient';
|
import { useAppClient } from '@/hooks/useAppClient';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import { Modal } from '@/ui/Modal';
|
import { Modal } from '@/ui/Modal';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ import FormActivityIndicator from '@/components/common/FormActivityIndicator';
|
|||||||
import InlineCode from '@/components/common/InlineCode';
|
import InlineCode from '@/components/common/InlineCode';
|
||||||
import DataBrowserEmptyState from '@/components/dataBrowser/DataBrowserEmptyState';
|
import DataBrowserEmptyState from '@/components/dataBrowser/DataBrowserEmptyState';
|
||||||
import DataBrowserGridControls from '@/components/dataBrowser/DataBrowserGridControls';
|
import DataBrowserGridControls from '@/components/dataBrowser/DataBrowserGridControls';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import useDeleteColumnWithToastMutation from '@/hooks/dataBrowser/useDeleteColumnMutation/useDeleteColumnWithToastMutation';
|
import useDeleteColumnWithToastMutation from '@/hooks/dataBrowser/useDeleteColumnMutation/useDeleteColumnWithToastMutation';
|
||||||
import useTableQuery from '@/hooks/dataBrowser/useTableQuery';
|
import useTableQuery from '@/hooks/dataBrowser/useTableQuery';
|
||||||
import type { UpdateRecordVariables } from '@/hooks/dataBrowser/useUpdateRecordMutation';
|
import type { UpdateRecordVariables } from '@/hooks/dataBrowser/useUpdateRecordMutation';
|
||||||
import useUpdateRecordWithToastMutation from '@/hooks/dataBrowser/useUpdateRecordMutation/useUpdateRecordWithToastMutation';
|
import useUpdateRecordWithToastMutation from '@/hooks/dataBrowser/useUpdateRecordMutation/useUpdateRecordWithToastMutation';
|
||||||
import useTablePath from '@/hooks/useTablePath';
|
import useTablePath from '@/hooks/useTablePath';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import type {
|
import type {
|
||||||
DataBrowserGridColumn,
|
DataBrowserGridColumn,
|
||||||
NormalizedQueryDataRow,
|
NormalizedQueryDataRow,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { DataGridPaginationProps } from '@/components/common/DataGridPagination';
|
import type { DataGridPaginationProps } from '@/components/common/DataGridPagination';
|
||||||
import DataGridPagination from '@/components/common/DataGridPagination';
|
import DataGridPagination from '@/components/common/DataGridPagination';
|
||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import useDeleteRecordMutation from '@/hooks/dataBrowser/useDeleteRecordMutation';
|
import useDeleteRecordMutation from '@/hooks/dataBrowser/useDeleteRecordMutation';
|
||||||
import useDataGridConfig from '@/hooks/useDataGridConfig';
|
import useDataGridConfig from '@/hooks/useDataGridConfig';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import type { DataBrowserGridColumn } from '@/types/dataBrowser';
|
import type { DataBrowserGridColumn } from '@/types/dataBrowser';
|
||||||
import type { BoxProps } from '@/ui/v2/Box';
|
import type { BoxProps } from '@/ui/v2/Box';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import FormActivityIndicator from '@/components/common/FormActivityIndicator';
|
|||||||
import InlineCode from '@/components/common/InlineCode';
|
import InlineCode from '@/components/common/InlineCode';
|
||||||
import NavLink from '@/components/common/NavLink';
|
import NavLink from '@/components/common/NavLink';
|
||||||
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
|
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
||||||
import useDatabaseQuery from '@/hooks/dataBrowser/useDatabaseQuery';
|
import useDatabaseQuery from '@/hooks/dataBrowser/useDatabaseQuery';
|
||||||
import useDeleteTableWithToastMutation from '@/hooks/dataBrowser/useDeleteTableMutation/useDeleteTableWithToastMutation';
|
import useDeleteTableWithToastMutation from '@/hooks/dataBrowser/useDeleteTableMutation/useDeleteTableWithToastMutation';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import FloatingActionButton from '@/ui/FloatingActionButton';
|
import FloatingActionButton from '@/ui/FloatingActionButton';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Backdrop from '@/ui/v2/Backdrop';
|
import Backdrop from '@/ui/v2/Backdrop';
|
||||||
@@ -17,6 +17,12 @@ import Chip from '@/ui/v2/Chip';
|
|||||||
import Divider from '@/ui/v2/Divider';
|
import Divider from '@/ui/v2/Divider';
|
||||||
import { Dropdown } from '@/ui/v2/Dropdown';
|
import { Dropdown } from '@/ui/v2/Dropdown';
|
||||||
import IconButton from '@/ui/v2/IconButton';
|
import IconButton from '@/ui/v2/IconButton';
|
||||||
|
import Link from '@/ui/v2/Link';
|
||||||
|
import List from '@/ui/v2/List';
|
||||||
|
import { ListItem } from '@/ui/v2/ListItem';
|
||||||
|
import Option from '@/ui/v2/Option';
|
||||||
|
import Select from '@/ui/v2/Select';
|
||||||
|
import Text from '@/ui/v2/Text';
|
||||||
import ArrowRightIcon from '@/ui/v2/icons/ArrowRightIcon';
|
import ArrowRightIcon from '@/ui/v2/icons/ArrowRightIcon';
|
||||||
import DotsHorizontalIcon from '@/ui/v2/icons/DotsHorizontalIcon';
|
import DotsHorizontalIcon from '@/ui/v2/icons/DotsHorizontalIcon';
|
||||||
import LockIcon from '@/ui/v2/icons/LockIcon';
|
import LockIcon from '@/ui/v2/icons/LockIcon';
|
||||||
@@ -24,12 +30,6 @@ import PencilIcon from '@/ui/v2/icons/PencilIcon';
|
|||||||
import PlusIcon from '@/ui/v2/icons/PlusIcon';
|
import PlusIcon from '@/ui/v2/icons/PlusIcon';
|
||||||
import TrashIcon from '@/ui/v2/icons/TrashIcon';
|
import TrashIcon from '@/ui/v2/icons/TrashIcon';
|
||||||
import UsersIcon from '@/ui/v2/icons/UsersIcon';
|
import UsersIcon from '@/ui/v2/icons/UsersIcon';
|
||||||
import Link from '@/ui/v2/Link';
|
|
||||||
import List from '@/ui/v2/List';
|
|
||||||
import { ListItem } from '@/ui/v2/ListItem';
|
|
||||||
import Option from '@/ui/v2/Option';
|
|
||||||
import Select from '@/ui/v2/Select';
|
|
||||||
import Text from '@/ui/v2/Text';
|
|
||||||
import { isSchemaLocked } from '@/utils/dataBrowser/schemaHelpers';
|
import { isSchemaLocked } from '@/utils/dataBrowser/schemaHelpers';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import useMetadataQuery from '@/hooks/dataBrowser/useMetadataQuery';
|
import useMetadataQuery from '@/hooks/dataBrowser/useMetadataQuery';
|
||||||
import useTableQuery from '@/hooks/dataBrowser/useTableQuery';
|
import useTableQuery from '@/hooks/dataBrowser/useTableQuery';
|
||||||
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import type { DialogFormProps } from '@/types/common';
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import type {
|
import type {
|
||||||
DatabaseAccessLevel,
|
DatabaseAccessLevel,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import ControlledSelect from '@/components/common/ControlledSelect';
|
import ControlledSelect from '@/components/common/ControlledSelect';
|
||||||
import type { RolePermissionEditorFormValues } from '@/components/dataBrowser/EditPermissionsForm/RolePermissionEditorForm';
|
import type { RolePermissionEditorFormValues } from '@/components/dataBrowser/EditPermissionsForm/RolePermissionEditorForm';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import useTableQuery from '@/hooks/dataBrowser/useTableQuery';
|
import useTableQuery from '@/hooks/dataBrowser/useTableQuery';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Autocomplete from '@/ui/v2/Autocomplete';
|
import Autocomplete from '@/ui/v2/Autocomplete';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import type { Rule, RuleGroup } from '@/types/dataBrowser';
|
import type { Rule, RuleGroup } from '@/types/dataBrowser';
|
||||||
import { Alert } from '@/ui/Alert';
|
import { Alert } from '@/ui/Alert';
|
||||||
import type { BoxProps } from '@/ui/v2/Box';
|
import type { BoxProps } from '@/ui/v2/Box';
|
||||||
@@ -194,7 +194,7 @@ export default function RuleGroupEditor({
|
|||||||
<Link
|
<Link
|
||||||
href={`${generateAppServiceUrl(
|
href={`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region?.awsName,
|
currentProject.region,
|
||||||
'hasura',
|
'hasura',
|
||||||
)}/console/data/default/schema/${schema}/tables/${table}/permissions`}
|
)}/console/data/default/schema/${schema}/tables/${table}/permissions`}
|
||||||
underline="hover"
|
underline="hover"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import ControlledSelect from '@/components/common/ControlledSelect';
|
|||||||
import ReadOnlyToggle from '@/components/common/ReadOnlyToggle';
|
import ReadOnlyToggle from '@/components/common/ReadOnlyToggle';
|
||||||
import type { ColumnAutocompleteProps } from '@/components/dataBrowser/ColumnAutocomplete';
|
import type { ColumnAutocompleteProps } from '@/components/dataBrowser/ColumnAutocomplete';
|
||||||
import ColumnAutocomplete from '@/components/dataBrowser/ColumnAutocomplete';
|
import ColumnAutocomplete from '@/components/dataBrowser/ColumnAutocomplete';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import type { HasuraOperator } from '@/types/dataBrowser';
|
import type { HasuraOperator } from '@/types/dataBrowser';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import type { AutocompleteOption } from '@/ui/v2/Autocomplete';
|
import type { AutocompleteOption } from '@/ui/v2/Autocomplete';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import NavLink from '@/components/common/NavLink';
|
import NavLink from '@/components/common/NavLink';
|
||||||
import AppDeploymentDuration from '@/components/deployments/AppDeploymentDuration';
|
import AppDeploymentDuration from '@/components/deployments/AppDeploymentDuration';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { Avatar } from '@/ui/Avatar';
|
import { Avatar } from '@/ui/Avatar';
|
||||||
import type { DeploymentStatus } from '@/ui/StatusCircle';
|
import type { DeploymentStatus } from '@/ui/StatusCircle';
|
||||||
import { StatusCircle } from '@/ui/StatusCircle';
|
import { StatusCircle } from '@/ui/StatusCircle';
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import DataGridPreviewCell from '@/components/common/DataGridPreviewCell';
|
|||||||
import DataGridTextCell from '@/components/common/DataGridTextCell';
|
import DataGridTextCell from '@/components/common/DataGridTextCell';
|
||||||
import FilesDataGridControls from '@/components/files/FilesDataGridControls';
|
import FilesDataGridControls from '@/components/files/FilesDataGridControls';
|
||||||
import { FileIcon } from '@/components/icons/FileIcon';
|
import { FileIcon } from '@/components/icons/FileIcon';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { useAppClient } from '@/hooks/useAppClient';
|
import { useAppClient } from '@/hooks/useAppClient';
|
||||||
import useBuckets from '@/hooks/useBuckets';
|
import useBuckets from '@/hooks/useBuckets';
|
||||||
import useFiles from '@/hooks/useFiles';
|
import useFiles from '@/hooks/useFiles';
|
||||||
import useFilesAggregate from '@/hooks/useFilesAggregate';
|
import useFilesAggregate from '@/hooks/useFilesAggregate';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import type { Files } from '@/utils/__generated__/graphql';
|
import type { Files } from '@/utils/__generated__/graphql';
|
||||||
import { Order_By as OrderBy } from '@/utils/__generated__/graphql';
|
import { Order_By as OrderBy } from '@/utils/__generated__/graphql';
|
||||||
import { getHasuraAdminSecret } from '@/utils/env';
|
import { getHasuraAdminSecret } from '@/utils/env';
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { DataGridPaginationProps } from '@/components/common/DataGridPagination';
|
import type { DataGridPaginationProps } from '@/components/common/DataGridPagination';
|
||||||
import DataGridPagination from '@/components/common/DataGridPagination';
|
import DataGridPagination from '@/components/common/DataGridPagination';
|
||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { useAppClient } from '@/hooks/useAppClient';
|
import { useAppClient } from '@/hooks/useAppClient';
|
||||||
import useDataGridConfig from '@/hooks/useDataGridConfig';
|
import useDataGridConfig from '@/hooks/useDataGridConfig';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import type { FileUploadButtonProps } from '@/ui/FileUploadButton';
|
import type { FileUploadButtonProps } from '@/ui/FileUploadButton';
|
||||||
import FileUploadButton from '@/ui/FileUploadButton';
|
import FileUploadButton from '@/ui/FileUploadButton';
|
||||||
import type { BoxProps } from '@/ui/v2/Box';
|
import type { BoxProps } from '@/ui/v2/Box';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { useInsertFeedbackOneMutation } from '@/generated/graphql';
|
import { useInsertFeedbackOneMutation } from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import { Avatar } from '@/ui/Avatar';
|
import { Avatar } from '@/ui/Avatar';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import DesktopNav from '@/components/common/DesktopNav';
|
|||||||
import { LoadingScreen } from '@/components/common/LoadingScreen';
|
import { LoadingScreen } from '@/components/common/LoadingScreen';
|
||||||
import type { AuthenticatedLayoutProps } from '@/components/layout/AuthenticatedLayout';
|
import type { AuthenticatedLayoutProps } from '@/components/layout/AuthenticatedLayout';
|
||||||
import AuthenticatedLayout from '@/components/layout/AuthenticatedLayout';
|
import AuthenticatedLayout from '@/components/layout/AuthenticatedLayout';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
||||||
import useProjectRoutes from '@/hooks/common/useProjectRoutes';
|
import useProjectRoutes from '@/hooks/common/useProjectRoutes';
|
||||||
import { useNavigationVisible } from '@/hooks/useNavigationVisible';
|
import { useNavigationVisible } from '@/hooks/useNavigationVisible';
|
||||||
import useNotFoundRedirect from '@/hooks/useNotFoundRedirect';
|
import useNotFoundRedirect from '@/hooks/useNotFoundRedirect';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import type { BoxProps } from '@/ui/v2/Box';
|
import type { BoxProps } from '@/ui/v2/Box';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import { NextSeo } from 'next-seo';
|
import { NextSeo } from 'next-seo';
|
||||||
@@ -103,7 +103,7 @@ function ProjectLayoutContent({
|
|||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
<NextSeo title={currentProject.name} />
|
<NextSeo title={currentProject?.name} />
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import LogsDatePicker from '@/components/logs/LogsDatePicker';
|
import LogsDatePicker from '@/components/logs/LogsDatePicker';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import type { AvailableLogsServices, LogsCustomInterval } from '@/types/logs';
|
import type { AvailableLogsServices, LogsCustomInterval } from '@/types/logs';
|
||||||
import type { BoxProps } from '@/ui/v2/Box';
|
import type { BoxProps } from '@/ui/v2/Box';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import useGitHubModal from '@/components/applications/github/useGitHubModal';
|
|||||||
import DeploymentListItem from '@/components/deployments/DeploymentListItem';
|
import DeploymentListItem from '@/components/deployments/DeploymentListItem';
|
||||||
import GithubIcon from '@/components/icons/GithubIcon';
|
import GithubIcon from '@/components/icons/GithubIcon';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { MetricsCardProps } from '@/components/overview/MetricsCard';
|
import type { MetricsCardProps } from '@/components/overview/MetricsCard';
|
||||||
import { MetricsCard } from '@/components/overview/MetricsCard';
|
import { MetricsCard } from '@/components/overview/MetricsCard';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
import { useGetProjectMetricsQuery } from '@/utils/__generated__/graphql';
|
import { useGetProjectMetricsQuery } from '@/utils/__generated__/graphql';
|
||||||
import { prettifyNumber } from '@/utils/common/prettifyNumber';
|
import { prettifyNumber } from '@/utils/common/prettifyNumber';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import InfoCard from '@/components/overview/InfoCard';
|
import InfoCard from '@/components/overview/InfoCard';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import GithubIcon from '@/components/icons/GithubIcon';
|
import GithubIcon from '@/components/icons/GithubIcon';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { ChangePlanModal } from '@/components/applications/ChangePlanModal';
|
import { ChangePlanModal } from '@/components/applications/ChangePlanModal';
|
||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCurrentUserOwner';
|
||||||
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import Chip from '@/ui/v2/Chip';
|
import Chip from '@/ui/v2/Chip';
|
||||||
import CogIcon from '@/ui/v2/icons/CogIcon';
|
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
|
import CogIcon from '@/ui/v2/icons/CogIcon';
|
||||||
import { formatDistanceToNowStrict, parseISO } from 'date-fns';
|
import { formatDistanceToNowStrict, parseISO } from 'date-fns';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
@@ -14,6 +15,7 @@ import Link from 'next/link';
|
|||||||
export default function OverviewTopBar() {
|
export default function OverviewTopBar() {
|
||||||
const isPlatform = useIsPlatform();
|
const isPlatform = useIsPlatform();
|
||||||
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
|
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
|
||||||
|
const isOwner = useIsCurrentUserOwner();
|
||||||
const isPro = !currentProject?.plan?.isFree;
|
const isPro = !currentProject?.plan?.isFree;
|
||||||
const { openDialog } = useDialog();
|
const { openDialog } = useDialog();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
@@ -87,7 +89,7 @@ export default function OverviewTopBar() {
|
|||||||
color={isPro ? 'primary' : 'default'}
|
color={isPro ? 'primary' : 'default'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!isPro && (
|
{!isPro && isOwner && (
|
||||||
<Button
|
<Button
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
|
import RetryableErrorBoundary from '@/components/common/RetryableErrorBoundary';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
useGetAppFunctionsMetadataQuery,
|
useGetAppFunctionsMetadataQuery,
|
||||||
useGetProjectMetricsQuery,
|
useGetProjectMetricsQuery,
|
||||||
@@ -6,7 +7,6 @@ import {
|
|||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
||||||
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import LinearProgress from '@/ui/v2/LinearProgress';
|
import LinearProgress from '@/ui/v2/LinearProgress';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
import { prettifySize } from '@/utils/common/prettifySize';
|
import { prettifySize } from '@/utils/common/prettifySize';
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
useResetPostgresPasswordMutation,
|
useResetPostgresPasswordMutation,
|
||||||
useUpdateApplicationMutation,
|
useUpdateApplicationMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import InputAdornment from '@/ui/v2/InputAdornment';
|
import InputAdornment from '@/ui/v2/InputAdornment';
|
||||||
|
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { discordAnnounce } from '@/utils/discordAnnounce';
|
import { discordAnnounce } from '@/utils/discordAnnounce';
|
||||||
import generateRandomDatabasePassword from '@/utils/settings/generateRandomDatabasePassword';
|
import generateRandomDatabasePassword from '@/utils/settings/generateRandomDatabasePassword';
|
||||||
@@ -56,7 +56,9 @@ export default function ResetDatabasePasswordSettings() {
|
|||||||
const handleGenerateRandomPassword = () => {
|
const handleGenerateRandomPassword = () => {
|
||||||
const newRandomDatabasePassword = generateRandomDatabasePassword();
|
const newRandomDatabasePassword = generateRandomDatabasePassword();
|
||||||
triggerToast('New random database password generated.');
|
triggerToast('New random database password generated.');
|
||||||
setValue('databasePassword', newRandomDatabasePassword);
|
setValue('databasePassword', newRandomDatabasePassword, {
|
||||||
|
shouldDirty: true,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangeDatabasePassword = async (
|
const handleChangeDatabasePassword = async (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import NavLink from '@/components/common/NavLink';
|
import NavLink from '@/components/common/NavLink';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import FloatingActionButton from '@/ui/FloatingActionButton';
|
import FloatingActionButton from '@/ui/FloatingActionButton';
|
||||||
import Backdrop from '@/ui/v2/Backdrop';
|
import Backdrop from '@/ui/v2/Backdrop';
|
||||||
import type { BoxProps } from '@/ui/v2/Box';
|
import type { BoxProps } from '@/ui/v2/Box';
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
useGetAuthenticationSettingsQuery,
|
useGetAuthenticationSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
useGetAuthenticationSettingsQuery,
|
useGetAuthenticationSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
useGetAuthenticationSettingsQuery,
|
useGetAuthenticationSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
useGetAuthenticationSettingsQuery,
|
useGetAuthenticationSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import ControlledSelect from '@/components/common/ControlledSelect';
|
|||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
useGetAuthenticationSettingsQuery,
|
useGetAuthenticationSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Option from '@/ui/v2/Option';
|
import Option from '@/ui/v2/Option';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
useGetAuthenticationSettingsQuery,
|
useGetAuthenticationSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type {
|
|||||||
import BaseEnvironmentVariableForm, {
|
import BaseEnvironmentVariableForm, {
|
||||||
baseEnvironmentVariableFormValidationSchema,
|
baseEnvironmentVariableFormValidationSchema,
|
||||||
} from '@/components/settings/environmentVariables/BaseEnvironmentVariableForm';
|
} from '@/components/settings/environmentVariables/BaseEnvironmentVariableForm';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import {
|
import {
|
||||||
GetEnvironmentVariablesDocument,
|
GetEnvironmentVariablesDocument,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type {
|
|||||||
import BaseEnvironmentVariableForm, {
|
import BaseEnvironmentVariableForm, {
|
||||||
baseEnvironmentVariableFormValidationSchema,
|
baseEnvironmentVariableFormValidationSchema,
|
||||||
} from '@/components/settings/environmentVariables/BaseEnvironmentVariableForm';
|
} from '@/components/settings/environmentVariables/BaseEnvironmentVariableForm';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import type { EnvironmentVariable } from '@/types/application';
|
import type { EnvironmentVariable } from '@/types/application';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import type { DialogFormProps } from '@/types/common';
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import SettingsContainer from '@/components/settings/SettingsContainer';
|
|||||||
import CreateEnvironmentVariableForm from '@/components/settings/environmentVariables/CreateEnvironmentVariableForm';
|
import CreateEnvironmentVariableForm from '@/components/settings/environmentVariables/CreateEnvironmentVariableForm';
|
||||||
import EditEnvironmentVariableForm from '@/components/settings/environmentVariables/EditEnvironmentVariableForm';
|
import EditEnvironmentVariableForm from '@/components/settings/environmentVariables/EditEnvironmentVariableForm';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import type { EnvironmentVariable } from '@/types/application';
|
import type { EnvironmentVariable } from '@/types/application';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import InlineCode from '@/components/common/InlineCode';
|
|||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import EditJwtSecretForm from '@/components/settings/environmentVariables/EditJwtSecretForm';
|
import EditJwtSecretForm from '@/components/settings/environmentVariables/EditJwtSecretForm';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
import useIsPlatform from '@/hooks/common/useIsPlatform';
|
||||||
import { useAppClient } from '@/hooks/useAppClient';
|
import { useAppClient } from '@/hooks/useAppClient';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
@@ -22,7 +22,6 @@ import generateAppServiceUrl, {
|
|||||||
defaultRemoteBackendSlugs,
|
defaultRemoteBackendSlugs,
|
||||||
} from '@/utils/common/generateAppServiceUrl';
|
} from '@/utils/common/generateAppServiceUrl';
|
||||||
import { getHasuraConsoleServiceUrl } from '@/utils/env';
|
import { getHasuraConsoleServiceUrl } from '@/utils/env';
|
||||||
import { generateRemoteAppUrl } from '@/utils/helpers';
|
|
||||||
import getJwtSecretsWithoutFalsyValues from '@/utils/settings/getJwtSecretsWithoutFalsyValues';
|
import getJwtSecretsWithoutFalsyValues from '@/utils/settings/getJwtSecretsWithoutFalsyValues';
|
||||||
import { Fragment, useState } from 'react';
|
import { Fragment, useState } from 'react';
|
||||||
|
|
||||||
@@ -99,10 +98,6 @@ export default function SystemEnvironmentVariableSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const systemEnvironmentVariables = [
|
const systemEnvironmentVariables = [
|
||||||
{
|
|
||||||
key: 'NHOST_BACKEND_URL',
|
|
||||||
value: generateRemoteAppUrl(currentProject.subdomain),
|
|
||||||
},
|
|
||||||
{ key: 'NHOST_SUBDOMAIN', value: currentProject.subdomain },
|
{ key: 'NHOST_SUBDOMAIN', value: currentProject.subdomain },
|
||||||
{ key: 'NHOST_REGION', value: currentProject.region.awsName },
|
{ key: 'NHOST_REGION', value: currentProject.region.awsName },
|
||||||
{
|
{
|
||||||
@@ -112,7 +107,7 @@ export default function SystemEnvironmentVariableSettings() {
|
|||||||
? `${getHasuraConsoleServiceUrl()}/console`
|
? `${getHasuraConsoleServiceUrl()}/console`
|
||||||
: generateAppServiceUrl(
|
: generateAppServiceUrl(
|
||||||
currentProject?.subdomain,
|
currentProject?.subdomain,
|
||||||
currentProject?.region.awsName,
|
currentProject?.region,
|
||||||
'hasura',
|
'hasura',
|
||||||
defaultLocalBackendSlugs,
|
defaultLocalBackendSlugs,
|
||||||
{ ...defaultRemoteBackendSlugs, hasura: '/console' },
|
{ ...defaultRemoteBackendSlugs, hasura: '/console' },
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import Form from '@/components/common/Form';
|
|||||||
import InlineCode from '@/components/common/InlineCode';
|
import InlineCode from '@/components/common/InlineCode';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetAllWorkspacesAndProjectsDocument,
|
GetAllWorkspacesAndProjectsDocument,
|
||||||
useUpdateApplicationMutation,
|
useUpdateApplicationMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import { Alert } from '@/ui/Alert';
|
import { Alert } from '@/ui/Alert';
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import { discordAnnounce } from '@/utils/discordAnnounce';
|
import { discordAnnounce } from '@/utils/discordAnnounce';
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetAllWorkspacesAndProjectsDocument,
|
GetAllWorkspacesAndProjectsDocument,
|
||||||
useUpdateApplicationMutation,
|
useUpdateApplicationMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import { Alert } from '@/ui/Alert';
|
import { Alert } from '@/ui/Alert';
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import { discordAnnounce } from '@/utils/discordAnnounce';
|
import { discordAnnounce } from '@/utils/discordAnnounce';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type {
|
|||||||
import BasePermissionVariableForm, {
|
import BasePermissionVariableForm, {
|
||||||
basePermissionVariableValidationSchema,
|
basePermissionVariableValidationSchema,
|
||||||
} from '@/components/settings/permissions/BasePermissionVariableForm';
|
} from '@/components/settings/permissions/BasePermissionVariableForm';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import {
|
import {
|
||||||
GetRolesPermissionsDocument,
|
GetRolesPermissionsDocument,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type {
|
|||||||
import BasePermissionVariableForm, {
|
import BasePermissionVariableForm, {
|
||||||
basePermissionVariableValidationSchema,
|
basePermissionVariableValidationSchema,
|
||||||
} from '@/components/settings/permissions/BasePermissionVariableForm';
|
} from '@/components/settings/permissions/BasePermissionVariableForm';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import type { PermissionVariable } from '@/types/application';
|
import type { PermissionVariable } from '@/types/application';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import SettingsContainer from '@/components/settings/SettingsContainer';
|
|||||||
import CreatePermissionVariableForm from '@/components/settings/permissions/CreatePermissionVariableForm';
|
import CreatePermissionVariableForm from '@/components/settings/permissions/CreatePermissionVariableForm';
|
||||||
import EditPermissionVariableForm from '@/components/settings/permissions/EditPermissionVariableForm';
|
import EditPermissionVariableForm from '@/components/settings/permissions/EditPermissionVariableForm';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import type { PermissionVariable } from '@/types/application';
|
import type { PermissionVariable } from '@/types/application';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
import { prettifyMemory } from '@/features/settings/resources/utils/prettifyMemory';
|
import { calculateBillableResources } from '@/features/projects/settings/resources/utils/calculateBillableResources';
|
||||||
import { prettifyVCPU } from '@/features/settings/resources/utils/prettifyVCPU';
|
import { prettifyMemory } from '@/features/projects/settings/resources/utils/prettifyMemory';
|
||||||
import useProPlan from '@/hooks/common/useProPlan';
|
import { prettifyVCPU } from '@/features/projects/settings/resources/utils/prettifyVCPU';
|
||||||
|
import type { ResourceSettingsFormValues } from '@/features/projects/settings/resources/utils/resourceSettingsValidationSchema';
|
||||||
|
import { useProPlan } from '@/hooks/common/useProPlan';
|
||||||
import { Alert } from '@/ui/Alert';
|
import { Alert } from '@/ui/Alert';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
import Divider from '@/ui/v2/Divider';
|
import Divider from '@/ui/v2/Divider';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
|
import Tooltip from '@/ui/v2/Tooltip';
|
||||||
|
import { InfoIcon } from '@/ui/v2/icons/InfoIcon';
|
||||||
import {
|
import {
|
||||||
RESOURCE_VCPU_MULTIPLIER,
|
RESOURCE_VCPU_MULTIPLIER,
|
||||||
RESOURCE_VCPU_PRICE,
|
RESOURCE_VCPU_PRICE,
|
||||||
|
RESOURCE_VCPU_PRICE_PER_MINUTE,
|
||||||
} from '@/utils/CONSTANTS';
|
} from '@/utils/CONSTANTS';
|
||||||
|
|
||||||
export interface ResourcesConfirmationDialogProps {
|
export interface ResourcesConfirmationDialogProps {
|
||||||
/**
|
/**
|
||||||
* Price of the new plan.
|
* The updated resources that the user has selected.
|
||||||
*/
|
*/
|
||||||
updatedResources: {
|
formValues: ResourceSettingsFormValues;
|
||||||
vcpu: number;
|
|
||||||
memory: number;
|
|
||||||
};
|
|
||||||
/**
|
/**
|
||||||
* Function to be called when the user clicks the cancel button.
|
* Function to be called when the user clicks the cancel button.
|
||||||
*/
|
*/
|
||||||
@@ -30,13 +32,47 @@ export interface ResourcesConfirmationDialogProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ResourcesConfirmationDialog({
|
export default function ResourcesConfirmationDialog({
|
||||||
updatedResources,
|
formValues,
|
||||||
onCancel,
|
onCancel,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
}: ResourcesConfirmationDialogProps) {
|
}: ResourcesConfirmationDialogProps) {
|
||||||
const { data: proPlan, loading, error } = useProPlan();
|
const { data: proPlan, loading, error } = useProPlan();
|
||||||
|
|
||||||
|
const priceForTotalAvailableVCPU =
|
||||||
|
(formValues.totalAvailableVCPU / RESOURCE_VCPU_MULTIPLIER) *
|
||||||
|
RESOURCE_VCPU_PRICE;
|
||||||
|
|
||||||
|
const billableResources = calculateBillableResources(
|
||||||
|
{
|
||||||
|
replicas: formValues.database?.replicas,
|
||||||
|
vcpu: formValues.database?.vcpu,
|
||||||
|
memory: formValues.database?.memory,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
replicas: formValues.hasura?.replicas,
|
||||||
|
vcpu: formValues.hasura?.vcpu,
|
||||||
|
memory: formValues.hasura?.memory,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
replicas: formValues.auth?.replicas,
|
||||||
|
vcpu: formValues.auth?.vcpu,
|
||||||
|
memory: formValues.auth?.memory,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
replicas: formValues.storage?.replicas,
|
||||||
|
vcpu: formValues.storage?.vcpu,
|
||||||
|
memory: formValues.storage?.memory,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalBillableVCPU = formValues.enabled ? billableResources.vcpu : 0;
|
||||||
|
const totalBillableMemory = formValues.enabled ? billableResources.memory : 0;
|
||||||
|
|
||||||
const updatedPrice =
|
const updatedPrice =
|
||||||
RESOURCE_VCPU_PRICE * (updatedResources.vcpu / RESOURCE_VCPU_MULTIPLIER);
|
Math.max(
|
||||||
|
priceForTotalAvailableVCPU,
|
||||||
|
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * RESOURCE_VCPU_PRICE,
|
||||||
|
) + proPlan.price;
|
||||||
|
|
||||||
if (!loading && !proPlan) {
|
if (!loading && !proPlan) {
|
||||||
return (
|
return (
|
||||||
@@ -50,9 +86,22 @@ export default function ResourcesConfirmationDialog({
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const databaseResources = `${prettifyVCPU(
|
||||||
|
formValues.database.vcpu,
|
||||||
|
)} vCPU + ${prettifyMemory(formValues.database.memory)}`;
|
||||||
|
const hasuraResources = `${prettifyVCPU(
|
||||||
|
formValues.hasura.vcpu,
|
||||||
|
)} vCPU + ${prettifyMemory(formValues.hasura.memory)}`;
|
||||||
|
const authResources = `${prettifyVCPU(
|
||||||
|
formValues.auth.vcpu,
|
||||||
|
)} vCPU + ${prettifyMemory(formValues.auth.memory)}`;
|
||||||
|
const storageResources = `${prettifyVCPU(
|
||||||
|
formValues.storage.vcpu,
|
||||||
|
)} vCPU + ${prettifyMemory(formValues.storage.memory)}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-flow-row gap-6 px-6 pb-6">
|
<div className="grid grid-flow-row gap-6 px-6 pb-6">
|
||||||
{updatedResources.vcpu > 0 ? (
|
{totalBillableVCPU > 0 ? (
|
||||||
<Text className="text-center">
|
<Text className="text-center">
|
||||||
Please allow some time for the selected resources to take effect.
|
Please allow some time for the selected resources to take effect.
|
||||||
</Text>
|
</Text>
|
||||||
@@ -69,28 +118,96 @@ export default function ResourcesConfirmationDialog({
|
|||||||
<Text>${proPlan.price.toFixed(2)}/mo</Text>
|
<Text>${proPlan.price.toFixed(2)}/mo</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box className="grid grid-flow-col items-center justify-between gap-2">
|
<Box className="grid grid-flow-row gap-1.5">
|
||||||
<Box className="grid grid-flow-row gap-0.5">
|
<Box className="grid grid-flow-col items-center justify-between gap-2">
|
||||||
<Text className="font-medium">Dedicated Resources</Text>
|
<Box className="grid grid-flow-row gap-0.5">
|
||||||
<Text className="text-xs" color="secondary">
|
<Text className="font-medium">Dedicated Resources</Text>
|
||||||
{prettifyVCPU(updatedResources.vcpu)} vCPUs +{' '}
|
</Box>
|
||||||
{prettifyMemory(updatedResources.memory)} of Memory
|
<Text>
|
||||||
|
$
|
||||||
|
{(
|
||||||
|
(totalBillableVCPU / RESOURCE_VCPU_MULTIPLIER) *
|
||||||
|
RESOURCE_VCPU_PRICE_PER_MINUTE
|
||||||
|
).toFixed(4)}
|
||||||
|
/min
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Text>${updatedPrice.toFixed(2)}/mo</Text>
|
|
||||||
|
<Box className="grid w-full grid-flow-row gap-1.5">
|
||||||
|
<Box className="grid grid-flow-col justify-between gap-2">
|
||||||
|
<Text className="text-xs" color="secondary">
|
||||||
|
PostgreSQL Database
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text className="text-xs" color="secondary">
|
||||||
|
{formValues.database.replicas > 1
|
||||||
|
? `${databaseResources} (${formValues.database.replicas} replicas)`
|
||||||
|
: databaseResources}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box className="grid grid-flow-col justify-between gap-2">
|
||||||
|
<Text className="text-xs" color="secondary">
|
||||||
|
Hasura GraphQL
|
||||||
|
</Text>
|
||||||
|
<Text className="text-xs" color="secondary">
|
||||||
|
{formValues.hasura.replicas > 1
|
||||||
|
? `${hasuraResources} (${formValues.hasura.replicas} replicas)`
|
||||||
|
: hasuraResources}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box className="grid grid-flow-col justify-between gap-2">
|
||||||
|
<Text className="text-xs" color="secondary">
|
||||||
|
Auth
|
||||||
|
</Text>
|
||||||
|
<Text className="text-xs" color="secondary">
|
||||||
|
{formValues.auth.replicas > 1
|
||||||
|
? `${authResources} (${formValues.auth.replicas} replicas)`
|
||||||
|
: authResources}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box className="grid grid-flow-col justify-between gap-2">
|
||||||
|
<Text className="text-xs" color="secondary">
|
||||||
|
Storage
|
||||||
|
</Text>
|
||||||
|
<Text className="text-xs" color="secondary">
|
||||||
|
{formValues.storage.replicas > 1
|
||||||
|
? `${storageResources} (${formValues.storage.replicas} replicas)`
|
||||||
|
: storageResources}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box className="grid grid-flow-col justify-between gap-2">
|
||||||
|
<Text className="text-xs font-medium" color="secondary">
|
||||||
|
Total
|
||||||
|
</Text>
|
||||||
|
<Text className="text-xs font-medium" color="secondary">
|
||||||
|
{prettifyVCPU(totalBillableVCPU)} vCPU +{' '}
|
||||||
|
{prettifyMemory(totalBillableMemory)}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<Box className="grid grid-flow-col justify-between gap-2">
|
<Box className="grid grid-flow-col justify-between gap-2">
|
||||||
<Text className="font-medium">Total</Text>
|
<Box className="grid grid-flow-col items-center gap-1.5">
|
||||||
<Text>${(updatedPrice + proPlan.price).toFixed(2)}/mo</Text>
|
<Text className="font-medium">Approximate Cost</Text>
|
||||||
|
|
||||||
|
<Tooltip title="$0.0012/minute for every 1 vCPU and 2 GiB of RAM">
|
||||||
|
<InfoIcon aria-label="Info" className="h-4 w-4" color="primary" />
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Text>${updatedPrice.toFixed(2)}/mo</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box className="grid grid-flow-row gap-2">
|
<Box className="grid grid-flow-row gap-2">
|
||||||
<Button
|
<Button
|
||||||
color={updatedResources.vcpu > 0 ? 'primary' : 'error'}
|
color={totalBillableVCPU > 0 ? 'primary' : 'error'}
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
autoFocus
|
autoFocus
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { mockMatchMediaValue, mockRouter } from '@/tests/mocks';
|
||||||
import {
|
import {
|
||||||
getProPlanOnlyQuery,
|
getProPlanOnlyQuery,
|
||||||
getWorkspaceAndProjectQuery,
|
getWorkspaceAndProjectQuery,
|
||||||
@@ -12,7 +13,6 @@ import {
|
|||||||
fireEvent,
|
fireEvent,
|
||||||
render,
|
render,
|
||||||
screen,
|
screen,
|
||||||
waitFor,
|
|
||||||
waitForElementToBeRemoved,
|
waitForElementToBeRemoved,
|
||||||
within,
|
within,
|
||||||
} from '@/tests/testUtils';
|
} from '@/tests/testUtils';
|
||||||
@@ -22,49 +22,16 @@ import {
|
|||||||
} from '@/utils/CONSTANTS';
|
} from '@/utils/CONSTANTS';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { setupServer } from 'msw/node';
|
import { setupServer } from 'msw/node';
|
||||||
import { test, vi } from 'vitest';
|
import { expect, test, vi } from 'vitest';
|
||||||
import ResourcesForm from './ResourcesForm';
|
import ResourcesForm from './ResourcesForm';
|
||||||
|
|
||||||
Object.defineProperty(window, 'matchMedia', {
|
Object.defineProperty(window, 'matchMedia', {
|
||||||
writable: true,
|
writable: true,
|
||||||
value: vi.fn().mockImplementation((query) => ({
|
value: vi.fn().mockImplementation(mockMatchMediaValue),
|
||||||
matches: false,
|
|
||||||
media: query,
|
|
||||||
onchange: null,
|
|
||||||
addListener: vi.fn(),
|
|
||||||
removeListener: vi.fn(),
|
|
||||||
addEventListener: vi.fn(),
|
|
||||||
removeEventListener: vi.fn(),
|
|
||||||
dispatchEvent: vi.fn(),
|
|
||||||
})),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock('next/router', () => ({
|
vi.mock('next/router', () => ({
|
||||||
useRouter: vi.fn().mockReturnValue({
|
useRouter: vi.fn().mockReturnValue(mockRouter),
|
||||||
basePath: '',
|
|
||||||
pathname: '/test-workspace/test-application',
|
|
||||||
route: '/[workspaceSlug]/[appSlug]',
|
|
||||||
asPath: '/test-workspace/test-application',
|
|
||||||
isLocaleDomain: false,
|
|
||||||
isReady: true,
|
|
||||||
isPreview: false,
|
|
||||||
query: {
|
|
||||||
workspaceSlug: 'test-workspace',
|
|
||||||
appSlug: 'test-application',
|
|
||||||
},
|
|
||||||
push: vi.fn(),
|
|
||||||
replace: vi.fn(),
|
|
||||||
reload: vi.fn(),
|
|
||||||
back: vi.fn(),
|
|
||||||
prefetch: vi.fn(),
|
|
||||||
beforePopState: vi.fn(),
|
|
||||||
events: {
|
|
||||||
on: vi.fn(),
|
|
||||||
off: vi.fn(),
|
|
||||||
emit: vi.fn(),
|
|
||||||
},
|
|
||||||
isFallback: false,
|
|
||||||
}),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const server = setupServer(
|
const server = setupServer(
|
||||||
@@ -79,7 +46,10 @@ beforeAll(() => {
|
|||||||
server.listen();
|
server.listen();
|
||||||
});
|
});
|
||||||
afterEach(() => server.resetHandlers());
|
afterEach(() => server.resetHandlers());
|
||||||
afterAll(() => server.close());
|
afterAll(() => {
|
||||||
|
server.close();
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
// Note: Workaround based on https://github.com/testing-library/user-event/issues/871#issuecomment-1059317998
|
// Note: Workaround based on https://github.com/testing-library/user-event/issues/871#issuecomment-1059317998
|
||||||
function changeSliderValue(slider: HTMLElement, value: number) {
|
function changeSliderValue(slider: HTMLElement, value: number) {
|
||||||
@@ -92,9 +62,7 @@ test('should show an empty state message that the feature must be enabled if no
|
|||||||
|
|
||||||
render(<ResourcesForm />);
|
render(<ResourcesForm />);
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.getByRole('progressbar'));
|
expect(await screen.findByText(/enable this feature/i)).toBeInTheDocument();
|
||||||
|
|
||||||
expect(screen.getByText(/enable this feature/i)).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show the sliders if the switch is enabled', async () => {
|
test('should show the sliders if the switch is enabled', async () => {
|
||||||
@@ -103,28 +71,23 @@ test('should show the sliders if the switch is enabled', async () => {
|
|||||||
|
|
||||||
render(<ResourcesForm />);
|
render(<ResourcesForm />);
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.getByRole('progressbar'));
|
expect(await screen.findByText(/enable this feature/i)).toBeInTheDocument();
|
||||||
|
|
||||||
expect(screen.getByText(/enable this feature/i)).toBeInTheDocument();
|
|
||||||
|
|
||||||
await user.click(screen.getByRole('checkbox'));
|
await user.click(screen.getByRole('checkbox'));
|
||||||
|
|
||||||
expect(screen.queryByText(/enable this feature/i)).not.toBeInTheDocument();
|
expect(screen.queryByText(/enable this feature/i)).not.toBeInTheDocument();
|
||||||
expect(screen.getAllByRole('slider')).toHaveLength(9);
|
expect(screen.getAllByRole('slider')).toHaveLength(12);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not show an empty state message if there is data available', async () => {
|
test('should not show an empty state message if there is data available', async () => {
|
||||||
render(<ResourcesForm />);
|
render(<ResourcesForm />);
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.getByRole('progressbar'));
|
expect(
|
||||||
await waitFor(() =>
|
await screen.findByRole('slider', { name: /total available vcpu/i }),
|
||||||
expect(
|
).toBeInTheDocument();
|
||||||
screen.queryByRole('slider', { name: /total available vcpu/i }),
|
|
||||||
).toBeInTheDocument(),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.queryByText(/enable this feature/i)).not.toBeInTheDocument();
|
expect(screen.queryByText(/enable this feature/i)).not.toBeInTheDocument();
|
||||||
expect(screen.getAllByRole('slider')).toHaveLength(9);
|
expect(screen.getAllByRole('slider')).toHaveLength(12);
|
||||||
expect(screen.getByText(/^vcpus:/i)).toHaveTextContent(/vcpus: 8/i);
|
expect(screen.getByText(/^vcpus:/i)).toHaveTextContent(/vcpus: 8/i);
|
||||||
expect(screen.getByText(/^memory:/i)).toHaveTextContent(/memory: 16384 mib/i);
|
expect(screen.getByText(/^memory:/i)).toHaveTextContent(/memory: 16384 mib/i);
|
||||||
});
|
});
|
||||||
@@ -132,7 +95,9 @@ test('should not show an empty state message if there is data available', async
|
|||||||
test('should show a warning message if not all the resources are allocated', async () => {
|
test('should show a warning message if not all the resources are allocated', async () => {
|
||||||
render(<ResourcesForm />);
|
render(<ResourcesForm />);
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.getByRole('progressbar'));
|
expect(
|
||||||
|
await screen.findByRole('slider', { name: /total available vcpu/i }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
changeSliderValue(
|
changeSliderValue(
|
||||||
screen.getByRole('slider', {
|
screen.getByRole('slider', {
|
||||||
@@ -145,14 +110,16 @@ test('should show a warning message if not all the resources are allocated', asy
|
|||||||
expect(screen.getByText(/^memory:/i)).toHaveTextContent(/memory: 18432 mib/i);
|
expect(screen.getByText(/^memory:/i)).toHaveTextContent(/memory: 18432 mib/i);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
screen.getByText(/you now have 1 vcpus and 2048 mib of memory unused./i),
|
screen.getByText(/you have 1 vcpus and 2048 mib of memory unused./i),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should update the price when the top slider is changed', async () => {
|
test('should update the price when the top slider is changed', async () => {
|
||||||
render(<ResourcesForm />);
|
render(<ResourcesForm />);
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.getByRole('progressbar'));
|
expect(
|
||||||
|
await screen.findByRole('slider', { name: /total available vcpu/i }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
expect(screen.queryByText(/\$200\.00\/mo/i)).not.toBeInTheDocument();
|
expect(screen.queryByText(/\$200\.00\/mo/i)).not.toBeInTheDocument();
|
||||||
|
|
||||||
@@ -172,7 +139,9 @@ test('should show a validation error when the form is submitted when not everyth
|
|||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
render(<ResourcesForm />);
|
render(<ResourcesForm />);
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.getByRole('progressbar'));
|
expect(
|
||||||
|
await screen.findByRole('slider', { name: /total available vcpu/i }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
|
expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
|
||||||
|
|
||||||
@@ -186,8 +155,10 @@ test('should show a validation error when the form is submitted when not everyth
|
|||||||
await user.click(screen.getByRole('button', { name: /save/i }));
|
await user.click(screen.getByRole('button', { name: /save/i }));
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
screen.getAllByText(/you now have 1 vcpus and 2048 mib of memory unused./i),
|
screen.getByText(/you have 1 vcpus and 2048 mib of memory unused./i),
|
||||||
).toHaveLength(2);
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.getByText(/invalid configuration/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show a confirmation dialog when the form is submitted', async () => {
|
test('should show a confirmation dialog when the form is submitted', async () => {
|
||||||
@@ -196,12 +167,9 @@ test('should show a confirmation dialog when the form is submitted', async () =>
|
|||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
render(<ResourcesForm />);
|
render(<ResourcesForm />);
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.getByRole('progressbar'));
|
expect(
|
||||||
await waitFor(() =>
|
await screen.findByRole('slider', { name: /total available vcpu/i }),
|
||||||
expect(
|
).toBeInTheDocument();
|
||||||
screen.queryByRole('slider', { name: /total available vcpu/i }),
|
|
||||||
).toBeInTheDocument(),
|
|
||||||
);
|
|
||||||
|
|
||||||
changeSliderValue(
|
changeSliderValue(
|
||||||
screen.getByRole('slider', {
|
screen.getByRole('slider', {
|
||||||
@@ -212,36 +180,36 @@ test('should show a confirmation dialog when the form is submitted', async () =>
|
|||||||
|
|
||||||
changeSliderValue(
|
changeSliderValue(
|
||||||
screen.getByRole('slider', { name: /database vcpu/i }),
|
screen.getByRole('slider', { name: /database vcpu/i }),
|
||||||
2.25 * RESOURCE_VCPU_MULTIPLIER,
|
2 * RESOURCE_VCPU_MULTIPLIER,
|
||||||
);
|
);
|
||||||
changeSliderValue(
|
changeSliderValue(
|
||||||
screen.getByRole('slider', { name: /hasura graphql vcpu/i }),
|
screen.getByRole('slider', { name: /hasura graphql vcpu/i }),
|
||||||
2.25 * RESOURCE_VCPU_MULTIPLIER,
|
2.5 * RESOURCE_VCPU_MULTIPLIER,
|
||||||
);
|
);
|
||||||
changeSliderValue(
|
changeSliderValue(
|
||||||
screen.getByRole('slider', { name: /auth vcpu/i }),
|
screen.getByRole('slider', { name: /auth vcpu/i }),
|
||||||
2.25 * RESOURCE_VCPU_MULTIPLIER,
|
1.5 * RESOURCE_VCPU_MULTIPLIER,
|
||||||
);
|
);
|
||||||
changeSliderValue(
|
changeSliderValue(
|
||||||
screen.getByRole('slider', { name: /storage vcpu/i }),
|
screen.getByRole('slider', { name: /storage vcpu/i }),
|
||||||
2.25 * RESOURCE_VCPU_MULTIPLIER,
|
3 * RESOURCE_VCPU_MULTIPLIER,
|
||||||
);
|
);
|
||||||
|
|
||||||
changeSliderValue(
|
changeSliderValue(
|
||||||
screen.getByRole('slider', { name: /database memory/i }),
|
screen.getByRole('slider', { name: /database memory/i }),
|
||||||
4.5 * RESOURCE_MEMORY_MULTIPLIER,
|
4.75 * RESOURCE_MEMORY_MULTIPLIER,
|
||||||
);
|
);
|
||||||
changeSliderValue(
|
changeSliderValue(
|
||||||
screen.getByRole('slider', { name: /hasura graphql memory/i }),
|
screen.getByRole('slider', { name: /hasura graphql memory/i }),
|
||||||
4.5 * RESOURCE_MEMORY_MULTIPLIER,
|
4.25 * RESOURCE_MEMORY_MULTIPLIER,
|
||||||
);
|
);
|
||||||
changeSliderValue(
|
changeSliderValue(
|
||||||
screen.getByRole('slider', { name: /auth memory/i }),
|
screen.getByRole('slider', { name: /auth memory/i }),
|
||||||
4.5 * RESOURCE_MEMORY_MULTIPLIER,
|
4 * RESOURCE_MEMORY_MULTIPLIER,
|
||||||
);
|
);
|
||||||
changeSliderValue(
|
changeSliderValue(
|
||||||
screen.getByRole('slider', { name: /storage memory/i }),
|
screen.getByRole('slider', { name: /storage memory/i }),
|
||||||
4.5 * RESOURCE_MEMORY_MULTIPLIER,
|
5 * RESOURCE_MEMORY_MULTIPLIER,
|
||||||
);
|
);
|
||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: /save/i }));
|
await user.click(screen.getByRole('button', { name: /save/i }));
|
||||||
@@ -253,15 +221,21 @@ test('should show a confirmation dialog when the form is submitted', async () =>
|
|||||||
}),
|
}),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
within(screen.getByRole('dialog')).getByText(
|
within(screen.getByRole('dialog')).getByText(/postgresql database/i)
|
||||||
/9 vcpus \+ 18432 mib of memory/i,
|
.parentElement,
|
||||||
{ exact: true },
|
).toHaveTextContent(/2 vcpu \+ 4864 mib/i);
|
||||||
),
|
|
||||||
).toBeInTheDocument();
|
|
||||||
expect(
|
expect(
|
||||||
within(screen.getByRole('dialog')).getByText(/\$475\.00\/mo/i, {
|
within(screen.getByRole('dialog')).getByText(/hasura graphql/i)
|
||||||
exact: true,
|
.parentElement,
|
||||||
}),
|
).toHaveTextContent(/2.5 vcpu \+ 4352 mib/i);
|
||||||
|
expect(
|
||||||
|
within(screen.getByRole('dialog')).getByText(/auth/i).parentElement,
|
||||||
|
).toHaveTextContent(/1.5 vcpu \+ 4096 mib/i);
|
||||||
|
expect(
|
||||||
|
within(screen.getByRole('dialog')).getByText(/storage/i).parentElement,
|
||||||
|
).toHaveTextContent(/3 vcpu \+ 5120 mib/i);
|
||||||
|
expect(
|
||||||
|
within(screen.getByRole('dialog')).getByText(/\$475\.00\/mo/i),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
|
|
||||||
// we need to mock the query again because the mutation updated the resources
|
// we need to mock the query again because the mutation updated the resources
|
||||||
@@ -270,7 +244,8 @@ test('should show a confirmation dialog when the form is submitted', async () =>
|
|||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: /confirm/i }));
|
await user.click(screen.getByRole('button', { name: /confirm/i }));
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.getByRole('dialog'));
|
await waitForElementToBeRemoved(() => screen.queryByRole('dialog'));
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await screen.findByText(/resources have been updated successfully./i),
|
await screen.findByText(/resources have been updated successfully./i),
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
@@ -286,13 +261,15 @@ test('should display a red button when custom resources are disabled', async ()
|
|||||||
|
|
||||||
render(<ResourcesForm />);
|
render(<ResourcesForm />);
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.getByRole('progressbar'));
|
expect(
|
||||||
|
await screen.findByRole('slider', { name: /total available vcpu/i }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
await user.click(screen.getByRole('checkbox'));
|
await user.click(screen.getByRole('checkbox'));
|
||||||
|
|
||||||
expect(screen.getByText(/enable this feature/i)).toBeInTheDocument();
|
expect(screen.getByText(/enable this feature/i)).toBeInTheDocument();
|
||||||
expect(screen.getByText(/total cost:/i)).toHaveTextContent(
|
expect(screen.getByText(/approximate cost:/i)).toHaveTextContent(
|
||||||
/total cost: \$25\.00\/mo/i,
|
/approximate cost: \$25\.00\/mo/i,
|
||||||
);
|
);
|
||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: /save/i }));
|
await user.click(screen.getByRole('button', { name: /save/i }));
|
||||||
@@ -307,14 +284,16 @@ test('should display a red button when custom resources are disabled', async ()
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should hide the footer when custom resource allocation is disabled', async () => {
|
test('should hide the pricing information when custom resource allocation is disabled', async () => {
|
||||||
server.use(updateConfigMutation);
|
server.use(updateConfigMutation);
|
||||||
|
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
|
|
||||||
render(<ResourcesForm />);
|
render(<ResourcesForm />);
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.getByRole('progressbar'));
|
expect(
|
||||||
|
await screen.findByRole('slider', { name: /total available vcpu/i }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
await user.click(screen.getByRole('checkbox'));
|
await user.click(screen.getByRole('checkbox'));
|
||||||
await user.click(screen.getByRole('button', { name: /save/i }));
|
await user.click(screen.getByRole('button', { name: /save/i }));
|
||||||
@@ -325,7 +304,203 @@ test('should hide the footer when custom resource allocation is disabled', async
|
|||||||
|
|
||||||
await user.click(screen.getByRole('button', { name: /confirm/i }));
|
await user.click(screen.getByRole('button', { name: /confirm/i }));
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.getByRole('dialog'));
|
await waitForElementToBeRemoved(() => screen.queryByRole('dialog'));
|
||||||
|
|
||||||
expect(screen.queryByText(/total cost:/i)).not.toBeInTheDocument();
|
expect(screen.queryByText(/approximate cost:/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show a warning message when resources are overallocated', async () => {
|
||||||
|
render(<ResourcesForm />);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByRole('slider', { name: /total available vcpu/i }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', {
|
||||||
|
name: /total available vcpu/i,
|
||||||
|
}),
|
||||||
|
7 * RESOURCE_VCPU_MULTIPLIER,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
/^you have 1 vCPUs and 2048 mib of memory overallocated\. reduce it before saving or increase the total amount\./i,
|
||||||
|
),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should change pricing based on selected replicas', async () => {
|
||||||
|
render(<ResourcesForm />);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByRole('slider', { name: /total available vcpu/i }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(screen.getByText(/approximate cost:/i)).toHaveTextContent(
|
||||||
|
/approximate cost: \$425\.00\/mo/i,
|
||||||
|
);
|
||||||
|
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', { name: /hasura graphql replicas/i }),
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText(/approximate cost:/i)).toHaveTextContent(
|
||||||
|
/approximate cost: \$525\.00\/mo/i,
|
||||||
|
);
|
||||||
|
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', { name: /hasura graphql replicas/i }),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText(/approximate cost:/i)).toHaveTextContent(
|
||||||
|
/approximate cost: \$425\.00\/mo/i,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should validate if vCPU and Memory match the 1:2 ratio if more than 1 replica is selected', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
render(<ResourcesForm />);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByRole('slider', { name: /total available vcpu/i }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', {
|
||||||
|
name: /total available vcpu/i,
|
||||||
|
}),
|
||||||
|
20 * RESOURCE_VCPU_MULTIPLIER,
|
||||||
|
);
|
||||||
|
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', { name: /storage replicas/i }),
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', { name: /storage vcpu/i }),
|
||||||
|
1 * RESOURCE_VCPU_MULTIPLIER,
|
||||||
|
);
|
||||||
|
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', { name: /storage memory/i }),
|
||||||
|
6 * RESOURCE_MEMORY_MULTIPLIER,
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button', { name: /save/i }));
|
||||||
|
|
||||||
|
expect(screen.getByText(/invalid configuration/i)).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
/please check the form for errors and the allocation for each service and try again\./i,
|
||||||
|
),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
const validationErrorMessage = screen.getByLabelText(
|
||||||
|
/vcpu and memory for this service must match the 1:2 ratio if more than one replica is selected\./i,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(validationErrorMessage).toBeInTheDocument();
|
||||||
|
expect(validationErrorMessage).toHaveStyle({ color: '#f13154' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should take replicas into account when confirming the resources', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
render(<ResourcesForm />);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByRole('slider', { name: /total available vcpu/i }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', {
|
||||||
|
name: /total available vcpu/i,
|
||||||
|
}),
|
||||||
|
8.5 * RESOURCE_VCPU_MULTIPLIER,
|
||||||
|
);
|
||||||
|
|
||||||
|
// setting up database
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', { name: /database vcpu/i }),
|
||||||
|
2 * RESOURCE_VCPU_MULTIPLIER,
|
||||||
|
);
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', { name: /database memory/i }),
|
||||||
|
4 * RESOURCE_MEMORY_MULTIPLIER,
|
||||||
|
);
|
||||||
|
|
||||||
|
// setting up hasura
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', { name: /hasura graphql replicas/i }),
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', { name: /hasura graphql vcpu/i }),
|
||||||
|
2.5 * RESOURCE_VCPU_MULTIPLIER,
|
||||||
|
);
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', { name: /hasura graphql memory/i }),
|
||||||
|
5 * RESOURCE_MEMORY_MULTIPLIER,
|
||||||
|
);
|
||||||
|
|
||||||
|
// setting up auth
|
||||||
|
changeSliderValue(screen.getByRole('slider', { name: /auth replicas/i }), 2);
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', { name: /auth vcpu/i }),
|
||||||
|
1.5 * RESOURCE_VCPU_MULTIPLIER,
|
||||||
|
);
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', { name: /auth memory/i }),
|
||||||
|
3 * RESOURCE_MEMORY_MULTIPLIER,
|
||||||
|
);
|
||||||
|
|
||||||
|
// setting up storage
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', { name: /storage replicas/i }),
|
||||||
|
4,
|
||||||
|
);
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', { name: /storage vcpu/i }),
|
||||||
|
2.5 * RESOURCE_VCPU_MULTIPLIER,
|
||||||
|
);
|
||||||
|
changeSliderValue(
|
||||||
|
screen.getByRole('slider', { name: /storage memory/i }),
|
||||||
|
5 * RESOURCE_MEMORY_MULTIPLIER,
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.click(screen.getByRole('button', { name: /save/i }));
|
||||||
|
|
||||||
|
expect(await screen.findByRole('dialog')).toBeInTheDocument();
|
||||||
|
|
||||||
|
const dialog = screen.getByRole('dialog');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
within(dialog).getByText(/postgresql database/i).parentElement,
|
||||||
|
).toHaveTextContent(/2 vcpu \+ 4096 mib/i);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
within(dialog).getByText(/hasura graphql/i).parentElement,
|
||||||
|
).toHaveTextContent(/2\.5 vcpu \+ 5120 mib \(3 replicas\)/i);
|
||||||
|
|
||||||
|
expect(within(dialog).getByText(/auth/i).parentElement).toHaveTextContent(
|
||||||
|
/1\.5 vcpu \+ 3072 mib \(2 replicas\)/i,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(within(dialog).getByText(/storage/i).parentElement).toHaveTextContent(
|
||||||
|
/2\.5 vcpu \+ 5120 mib \(4 replicas\)/i,
|
||||||
|
);
|
||||||
|
|
||||||
|
// total must contain the sum of all resources when replicas are taken into
|
||||||
|
// account
|
||||||
|
expect(within(dialog).getByText(/total/i).parentElement).toHaveTextContent(
|
||||||
|
/22\.5 vcpu \+ 46080 mib/i,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(within(dialog).getByText(/\$0.0270\/min/i)).toBeInTheDocument();
|
||||||
|
expect(within(dialog).getByText(/\$1150\.00\/mo/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,18 +4,15 @@ import SettingsContainer from '@/components/settings/SettingsContainer';
|
|||||||
import ResourcesConfirmationDialog from '@/components/settings/resources/ResourcesConfirmationDialog';
|
import ResourcesConfirmationDialog from '@/components/settings/resources/ResourcesConfirmationDialog';
|
||||||
import ServiceResourcesFormFragment from '@/components/settings/resources/ServiceResourcesFormFragment';
|
import ServiceResourcesFormFragment from '@/components/settings/resources/ServiceResourcesFormFragment';
|
||||||
import TotalResourcesFormFragment from '@/components/settings/resources/TotalResourcesFormFragment';
|
import TotalResourcesFormFragment from '@/components/settings/resources/TotalResourcesFormFragment';
|
||||||
import { prettifyMemory } from '@/features/settings/resources/utils/prettifyMemory';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { prettifyVCPU } from '@/features/settings/resources/utils/prettifyVCPU';
|
import { calculateBillableResources } from '@/features/projects/settings/resources/utils/calculateBillableResources';
|
||||||
import type { ResourceSettingsFormValues } from '@/features/settings/resources/utils/resourceSettingsValidationSchema';
|
import type { ResourceSettingsFormValues } from '@/features/projects/settings/resources/utils/resourceSettingsValidationSchema';
|
||||||
import { resourceSettingsValidationSchema } from '@/features/settings/resources/utils/resourceSettingsValidationSchema';
|
import { resourceSettingsValidationSchema } from '@/features/projects/settings/resources/utils/resourceSettingsValidationSchema';
|
||||||
import useProPlan from '@/hooks/common/useProPlan';
|
import { useProPlan } from '@/hooks/common/useProPlan';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import { Alert } from '@/ui/Alert';
|
import { Alert } from '@/ui/Alert';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Button from '@/ui/v2/Button';
|
|
||||||
import Divider from '@/ui/v2/Divider';
|
import Divider from '@/ui/v2/Divider';
|
||||||
import Text from '@/ui/v2/Text';
|
|
||||||
import {
|
import {
|
||||||
RESOURCE_VCPU_MULTIPLIER,
|
RESOURCE_VCPU_MULTIPLIER,
|
||||||
RESOURCE_VCPU_PRICE,
|
RESOURCE_VCPU_PRICE,
|
||||||
@@ -27,29 +24,27 @@ import {
|
|||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/utils/__generated__/graphql';
|
} from '@/utils/__generated__/graphql';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
import getUnallocatedResources from '@/utils/settings/getUnallocatedResources';
|
|
||||||
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { useState } from 'react';
|
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
import ResourcesFormFooter from './ResourcesFormFooter';
|
||||||
|
|
||||||
function getInitialServiceResources(
|
function getInitialServiceResources(
|
||||||
data: GetResourcesQuery,
|
data: GetResourcesQuery,
|
||||||
service: Exclude<keyof GetResourcesQuery['config'], '__typename'>,
|
service: Exclude<keyof GetResourcesQuery['config'], '__typename'>,
|
||||||
) {
|
) {
|
||||||
const { cpu, memory } = data?.config?.[service]?.resources?.compute || {};
|
const { compute, replicas } = data?.config?.[service]?.resources || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
vcpu: cpu || 0,
|
replicas,
|
||||||
memory: memory || 0,
|
vcpu: compute?.cpu || 0,
|
||||||
|
memory: compute?.memory || 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ResourcesForm() {
|
export default function ResourcesForm() {
|
||||||
const [validationError, setValidationError] = useState<Error | null>(null);
|
|
||||||
|
|
||||||
const { openDialog, closeDialog } = useDialog();
|
const { openDialog, closeDialog } = useDialog();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
|
|
||||||
@@ -95,14 +90,26 @@ export default function ResourcesForm() {
|
|||||||
enabled: totalInitialVCPU > 0 && totalInitialMemory > 0,
|
enabled: totalInitialVCPU > 0 && totalInitialMemory > 0,
|
||||||
totalAvailableVCPU: totalInitialVCPU || 2000,
|
totalAvailableVCPU: totalInitialVCPU || 2000,
|
||||||
totalAvailableMemory: totalInitialMemory || 4096,
|
totalAvailableMemory: totalInitialMemory || 4096,
|
||||||
hasuraVCPU: initialHasuraResources.vcpu || 500,
|
database: {
|
||||||
hasuraMemory: initialHasuraResources.memory || 1536,
|
replicas: initialDatabaseResources.replicas || 1,
|
||||||
databaseVCPU: initialDatabaseResources.vcpu || 1000,
|
vcpu: initialDatabaseResources.vcpu || 1000,
|
||||||
databaseMemory: initialDatabaseResources.memory || 2048,
|
memory: initialDatabaseResources.memory || 2048,
|
||||||
authVCPU: initialAuthResources.vcpu || 250,
|
},
|
||||||
authMemory: initialAuthResources.memory || 256,
|
hasura: {
|
||||||
storageVCPU: initialStorageResources.vcpu || 250,
|
replicas: initialHasuraResources.replicas || 1,
|
||||||
storageMemory: initialStorageResources.memory || 256,
|
vcpu: initialHasuraResources.vcpu || 500,
|
||||||
|
memory: initialHasuraResources.memory || 1536,
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
replicas: initialAuthResources.replicas || 1,
|
||||||
|
vcpu: initialAuthResources.vcpu || 250,
|
||||||
|
memory: initialAuthResources.memory || 256,
|
||||||
|
},
|
||||||
|
storage: {
|
||||||
|
replicas: initialStorageResources.replicas || 1,
|
||||||
|
vcpu: initialStorageResources.vcpu || 250,
|
||||||
|
memory: initialStorageResources.memory || 256,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
resolver: yupResolver(resourceSettingsValidationSchema),
|
resolver: yupResolver(resourceSettingsValidationSchema),
|
||||||
});
|
});
|
||||||
@@ -127,16 +134,32 @@ export default function ResourcesForm() {
|
|||||||
|
|
||||||
const { watch, formState } = form;
|
const { watch, formState } = form;
|
||||||
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
||||||
|
const hasFormErrors = Object.keys(formState.errors).length > 0;
|
||||||
|
|
||||||
const enabled = watch('enabled');
|
const enabled = watch('enabled');
|
||||||
const totalAvailableVCPU = enabled ? watch('totalAvailableVCPU') : 0;
|
|
||||||
|
const billableResources = calculateBillableResources(
|
||||||
|
{
|
||||||
|
replicas: initialDatabaseResources.replicas,
|
||||||
|
vcpu: initialDatabaseResources.vcpu,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
replicas: initialHasuraResources.replicas,
|
||||||
|
vcpu: initialHasuraResources.vcpu,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
replicas: initialAuthResources.replicas,
|
||||||
|
vcpu: initialAuthResources.vcpu,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
replicas: initialStorageResources.replicas,
|
||||||
|
vcpu: initialStorageResources.vcpu,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const initialPrice =
|
const initialPrice =
|
||||||
RESOURCE_VCPU_PRICE * (totalInitialVCPU / RESOURCE_VCPU_MULTIPLIER) +
|
proPlan.price +
|
||||||
proPlan.price;
|
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * RESOURCE_VCPU_PRICE;
|
||||||
const updatedPrice =
|
|
||||||
RESOURCE_VCPU_PRICE * (totalAvailableVCPU / RESOURCE_VCPU_MULTIPLIER) +
|
|
||||||
proPlan.price;
|
|
||||||
|
|
||||||
async function handleSubmit(formValues: ResourceSettingsFormValues) {
|
async function handleSubmit(formValues: ResourceSettingsFormValues) {
|
||||||
const updateConfigPromise = updateConfig({
|
const updateConfigPromise = updateConfig({
|
||||||
@@ -144,46 +167,46 @@ export default function ResourcesForm() {
|
|||||||
appId: currentProject?.id,
|
appId: currentProject?.id,
|
||||||
config: {
|
config: {
|
||||||
postgres: {
|
postgres: {
|
||||||
resources: enabled
|
resources: formValues.enabled
|
||||||
? {
|
? {
|
||||||
compute: {
|
compute: {
|
||||||
cpu: formValues.databaseVCPU,
|
cpu: formValues.database.vcpu,
|
||||||
memory: formValues.databaseMemory,
|
memory: formValues.database.memory,
|
||||||
},
|
},
|
||||||
replicas: 1,
|
replicas: formValues.database.replicas,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
},
|
},
|
||||||
hasura: {
|
hasura: {
|
||||||
resources: enabled
|
resources: formValues.enabled
|
||||||
? {
|
? {
|
||||||
compute: {
|
compute: {
|
||||||
cpu: formValues.hasuraVCPU,
|
cpu: formValues.hasura.vcpu,
|
||||||
memory: formValues.hasuraMemory,
|
memory: formValues.hasura.memory,
|
||||||
},
|
},
|
||||||
replicas: 1,
|
replicas: formValues.hasura.replicas,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
resources: enabled
|
resources: formValues.enabled
|
||||||
? {
|
? {
|
||||||
compute: {
|
compute: {
|
||||||
cpu: formValues.authVCPU,
|
cpu: formValues.auth.vcpu,
|
||||||
memory: formValues.authMemory,
|
memory: formValues.auth.memory,
|
||||||
},
|
},
|
||||||
replicas: 1,
|
replicas: formValues.auth.replicas,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
},
|
},
|
||||||
storage: {
|
storage: {
|
||||||
resources: enabled
|
resources: formValues.enabled
|
||||||
? {
|
? {
|
||||||
compute: {
|
compute: {
|
||||||
cpu: formValues.storageVCPU,
|
cpu: formValues.storage.vcpu,
|
||||||
memory: formValues.storageMemory,
|
memory: formValues.storage.memory,
|
||||||
},
|
},
|
||||||
replicas: 1,
|
replicas: formValues.storage.replicas,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
},
|
},
|
||||||
@@ -209,14 +232,26 @@ export default function ResourcesForm() {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
totalAvailableVCPU: 2000,
|
totalAvailableVCPU: 2000,
|
||||||
totalAvailableMemory: 4096,
|
totalAvailableMemory: 4096,
|
||||||
hasuraVCPU: 500,
|
database: {
|
||||||
hasuraMemory: 1536,
|
replicas: 1,
|
||||||
databaseVCPU: 1000,
|
vcpu: 1000,
|
||||||
databaseMemory: 2048,
|
memory: 2048,
|
||||||
authVCPU: 250,
|
},
|
||||||
authMemory: 256,
|
hasura: {
|
||||||
storageVCPU: 250,
|
replicas: 1,
|
||||||
storageMemory: 256,
|
vcpu: 500,
|
||||||
|
memory: 1536,
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
replicas: 1,
|
||||||
|
vcpu: 250,
|
||||||
|
memory: 256,
|
||||||
|
},
|
||||||
|
storage: {
|
||||||
|
replicas: 1,
|
||||||
|
vcpu: 250,
|
||||||
|
memory: 256,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
form.reset(null, { keepValues: true, keepDirty: false });
|
form.reset(null, { keepValues: true, keepDirty: false });
|
||||||
@@ -227,41 +262,13 @@ export default function ResourcesForm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleConfirm(formValues: ResourceSettingsFormValues) {
|
function handleConfirm(formValues: ResourceSettingsFormValues) {
|
||||||
setValidationError(null);
|
|
||||||
|
|
||||||
const { vcpu: unallocatedVCPU, memory: unallocatedMemory } =
|
|
||||||
getUnallocatedResources(formValues);
|
|
||||||
const hasUnusedResources = unallocatedVCPU > 0 || unallocatedMemory > 0;
|
|
||||||
|
|
||||||
if (hasUnusedResources) {
|
|
||||||
const unusedResourceMessage = [
|
|
||||||
unallocatedVCPU > 0 ? `${prettifyVCPU(unallocatedVCPU)} vCPUs` : '',
|
|
||||||
unallocatedMemory > 0
|
|
||||||
? `${prettifyMemory(unallocatedMemory)} of Memory`
|
|
||||||
: '',
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(' and ');
|
|
||||||
|
|
||||||
setValidationError(
|
|
||||||
new Error(
|
|
||||||
`You now have ${unusedResourceMessage} unused. Allocate it to any of the services before saving.`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
openDialog({
|
openDialog({
|
||||||
title: enabled
|
title: formValues.enabled
|
||||||
? 'Confirm Dedicated Resources'
|
? 'Confirm Dedicated Resources'
|
||||||
: 'Disable Dedicated Resources',
|
: 'Disable Dedicated Resources',
|
||||||
component: (
|
component: (
|
||||||
<ResourcesConfirmationDialog
|
<ResourcesConfirmationDialog
|
||||||
updatedResources={{
|
formValues={formValues}
|
||||||
vcpu: enabled ? formValues.totalAvailableVCPU : 0,
|
|
||||||
memory: enabled ? formValues.totalAvailableMemory : 0,
|
|
||||||
}}
|
|
||||||
onCancel={closeDialog}
|
onCancel={closeDialog}
|
||||||
onSubmit={async () => {
|
onSubmit={async () => {
|
||||||
await handleSubmit(formValues);
|
await handleSubmit(formValues);
|
||||||
@@ -304,47 +311,46 @@ export default function ResourcesForm() {
|
|||||||
<ServiceResourcesFormFragment
|
<ServiceResourcesFormFragment
|
||||||
title="PostgreSQL Database"
|
title="PostgreSQL Database"
|
||||||
description="Manage how much compute you need for the PostgreSQL Database."
|
description="Manage how much compute you need for the PostgreSQL Database."
|
||||||
cpuKey="databaseVCPU"
|
serviceKey="database"
|
||||||
memoryKey="databaseMemory"
|
disableReplicas
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<ServiceResourcesFormFragment
|
<ServiceResourcesFormFragment
|
||||||
title="Hasura GraphQL"
|
title="Hasura GraphQL"
|
||||||
description="Manage how much compute you need for the Hasura GraphQL API."
|
description="Manage how much compute you need for the Hasura GraphQL API."
|
||||||
cpuKey="hasuraVCPU"
|
serviceKey="hasura"
|
||||||
memoryKey="hasuraMemory"
|
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<ServiceResourcesFormFragment
|
<ServiceResourcesFormFragment
|
||||||
title="Auth"
|
title="Auth"
|
||||||
description="Manage how much compute you need for Auth."
|
description="Manage how much compute you need for Auth."
|
||||||
cpuKey="authVCPU"
|
serviceKey="auth"
|
||||||
memoryKey="authMemory"
|
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<ServiceResourcesFormFragment
|
<ServiceResourcesFormFragment
|
||||||
title="Storage"
|
title="Storage"
|
||||||
description="Manage how much compute you need for Storage."
|
description="Manage how much compute you need for Storage."
|
||||||
cpuKey="storageVCPU"
|
serviceKey="storage"
|
||||||
memoryKey="storageMemory"
|
|
||||||
/>
|
/>
|
||||||
{validationError && (
|
|
||||||
|
{hasFormErrors && (
|
||||||
<Box className="px-4 pb-4">
|
<Box className="px-4 pb-4">
|
||||||
<Alert
|
<Alert
|
||||||
severity="error"
|
severity="error"
|
||||||
className="flex flex-col gap-2 text-left"
|
className="flex flex-col gap-2 text-left"
|
||||||
>
|
>
|
||||||
<strong>
|
<strong>Invalid Configuration</strong>
|
||||||
Please use all the available vCPUs and Memory
|
|
||||||
</strong>
|
|
||||||
|
|
||||||
<p>{validationError.message}</p>
|
<p>
|
||||||
|
Please check the form for errors and the allocation for
|
||||||
|
each service and try again.
|
||||||
|
</p>
|
||||||
</Alert>
|
</Alert>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Box className={twMerge('px-4', (enabled || isDirty) && 'pb-4')}>
|
<Box className={twMerge('px-4', 'pb-4')}>
|
||||||
<Alert className="text-left">
|
<Alert className="text-left">
|
||||||
Enable this feature to access custom resource allocation for
|
Enable this feature to access custom resource allocation for
|
||||||
your services.
|
your services.
|
||||||
@@ -352,29 +358,7 @@ export default function ResourcesForm() {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(enabled || isDirty) && (
|
<ResourcesFormFooter />
|
||||||
<Box className="flex flex-row items-center justify-between border-t px-4 pt-4">
|
|
||||||
<span />
|
|
||||||
|
|
||||||
<Box className="flex flex-row items-center gap-4">
|
|
||||||
<Text>
|
|
||||||
Total cost:{' '}
|
|
||||||
<span className="font-medium">
|
|
||||||
${updatedPrice.toFixed(2)}/mo
|
|
||||||
</span>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant={isDirty ? 'contained' : 'outlined'}
|
|
||||||
color={isDirty ? 'primary' : 'secondary'}
|
|
||||||
disabled={!isDirty}
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
</Form>
|
</Form>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import { calculateBillableResources } from '@/features/projects/settings/resources/utils/calculateBillableResources';
|
||||||
|
import type { ResourceSettingsFormValues } from '@/features/projects/settings/resources/utils/resourceSettingsValidationSchema';
|
||||||
|
import { useProPlan } from '@/hooks/common/useProPlan';
|
||||||
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
|
import Box from '@/ui/v2/Box';
|
||||||
|
import Button from '@/ui/v2/Button';
|
||||||
|
import Link from '@/ui/v2/Link';
|
||||||
|
import Text from '@/ui/v2/Text';
|
||||||
|
import Tooltip from '@/ui/v2/Tooltip';
|
||||||
|
import ArrowSquareOutIcon from '@/ui/v2/icons/ArrowSquareOutIcon';
|
||||||
|
import { InfoIcon } from '@/ui/v2/icons/InfoIcon';
|
||||||
|
import {
|
||||||
|
RESOURCE_VCPU_MULTIPLIER,
|
||||||
|
RESOURCE_VCPU_PRICE,
|
||||||
|
} from '@/utils/CONSTANTS';
|
||||||
|
import { useFormState, useWatch } from 'react-hook-form';
|
||||||
|
|
||||||
|
export default function ResourcesFormFooter() {
|
||||||
|
const {
|
||||||
|
data: proPlan,
|
||||||
|
loading: proPlanLoading,
|
||||||
|
error: proPlanError,
|
||||||
|
} = useProPlan();
|
||||||
|
|
||||||
|
const formState = useFormState<ResourceSettingsFormValues>();
|
||||||
|
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
||||||
|
|
||||||
|
const enabled = useWatch<ResourceSettingsFormValues>({ name: 'enabled' });
|
||||||
|
const [totalAvailableVCPU, database, hasura, auth, storage] = useWatch<
|
||||||
|
ResourceSettingsFormValues,
|
||||||
|
['totalAvailableVCPU', 'database', 'hasura', 'auth', 'storage']
|
||||||
|
>({
|
||||||
|
name: ['totalAvailableVCPU', 'database', 'hasura', 'auth', 'storage'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (proPlanLoading) {
|
||||||
|
return <ActivityIndicator label="Loading plan details..." delay={1000} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proPlanError) {
|
||||||
|
throw proPlanError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const priceForTotalAvailableVCPU =
|
||||||
|
(totalAvailableVCPU / RESOURCE_VCPU_MULTIPLIER) * RESOURCE_VCPU_PRICE;
|
||||||
|
|
||||||
|
const billableResources = calculateBillableResources(
|
||||||
|
{
|
||||||
|
replicas: database?.replicas,
|
||||||
|
vcpu: database?.vcpu,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
replicas: hasura?.replicas,
|
||||||
|
vcpu: hasura?.vcpu,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
replicas: auth?.replicas,
|
||||||
|
vcpu: auth?.vcpu,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
replicas: storage?.replicas,
|
||||||
|
vcpu: storage?.vcpu,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedPrice = enabled
|
||||||
|
? Math.max(
|
||||||
|
priceForTotalAvailableVCPU,
|
||||||
|
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) *
|
||||||
|
RESOURCE_VCPU_PRICE,
|
||||||
|
) + proPlan.price
|
||||||
|
: proPlan.price;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
className="grid items-center gap-4 border-t px-4 pt-4 lg:grid-flow-col lg:justify-between lg:gap-2"
|
||||||
|
component="footer"
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
Learn more about{' '}
|
||||||
|
<Link
|
||||||
|
href="https://docs.nhost.io/platform/compute"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
underline="hover"
|
||||||
|
className="font-medium"
|
||||||
|
>
|
||||||
|
Compute Resources
|
||||||
|
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{(enabled || isDirty) && (
|
||||||
|
<Box className="grid grid-flow-col items-center justify-between gap-4">
|
||||||
|
<Box className="grid grid-flow-col items-center gap-1.5">
|
||||||
|
<Text>
|
||||||
|
Approximate cost:{' '}
|
||||||
|
<span className="font-medium">${updatedPrice.toFixed(2)}/mo</span>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Tooltip title="$0.0012/minute for every 1 vCPU and 2 GiB of RAM">
|
||||||
|
<InfoIcon aria-label="Info" className="h-4 w-4" color="primary" />
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant={isDirty ? 'contained' : 'outlined'}
|
||||||
|
color={isDirty ? 'primary' : 'secondary'}
|
||||||
|
disabled={!isDirty}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,15 +1,19 @@
|
|||||||
import { prettifyMemory } from '@/features/settings/resources/utils/prettifyMemory';
|
import { prettifyMemory } from '@/features/projects/settings/resources/utils/prettifyMemory';
|
||||||
import { prettifyVCPU } from '@/features/settings/resources/utils/prettifyVCPU';
|
import { prettifyVCPU } from '@/features/projects/settings/resources/utils/prettifyVCPU';
|
||||||
import type { ResourceSettingsFormValues } from '@/features/settings/resources/utils/resourceSettingsValidationSchema';
|
import type { ResourceSettingsFormValues } from '@/features/projects/settings/resources/utils/resourceSettingsValidationSchema';
|
||||||
import {
|
import {
|
||||||
MAX_SERVICE_MEMORY,
|
MAX_SERVICE_MEMORY,
|
||||||
|
MAX_SERVICE_REPLICAS,
|
||||||
MAX_SERVICE_VCPU,
|
MAX_SERVICE_VCPU,
|
||||||
MIN_SERVICE_MEMORY,
|
MIN_SERVICE_MEMORY,
|
||||||
|
MIN_SERVICE_REPLICAS,
|
||||||
MIN_SERVICE_VCPU,
|
MIN_SERVICE_VCPU,
|
||||||
} from '@/features/settings/resources/utils/resourceSettingsValidationSchema';
|
} from '@/features/projects/settings/resources/utils/resourceSettingsValidationSchema';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Slider from '@/ui/v2/Slider';
|
import Slider from '@/ui/v2/Slider';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
|
import Tooltip from '@/ui/v2/Tooltip';
|
||||||
|
import { ExclamationIcon } from '@/ui/v2/icons/ExclamationIcon';
|
||||||
import { RESOURCE_MEMORY_STEP, RESOURCE_VCPU_STEP } from '@/utils/CONSTANTS';
|
import { RESOURCE_MEMORY_STEP, RESOURCE_VCPU_STEP } from '@/utils/CONSTANTS';
|
||||||
import { useFormContext, useWatch } from 'react-hook-form';
|
import { useFormContext, useWatch } from 'react-hook-form';
|
||||||
|
|
||||||
@@ -23,79 +27,98 @@ export interface ServiceResourcesFormFragmentProps {
|
|||||||
*/
|
*/
|
||||||
description: string;
|
description: string;
|
||||||
/**
|
/**
|
||||||
* Form field name for CPU.
|
* Form field name for service.
|
||||||
*/
|
*/
|
||||||
cpuKey: Exclude<
|
serviceKey: Exclude<
|
||||||
keyof ResourceSettingsFormValues,
|
keyof ResourceSettingsFormValues,
|
||||||
'enabled' | 'totalAvailableVCPU' | 'totalAvailableMemory'
|
'enabled' | 'totalAvailableVCPU' | 'totalAvailableMemory'
|
||||||
>;
|
>;
|
||||||
/**
|
/**
|
||||||
* Form field name for Memory.
|
* Whether to disable the replicas field.
|
||||||
*/
|
*/
|
||||||
memoryKey: Exclude<
|
disableReplicas?: boolean;
|
||||||
keyof ResourceSettingsFormValues,
|
|
||||||
'enabled' | 'totalAvailableVCPU' | 'totalAvailableMemory'
|
|
||||||
>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ServiceResourcesFormFragment({
|
export default function ServiceResourcesFormFragment({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
cpuKey,
|
serviceKey,
|
||||||
memoryKey,
|
disableReplicas = false,
|
||||||
}: ServiceResourcesFormFragmentProps) {
|
}: ServiceResourcesFormFragmentProps) {
|
||||||
const { setValue } = useFormContext<ResourceSettingsFormValues>();
|
const {
|
||||||
|
setValue,
|
||||||
|
trigger: triggerValidation,
|
||||||
|
formState,
|
||||||
|
} = useFormContext<ResourceSettingsFormValues>();
|
||||||
const formValues = useWatch<ResourceSettingsFormValues>();
|
const formValues = useWatch<ResourceSettingsFormValues>();
|
||||||
|
const serviceValues = formValues[serviceKey];
|
||||||
|
|
||||||
// Total allocated CPU for all resources
|
// Total allocated CPU for all resources
|
||||||
const totalAllocatedCPU = Object.keys(formValues)
|
const totalAllocatedVCPU = Object.keys(formValues)
|
||||||
.filter((key) => key.endsWith('CPU') && key !== 'totalAvailableVCPU')
|
.filter(
|
||||||
.reduce((acc, key) => acc + formValues[key], 0);
|
(key) =>
|
||||||
|
!['enabled', 'totalAvailableVCPU', 'totalAvailableMemory'].includes(
|
||||||
|
key,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.reduce((acc, key) => acc + formValues[key].vcpu, 0);
|
||||||
|
|
||||||
// Total allocated memory for all resources
|
// Total allocated memory for all resources
|
||||||
const totalAllocatedMemory = Object.keys(formValues)
|
const totalAllocatedMemory = Object.keys(formValues)
|
||||||
.filter((key) => key.endsWith('Memory') && key !== 'totalAvailableMemory')
|
.filter(
|
||||||
.reduce((acc, key) => acc + formValues[key], 0);
|
(key) =>
|
||||||
|
!['enabled', 'totalAvailableVCPU', 'totalAvailableMemory'].includes(
|
||||||
|
key,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.reduce((acc, key) => acc + formValues[key].memory, 0);
|
||||||
|
|
||||||
const remainingCPU = formValues.totalAvailableVCPU - totalAllocatedCPU;
|
const remainingVCPU = formValues.totalAvailableVCPU - totalAllocatedVCPU;
|
||||||
const allowedCPU = remainingCPU + formValues[cpuKey];
|
const allowedVCPU = remainingVCPU + serviceValues.vcpu;
|
||||||
|
|
||||||
const remainingMemory =
|
const remainingMemory =
|
||||||
formValues.totalAvailableMemory - totalAllocatedMemory;
|
formValues.totalAvailableMemory - totalAllocatedMemory;
|
||||||
const allowedMemory = remainingMemory + formValues[memoryKey];
|
const allowedMemory = remainingMemory + serviceValues.memory;
|
||||||
|
|
||||||
function handleCPUChange(value: string) {
|
function handleReplicaChange(value: string) {
|
||||||
const updatedCPU = parseFloat(value);
|
const updatedReplicas = parseInt(value, 10);
|
||||||
const exceedsAvailableCPU =
|
|
||||||
updatedCPU + (totalAllocatedCPU - formValues[cpuKey]) >
|
|
||||||
formValues.totalAvailableVCPU;
|
|
||||||
|
|
||||||
if (
|
if (updatedReplicas < MIN_SERVICE_REPLICAS) {
|
||||||
Number.isNaN(updatedCPU) ||
|
|
||||||
exceedsAvailableCPU ||
|
|
||||||
updatedCPU < MIN_SERVICE_VCPU
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue(cpuKey, updatedCPU, { shouldDirty: true });
|
setValue(`${serviceKey}.replicas`, updatedReplicas, { shouldDirty: true });
|
||||||
|
triggerValidation(`${serviceKey}.replicas`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleVCPUChange(value: string) {
|
||||||
|
const updatedVCPU = parseFloat(value);
|
||||||
|
|
||||||
|
if (Number.isNaN(updatedVCPU) || updatedVCPU < MIN_SERVICE_VCPU) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(`${serviceKey}.vcpu`, updatedVCPU, { shouldDirty: true });
|
||||||
|
|
||||||
|
// trigger validation for "replicas" field
|
||||||
|
if (!disableReplicas) {
|
||||||
|
triggerValidation(`${serviceKey}.replicas`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMemoryChange(value: string) {
|
function handleMemoryChange(value: string) {
|
||||||
const updatedMemory = parseFloat(value);
|
const updatedMemory = parseFloat(value);
|
||||||
const exceedsAvailableMemory =
|
|
||||||
updatedMemory + (totalAllocatedMemory - formValues[memoryKey]) >
|
|
||||||
formValues.totalAvailableMemory;
|
|
||||||
|
|
||||||
if (
|
if (Number.isNaN(updatedMemory) || updatedMemory < MIN_SERVICE_MEMORY) {
|
||||||
Number.isNaN(updatedMemory) ||
|
|
||||||
exceedsAvailableMemory ||
|
|
||||||
updatedMemory < MIN_SERVICE_MEMORY
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue(memoryKey, updatedMemory, { shouldDirty: true });
|
setValue(`${serviceKey}.memory`, updatedMemory, { shouldDirty: true });
|
||||||
|
|
||||||
|
// trigger validation for "replicas" field
|
||||||
|
if (!disableReplicas) {
|
||||||
|
triggerValidation(`${serviceKey}.replicas`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -113,14 +136,14 @@ export default function ServiceResourcesFormFragment({
|
|||||||
<Text>
|
<Text>
|
||||||
Allocated vCPUs:{' '}
|
Allocated vCPUs:{' '}
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{prettifyVCPU(formValues[cpuKey])}
|
{prettifyVCPU(serviceValues.vcpu)}
|
||||||
</span>
|
</span>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{remainingCPU > 0 && formValues[cpuKey] < MAX_SERVICE_VCPU && (
|
{remainingVCPU > 0 && serviceValues.vcpu < MAX_SERVICE_VCPU && (
|
||||||
<Text className="text-sm">
|
<Text className="text-sm">
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{prettifyVCPU(remainingCPU)} vCPUs
|
{prettifyVCPU(remainingVCPU)} vCPUs
|
||||||
</span>{' '}
|
</span>{' '}
|
||||||
remaining
|
remaining
|
||||||
</Text>
|
</Text>
|
||||||
@@ -128,11 +151,11 @@ export default function ServiceResourcesFormFragment({
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Slider
|
<Slider
|
||||||
value={formValues[cpuKey]}
|
value={serviceValues.vcpu}
|
||||||
onChange={(_event, value) => handleCPUChange(value.toString())}
|
onChange={(_event, value) => handleVCPUChange(value.toString())}
|
||||||
max={MAX_SERVICE_VCPU}
|
max={MAX_SERVICE_VCPU}
|
||||||
step={RESOURCE_VCPU_STEP}
|
step={RESOURCE_VCPU_STEP}
|
||||||
allowed={allowedCPU}
|
allowed={allowedVCPU}
|
||||||
aria-label={`${title} vCPU`}
|
aria-label={`${title} vCPU`}
|
||||||
marks
|
marks
|
||||||
/>
|
/>
|
||||||
@@ -143,11 +166,11 @@ export default function ServiceResourcesFormFragment({
|
|||||||
<Text>
|
<Text>
|
||||||
Allocated Memory:{' '}
|
Allocated Memory:{' '}
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{prettifyMemory(formValues[memoryKey])}
|
{prettifyMemory(serviceValues.memory)}
|
||||||
</span>
|
</span>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{remainingMemory > 0 && formValues[memoryKey] < MAX_SERVICE_MEMORY && (
|
{remainingMemory > 0 && serviceValues.memory < MAX_SERVICE_MEMORY && (
|
||||||
<Text className="text-sm">
|
<Text className="text-sm">
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{prettifyMemory(remainingMemory)} of Memory
|
{prettifyMemory(remainingMemory)} of Memory
|
||||||
@@ -158,7 +181,7 @@ export default function ServiceResourcesFormFragment({
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Slider
|
<Slider
|
||||||
value={formValues[memoryKey]}
|
value={serviceValues.memory}
|
||||||
onChange={(_event, value) => handleMemoryChange(value.toString())}
|
onChange={(_event, value) => handleMemoryChange(value.toString())}
|
||||||
max={MAX_SERVICE_MEMORY}
|
max={MAX_SERVICE_MEMORY}
|
||||||
step={RESOURCE_MEMORY_STEP}
|
step={RESOURCE_MEMORY_STEP}
|
||||||
@@ -167,6 +190,47 @@ export default function ServiceResourcesFormFragment({
|
|||||||
marks
|
marks
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{!disableReplicas && (
|
||||||
|
<Box className="grid grid-flow-row gap-2">
|
||||||
|
<Box className="grid grid-flow-col items-center justify-start gap-2">
|
||||||
|
<Text
|
||||||
|
color={
|
||||||
|
formState.errors?.[serviceKey]?.replicas?.message
|
||||||
|
? 'error'
|
||||||
|
: 'primary'
|
||||||
|
}
|
||||||
|
aria-errormessage={`${serviceKey}-replicas-error-tooltip`}
|
||||||
|
>
|
||||||
|
Replicas:{' '}
|
||||||
|
<span className="font-medium">{serviceValues.replicas}</span>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{formState.errors?.[serviceKey]?.replicas?.message ? (
|
||||||
|
<Tooltip
|
||||||
|
title={formState.errors[serviceKey].replicas.message}
|
||||||
|
id={`${serviceKey}-replicas-error-tooltip`}
|
||||||
|
>
|
||||||
|
<ExclamationIcon
|
||||||
|
color="error"
|
||||||
|
className="h-4 w-4"
|
||||||
|
aria-hidden="false"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Slider
|
||||||
|
value={serviceValues.replicas}
|
||||||
|
onChange={(_event, value) => handleReplicaChange(value.toString())}
|
||||||
|
min={0}
|
||||||
|
max={MAX_SERVICE_REPLICAS}
|
||||||
|
step={1}
|
||||||
|
aria-label={`${title} Replicas`}
|
||||||
|
marks
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { prettifyMemory } from '@/features/settings/resources/utils/prettifyMemory';
|
import { calculateBillableResources } from '@/features/projects/settings/resources/utils/calculateBillableResources';
|
||||||
import { prettifyVCPU } from '@/features/settings/resources/utils/prettifyVCPU';
|
import { getAllocatedResources } from '@/features/projects/settings/resources/utils/getAllocatedResources';
|
||||||
import type { ResourceSettingsFormValues } from '@/features/settings/resources/utils/resourceSettingsValidationSchema';
|
import { prettifyMemory } from '@/features/projects/settings/resources/utils/prettifyMemory';
|
||||||
|
import { prettifyVCPU } from '@/features/projects/settings/resources/utils/prettifyVCPU';
|
||||||
|
import type { ResourceSettingsFormValues } from '@/features/projects/settings/resources/utils/resourceSettingsValidationSchema';
|
||||||
import {
|
import {
|
||||||
MAX_TOTAL_VCPU,
|
MAX_TOTAL_VCPU,
|
||||||
MIN_TOTAL_MEMORY,
|
|
||||||
MIN_TOTAL_VCPU,
|
MIN_TOTAL_VCPU,
|
||||||
} from '@/features/settings/resources/utils/resourceSettingsValidationSchema';
|
} from '@/features/projects/settings/resources/utils/resourceSettingsValidationSchema';
|
||||||
import useProPlan from '@/hooks/common/useProPlan';
|
import { useProPlan } from '@/hooks/common/useProPlan';
|
||||||
import { Alert } from '@/ui/Alert';
|
import { Alert } from '@/ui/Alert';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
import Slider, { sliderClasses } from '@/ui/v2/Slider';
|
import Slider, { sliderClasses } from '@/ui/v2/Slider';
|
||||||
@@ -19,7 +20,6 @@ import {
|
|||||||
RESOURCE_VCPU_PRICE,
|
RESOURCE_VCPU_PRICE,
|
||||||
RESOURCE_VCPU_STEP,
|
RESOURCE_VCPU_STEP,
|
||||||
} from '@/utils/CONSTANTS';
|
} from '@/utils/CONSTANTS';
|
||||||
import getUnallocatedResources from '@/utils/settings/getUnallocatedResources';
|
|
||||||
import { alpha, styled } from '@mui/material';
|
import { alpha, styled } from '@mui/material';
|
||||||
import { useFormContext, useWatch } from 'react-hook-form';
|
import { useFormContext, useWatch } from 'react-hook-form';
|
||||||
|
|
||||||
@@ -59,51 +59,72 @@ export default function TotalResourcesFormFragment({
|
|||||||
throw proPlanError;
|
throw proPlanError;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allocatedCPU =
|
const priceForTotalAvailableVCPU =
|
||||||
formValues.databaseVCPU +
|
(formValues.totalAvailableVCPU / RESOURCE_VCPU_MULTIPLIER) *
|
||||||
formValues.hasuraVCPU +
|
RESOURCE_VCPU_PRICE;
|
||||||
formValues.authVCPU +
|
|
||||||
formValues.storageVCPU;
|
const billableResources = calculateBillableResources(
|
||||||
const allocatedMemory =
|
{
|
||||||
formValues.databaseMemory +
|
replicas: formValues.database?.replicas,
|
||||||
formValues.hasuraMemory +
|
vcpu: formValues.database?.vcpu,
|
||||||
formValues.authMemory +
|
memory: formValues.database?.memory,
|
||||||
formValues.storageMemory;
|
},
|
||||||
|
{
|
||||||
|
replicas: formValues.hasura?.replicas,
|
||||||
|
vcpu: formValues.hasura?.vcpu,
|
||||||
|
memory: formValues.hasura?.memory,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
replicas: formValues.auth?.replicas,
|
||||||
|
vcpu: formValues.auth?.vcpu,
|
||||||
|
memory: formValues.auth?.memory,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
replicas: formValues.storage?.replicas,
|
||||||
|
vcpu: formValues.storage?.vcpu,
|
||||||
|
memory: formValues.storage?.memory,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const updatedPrice =
|
const updatedPrice =
|
||||||
RESOURCE_VCPU_PRICE *
|
Math.max(
|
||||||
(formValues.totalAvailableVCPU / RESOURCE_VCPU_MULTIPLIER) +
|
priceForTotalAvailableVCPU,
|
||||||
proPlan.price;
|
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * RESOURCE_VCPU_PRICE,
|
||||||
|
) + proPlan.price;
|
||||||
|
|
||||||
const { vcpu: unallocatedVCPU, memory: unallocatedMemory } =
|
const { vcpu: allocatedVCPU, memory: allocatedMemory } =
|
||||||
getUnallocatedResources(formValues);
|
getAllocatedResources(formValues);
|
||||||
|
const remainingVCPU = formValues.totalAvailableVCPU - allocatedVCPU;
|
||||||
|
const remainingMemory = formValues.totalAvailableMemory - allocatedMemory;
|
||||||
|
const hasUnusedResources = remainingVCPU > 0 || remainingMemory > 0;
|
||||||
|
const hasOverallocatedResources = remainingVCPU < 0 || remainingMemory < 0;
|
||||||
|
|
||||||
const hasUnusedResources = unallocatedVCPU > 0 || unallocatedMemory > 0;
|
|
||||||
const unusedResourceMessage = [
|
const unusedResourceMessage = [
|
||||||
unallocatedVCPU > 0 ? `${prettifyVCPU(unallocatedVCPU)} vCPUs` : '',
|
remainingVCPU > 0 ? `${prettifyVCPU(remainingVCPU)} vCPUs` : '',
|
||||||
unallocatedMemory > 0
|
remainingMemory > 0 ? `${prettifyMemory(remainingMemory)} of Memory` : '',
|
||||||
? `${prettifyMemory(unallocatedMemory)} of Memory`
|
|
||||||
: '',
|
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' and ');
|
.join(' and ');
|
||||||
|
|
||||||
function handleCPUChange(value: string) {
|
const overallocatedResourceMessage = [
|
||||||
const updatedCPU = parseFloat(value);
|
remainingVCPU < 0 ? `${prettifyVCPU(-remainingVCPU)} vCPUs` : '',
|
||||||
|
remainingMemory < 0 ? `${prettifyMemory(-remainingMemory)} of Memory` : '',
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' and ');
|
||||||
|
|
||||||
|
function handleVCPUChange(value: string) {
|
||||||
|
const updatedVCPU = parseFloat(value);
|
||||||
const updatedMemory =
|
const updatedMemory =
|
||||||
(updatedCPU / RESOURCE_VCPU_MULTIPLIER) *
|
(updatedVCPU / RESOURCE_VCPU_MULTIPLIER) *
|
||||||
RESOURCE_VCPU_MEMORY_RATIO *
|
RESOURCE_VCPU_MEMORY_RATIO *
|
||||||
RESOURCE_MEMORY_MULTIPLIER;
|
RESOURCE_MEMORY_MULTIPLIER;
|
||||||
|
|
||||||
if (
|
if (Number.isNaN(updatedVCPU) || updatedVCPU < MIN_TOTAL_VCPU) {
|
||||||
Number.isNaN(updatedCPU) ||
|
|
||||||
updatedCPU < Math.max(MIN_TOTAL_VCPU, allocatedCPU) ||
|
|
||||||
updatedMemory < Math.max(MIN_TOTAL_MEMORY, allocatedMemory)
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue('totalAvailableVCPU', updatedCPU, { shouldDirty: true });
|
setValue('totalAvailableVCPU', updatedVCPU, { shouldDirty: true });
|
||||||
setValue('totalAvailableMemory', updatedMemory, { shouldDirty: true });
|
setValue('totalAvailableMemory', updatedMemory, { shouldDirty: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +168,7 @@ export default function TotalResourcesFormFragment({
|
|||||||
|
|
||||||
<StyledAvailableCpuSlider
|
<StyledAvailableCpuSlider
|
||||||
value={formValues.totalAvailableVCPU}
|
value={formValues.totalAvailableVCPU}
|
||||||
onChange={(_event, value) => handleCPUChange(value.toString())}
|
onChange={(_event, value) => handleVCPUChange(value.toString())}
|
||||||
max={MAX_TOTAL_VCPU}
|
max={MAX_TOTAL_VCPU}
|
||||||
step={RESOURCE_VCPU_STEP}
|
step={RESOURCE_VCPU_STEP}
|
||||||
aria-label="Total Available vCPU"
|
aria-label="Total Available vCPU"
|
||||||
@@ -155,19 +176,34 @@ export default function TotalResourcesFormFragment({
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Alert
|
<Alert
|
||||||
severity={hasUnusedResources ? 'warning' : 'info'}
|
severity={
|
||||||
|
hasUnusedResources || hasOverallocatedResources ? 'warning' : 'info'
|
||||||
|
}
|
||||||
className="grid grid-flow-row gap-2 rounded-t-none rounded-b-[5px] text-left"
|
className="grid grid-flow-row gap-2 rounded-t-none rounded-b-[5px] text-left"
|
||||||
>
|
>
|
||||||
{hasUnusedResources ? (
|
{hasUnusedResources && !hasOverallocatedResources && (
|
||||||
<>
|
<>
|
||||||
<strong>Please use all the available vCPUs and Memory</strong>
|
<strong>Please use all the available vCPUs and Memory</strong>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
You now have {unusedResourceMessage} unused. Allocate it to any
|
You have {unusedResourceMessage} unused. Allocate it to any of
|
||||||
of the services before saving.
|
the services before saving.
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
) : (
|
)}
|
||||||
|
|
||||||
|
{hasOverallocatedResources && (
|
||||||
|
<>
|
||||||
|
<strong>Overallocated Resources</strong>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You have {overallocatedResourceMessage} overallocated. Reduce it
|
||||||
|
before saving or increase the total amount.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!hasUnusedResources && !hasOverallocatedResources && (
|
||||||
<>
|
<>
|
||||||
<strong>You're All Set</strong>
|
<strong>You're All Set</strong>
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type {
|
|||||||
import BaseRoleForm, {
|
import BaseRoleForm, {
|
||||||
baseRoleFormValidationSchema,
|
baseRoleFormValidationSchema,
|
||||||
} from '@/components/settings/roles/BaseRoleForm';
|
} from '@/components/settings/roles/BaseRoleForm';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import {
|
import {
|
||||||
GetRolesPermissionsDocument,
|
GetRolesPermissionsDocument,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type {
|
|||||||
import BaseRoleForm, {
|
import BaseRoleForm, {
|
||||||
baseRoleFormValidationSchema,
|
baseRoleFormValidationSchema,
|
||||||
} from '@/components/settings/roles/BaseRoleForm';
|
} from '@/components/settings/roles/BaseRoleForm';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import type { Role } from '@/types/application';
|
import type { Role } from '@/types/application';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import SettingsContainer from '@/components/settings/SettingsContainer';
|
|||||||
import CreateRoleForm from '@/components/settings/roles/CreateRoleForm';
|
import CreateRoleForm from '@/components/settings/roles/CreateRoleForm';
|
||||||
import EditRoleForm from '@/components/settings/roles/EditRoleForm';
|
import EditRoleForm from '@/components/settings/roles/EditRoleForm';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import type { Role } from '@/types/application';
|
import type { Role } from '@/types/application';
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type {
|
|||||||
import BaseSecretForm, {
|
import BaseSecretForm, {
|
||||||
baseSecretFormValidationSchema,
|
baseSecretFormValidationSchema,
|
||||||
} from '@/components/settings/secrets/BaseSecretForm';
|
} from '@/components/settings/secrets/BaseSecretForm';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSecretsDocument,
|
GetSecretsDocument,
|
||||||
useInsertSecretMutation,
|
useInsertSecretMutation,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type {
|
|||||||
import BaseSecretForm, {
|
import BaseSecretForm, {
|
||||||
baseSecretFormValidationSchema,
|
baseSecretFormValidationSchema,
|
||||||
} from '@/components/settings/secrets/BaseSecretForm';
|
} from '@/components/settings/secrets/BaseSecretForm';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import type { Secret } from '@/types/application';
|
import type { Secret } from '@/types/application';
|
||||||
import {
|
import {
|
||||||
GetSecretsDocument,
|
GetSecretsDocument,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import IconButton from '@/ui/v2/IconButton';
|
import IconButton from '@/ui/v2/IconButton';
|
||||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import InputAdornment from '@/ui/v2/InputAdornment';
|
import InputAdornment from '@/ui/v2/InputAdornment';
|
||||||
|
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||||
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
@@ -217,7 +217,7 @@ export default function AppleProviderSettings() {
|
|||||||
id="redirectUrl"
|
id="redirectUrl"
|
||||||
defaultValue={`${generateAppServiceUrl(
|
defaultValue={`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/apple/callback`}
|
)}/signin/provider/apple/callback`}
|
||||||
className="col-span-2"
|
className="col-span-2"
|
||||||
@@ -236,7 +236,7 @@ export default function AppleProviderSettings() {
|
|||||||
copy(
|
copy(
|
||||||
`${generateAppServiceUrl(
|
`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/apple/callback`,
|
)}/signin/provider/apple/callback`,
|
||||||
'Redirect URL',
|
'Redirect URL',
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import Form from '@/components/common/Form';
|
|||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import BaseProviderSettings from '@/components/settings/signInMethods/BaseProviderSettings';
|
import BaseProviderSettings from '@/components/settings/signInMethods/BaseProviderSettings';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import IconButton from '@/ui/v2/IconButton';
|
import IconButton from '@/ui/v2/IconButton';
|
||||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import InputAdornment from '@/ui/v2/InputAdornment';
|
import InputAdornment from '@/ui/v2/InputAdornment';
|
||||||
|
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||||
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
@@ -163,7 +163,7 @@ export default function AzureADProviderSettings() {
|
|||||||
id="redirectUrl"
|
id="redirectUrl"
|
||||||
defaultValue={`${generateAppServiceUrl(
|
defaultValue={`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/azuread/callback`}
|
)}/signin/provider/azuread/callback`}
|
||||||
className="col-span-2"
|
className="col-span-2"
|
||||||
@@ -182,7 +182,7 @@ export default function AzureADProviderSettings() {
|
|||||||
copy(
|
copy(
|
||||||
`${generateAppServiceUrl(
|
`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/azuread/callback`,
|
)}/signin/provider/azuread/callback`,
|
||||||
'Redirect URL',
|
'Redirect URL',
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ import BaseProviderSettings, {
|
|||||||
baseProviderValidationSchema,
|
baseProviderValidationSchema,
|
||||||
} from '@/components/settings/signInMethods/BaseProviderSettings';
|
} from '@/components/settings/signInMethods/BaseProviderSettings';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import IconButton from '@/ui/v2/IconButton';
|
import IconButton from '@/ui/v2/IconButton';
|
||||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import InputAdornment from '@/ui/v2/InputAdornment';
|
import InputAdornment from '@/ui/v2/InputAdornment';
|
||||||
|
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||||
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
@@ -139,7 +139,7 @@ export default function DiscordProviderSettings() {
|
|||||||
label="Redirect URL"
|
label="Redirect URL"
|
||||||
defaultValue={`${generateAppServiceUrl(
|
defaultValue={`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/discord/callback`}
|
)}/signin/provider/discord/callback`}
|
||||||
disabled
|
disabled
|
||||||
@@ -154,7 +154,7 @@ export default function DiscordProviderSettings() {
|
|||||||
copy(
|
copy(
|
||||||
`${generateAppServiceUrl(
|
`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/discord/callback`,
|
)}/signin/provider/discord/callback`,
|
||||||
'Redirect URL',
|
'Redirect URL',
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import ControlledCheckbox from '@/components/common/ControlledCheckbox';
|
|||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Text from '@/ui/v2/Text';
|
import Text from '@/ui/v2/Text';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ import BaseProviderSettings, {
|
|||||||
baseProviderValidationSchema,
|
baseProviderValidationSchema,
|
||||||
} from '@/components/settings/signInMethods/BaseProviderSettings';
|
} from '@/components/settings/signInMethods/BaseProviderSettings';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import IconButton from '@/ui/v2/IconButton';
|
import IconButton from '@/ui/v2/IconButton';
|
||||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import InputAdornment from '@/ui/v2/InputAdornment';
|
import InputAdornment from '@/ui/v2/InputAdornment';
|
||||||
|
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||||
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
@@ -139,7 +139,7 @@ export default function FacebookProviderSettings() {
|
|||||||
label="Redirect URL"
|
label="Redirect URL"
|
||||||
defaultValue={`${generateAppServiceUrl(
|
defaultValue={`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/facebook/callback`}
|
)}/signin/provider/facebook/callback`}
|
||||||
disabled
|
disabled
|
||||||
@@ -154,7 +154,7 @@ export default function FacebookProviderSettings() {
|
|||||||
copy(
|
copy(
|
||||||
`${generateAppServiceUrl(
|
`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/facebook/callback`,
|
)}/signin/provider/facebook/callback`,
|
||||||
'Redirect URL',
|
'Redirect URL',
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ import BaseProviderSettings, {
|
|||||||
baseProviderValidationSchema,
|
baseProviderValidationSchema,
|
||||||
} from '@/components/settings/signInMethods/BaseProviderSettings';
|
} from '@/components/settings/signInMethods/BaseProviderSettings';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import IconButton from '@/ui/v2/IconButton';
|
import IconButton from '@/ui/v2/IconButton';
|
||||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import InputAdornment from '@/ui/v2/InputAdornment';
|
import InputAdornment from '@/ui/v2/InputAdornment';
|
||||||
|
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||||
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
@@ -145,7 +145,7 @@ export default function GitHubProviderSettings() {
|
|||||||
label="Redirect URL"
|
label="Redirect URL"
|
||||||
defaultValue={`${generateAppServiceUrl(
|
defaultValue={`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/github/callback`}
|
)}/signin/provider/github/callback`}
|
||||||
disabled
|
disabled
|
||||||
@@ -160,7 +160,7 @@ export default function GitHubProviderSettings() {
|
|||||||
copy(
|
copy(
|
||||||
`${generateAppServiceUrl(
|
`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/github/callback`,
|
)}/signin/provider/github/callback`,
|
||||||
'Redirect URL',
|
'Redirect URL',
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ import BaseProviderSettings, {
|
|||||||
baseProviderValidationSchema,
|
baseProviderValidationSchema,
|
||||||
} from '@/components/settings/signInMethods/BaseProviderSettings';
|
} from '@/components/settings/signInMethods/BaseProviderSettings';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import IconButton from '@/ui/v2/IconButton';
|
import IconButton from '@/ui/v2/IconButton';
|
||||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import InputAdornment from '@/ui/v2/InputAdornment';
|
import InputAdornment from '@/ui/v2/InputAdornment';
|
||||||
|
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||||
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
@@ -139,7 +139,7 @@ export default function GoogleProviderSettings() {
|
|||||||
label="Redirect URL"
|
label="Redirect URL"
|
||||||
defaultValue={`${generateAppServiceUrl(
|
defaultValue={`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/google/callback`}
|
)}/signin/provider/google/callback`}
|
||||||
disabled
|
disabled
|
||||||
@@ -154,7 +154,7 @@ export default function GoogleProviderSettings() {
|
|||||||
copy(
|
copy(
|
||||||
`${generateAppServiceUrl(
|
`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/google/callback`,
|
)}/signin/provider/google/callback`,
|
||||||
'Redirect URL',
|
'Redirect URL',
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ import BaseProviderSettings, {
|
|||||||
baseProviderValidationSchema,
|
baseProviderValidationSchema,
|
||||||
} from '@/components/settings/signInMethods/BaseProviderSettings';
|
} from '@/components/settings/signInMethods/BaseProviderSettings';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import IconButton from '@/ui/v2/IconButton';
|
import IconButton from '@/ui/v2/IconButton';
|
||||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import InputAdornment from '@/ui/v2/InputAdornment';
|
import InputAdornment from '@/ui/v2/InputAdornment';
|
||||||
|
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||||
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
@@ -139,7 +139,7 @@ export default function LinkedInProviderSettings() {
|
|||||||
label="Redirect URL"
|
label="Redirect URL"
|
||||||
defaultValue={`${generateAppServiceUrl(
|
defaultValue={`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/linkedin/callback`}
|
)}/signin/provider/linkedin/callback`}
|
||||||
disabled
|
disabled
|
||||||
@@ -154,7 +154,7 @@ export default function LinkedInProviderSettings() {
|
|||||||
copy(
|
copy(
|
||||||
`${generateAppServiceUrl(
|
`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/linkedin/callback`,
|
)}/signin/provider/linkedin/callback`,
|
||||||
'Redirect URL',
|
'Redirect URL',
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import Option from '@/ui/v2/Option';
|
import Option from '@/ui/v2/Option';
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ import BaseProviderSettings, {
|
|||||||
baseProviderValidationSchema,
|
baseProviderValidationSchema,
|
||||||
} from '@/components/settings/signInMethods/BaseProviderSettings';
|
} from '@/components/settings/signInMethods/BaseProviderSettings';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import IconButton from '@/ui/v2/IconButton';
|
import IconButton from '@/ui/v2/IconButton';
|
||||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import InputAdornment from '@/ui/v2/InputAdornment';
|
import InputAdornment from '@/ui/v2/InputAdornment';
|
||||||
|
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||||
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
@@ -139,7 +139,7 @@ export default function SpotifyProviderSettings() {
|
|||||||
label="Redirect URL"
|
label="Redirect URL"
|
||||||
defaultValue={`${generateAppServiceUrl(
|
defaultValue={`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/spotify/callback`}
|
)}/signin/provider/spotify/callback`}
|
||||||
disabled
|
disabled
|
||||||
@@ -154,7 +154,7 @@ export default function SpotifyProviderSettings() {
|
|||||||
copy(
|
copy(
|
||||||
`${generateAppServiceUrl(
|
`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/spotify/callback`,
|
)}/signin/provider/spotify/callback`,
|
||||||
'Redirect URL',
|
'Redirect URL',
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ import BaseProviderSettings, {
|
|||||||
baseProviderValidationSchema,
|
baseProviderValidationSchema,
|
||||||
} from '@/components/settings/signInMethods/BaseProviderSettings';
|
} from '@/components/settings/signInMethods/BaseProviderSettings';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import IconButton from '@/ui/v2/IconButton';
|
import IconButton from '@/ui/v2/IconButton';
|
||||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import InputAdornment from '@/ui/v2/InputAdornment';
|
import InputAdornment from '@/ui/v2/InputAdornment';
|
||||||
|
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||||
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
@@ -145,7 +145,7 @@ export default function TwitchProviderSettings() {
|
|||||||
label="Redirect URL"
|
label="Redirect URL"
|
||||||
defaultValue={`${generateAppServiceUrl(
|
defaultValue={`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/twitch/callback`}
|
)}/signin/provider/twitch/callback`}
|
||||||
disabled
|
disabled
|
||||||
@@ -160,7 +160,7 @@ export default function TwitchProviderSettings() {
|
|||||||
copy(
|
copy(
|
||||||
`${generateAppServiceUrl(
|
`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/twitch/callback`,
|
)}/signin/provider/twitch/callback`,
|
||||||
'Redirect URL',
|
'Redirect URL',
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import IconButton from '@/ui/v2/IconButton';
|
import IconButton from '@/ui/v2/IconButton';
|
||||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import InputAdornment from '@/ui/v2/InputAdornment';
|
import InputAdornment from '@/ui/v2/InputAdornment';
|
||||||
|
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||||
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
@@ -167,7 +167,7 @@ export default function TwitterProviderSettings() {
|
|||||||
id="redirectUrl"
|
id="redirectUrl"
|
||||||
defaultValue={`${generateAppServiceUrl(
|
defaultValue={`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/twitter/callback`}
|
)}/signin/provider/twitter/callback`}
|
||||||
className="col-span-2"
|
className="col-span-2"
|
||||||
@@ -186,7 +186,7 @@ export default function TwitterProviderSettings() {
|
|||||||
copy(
|
copy(
|
||||||
`${generateAppServiceUrl(
|
`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/twitter/callback`,
|
)}/signin/provider/twitter/callback`,
|
||||||
'Redirect URL',
|
'Redirect URL',
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ import BaseProviderSettings, {
|
|||||||
baseProviderValidationSchema,
|
baseProviderValidationSchema,
|
||||||
} from '@/components/settings/signInMethods/BaseProviderSettings';
|
} from '@/components/settings/signInMethods/BaseProviderSettings';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import IconButton from '@/ui/v2/IconButton';
|
import IconButton from '@/ui/v2/IconButton';
|
||||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import InputAdornment from '@/ui/v2/InputAdornment';
|
import InputAdornment from '@/ui/v2/InputAdornment';
|
||||||
|
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||||
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
@@ -138,7 +138,7 @@ export default function WindowsLiveProviderSettings() {
|
|||||||
label="Redirect URL"
|
label="Redirect URL"
|
||||||
defaultValue={`${generateAppServiceUrl(
|
defaultValue={`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/windowslive/callback`}
|
)}/signin/provider/windowslive/callback`}
|
||||||
disabled
|
disabled
|
||||||
@@ -153,7 +153,7 @@ export default function WindowsLiveProviderSettings() {
|
|||||||
copy(
|
copy(
|
||||||
`${generateAppServiceUrl(
|
`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/windowslive/callback`,
|
)}/signin/provider/windowslive/callback`,
|
||||||
'Redirect URL',
|
'Redirect URL',
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import Form from '@/components/common/Form';
|
|||||||
import SettingsContainer from '@/components/settings/SettingsContainer';
|
import SettingsContainer from '@/components/settings/SettingsContainer';
|
||||||
import BaseProviderSettings from '@/components/settings/signInMethods/BaseProviderSettings';
|
import BaseProviderSettings from '@/components/settings/signInMethods/BaseProviderSettings';
|
||||||
import { useUI } from '@/context/UIContext';
|
import { useUI } from '@/context/UIContext';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetSignInMethodsDocument,
|
GetSignInMethodsDocument,
|
||||||
useGetSignInMethodsQuery,
|
useGetSignInMethodsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import IconButton from '@/ui/v2/IconButton';
|
import IconButton from '@/ui/v2/IconButton';
|
||||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
|
||||||
import Input from '@/ui/v2/Input';
|
import Input from '@/ui/v2/Input';
|
||||||
import InputAdornment from '@/ui/v2/InputAdornment';
|
import InputAdornment from '@/ui/v2/InputAdornment';
|
||||||
|
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||||
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import getServerError from '@/utils/settings/getServerError';
|
import getServerError from '@/utils/settings/getServerError';
|
||||||
@@ -184,7 +184,7 @@ export default function WorkOsProviderSettings() {
|
|||||||
id="redirectUrl"
|
id="redirectUrl"
|
||||||
defaultValue={`${generateAppServiceUrl(
|
defaultValue={`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/workos/callback`}
|
)}/signin/provider/workos/callback`}
|
||||||
className="col-span-2"
|
className="col-span-2"
|
||||||
@@ -203,7 +203,7 @@ export default function WorkOsProviderSettings() {
|
|||||||
copy(
|
copy(
|
||||||
`${generateAppServiceUrl(
|
`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
currentProject.subdomain,
|
||||||
currentProject.region.awsName,
|
currentProject.region,
|
||||||
'auth',
|
'auth',
|
||||||
)}/signin/provider/workos/callback`,
|
)}/signin/provider/workos/callback`,
|
||||||
'Redirect URL',
|
'Redirect URL',
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ const StyledSlider = styled(MaterialSlider)(({ theme }) => ({
|
|||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.main,
|
||||||
[`&:focus, &:hover, &.${materialSliderClasses.active}, &.${materialSliderClasses.focusVisible}`]:
|
[`&:focus, &:hover, &.${materialSliderClasses.active}, &.${materialSliderClasses.focusVisible}`]:
|
||||||
{
|
{
|
||||||
boxShadow: `0 0 0 2px ${alpha(theme.palette.primary.main, 0.3)}`,
|
boxShadow: `0 0 0 3px ${alpha(theme.palette.primary.main, 0.35)}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import type { IconProps } from '@/ui/v2/icons';
|
||||||
|
import SvgIcon from '@/ui/v2/icons/SvgIcon';
|
||||||
|
import type { ForwardedRef } from 'react';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
|
function ExclamationIcon(props: IconProps, ref: ForwardedRef<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<SvgIcon
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
aria-label="Exclamation mark"
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
opacity=".2"
|
||||||
|
d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M8.75 5.5V4h-1.5v5.5h1.5v-4Zm0 5.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExclamationIcon.displayName = 'NhostExclamationIcon';
|
||||||
|
|
||||||
|
export default forwardRef(ExclamationIcon);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as ExclamationIcon } from './ExclamationIcon';
|
||||||
26
dashboard/src/components/ui/v2/icons/GaugeIcon/GaugeIcon.tsx
Normal file
26
dashboard/src/components/ui/v2/icons/GaugeIcon/GaugeIcon.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { IconProps } from '@/ui/v2/icons';
|
||||||
|
import SvgIcon from '@/ui/v2/icons/SvgIcon';
|
||||||
|
|
||||||
|
function GaugeIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<SvgIcon
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
aria-label="A gauge"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M10.757 3.295A7.25 7.25 0 0 0 8 2.75h-.026c-3.286.011-6.032 2.231-6.92 5.227a.753.753 0 0 0-.081.293 7.359 7.359 0 0 0-.223 1.8v1.43A1.25 1.25 0 0 0 2 12.75h12a1.25 1.25 0 0 0 1.25-1.25V10a7.25 7.25 0 0 0-.246-1.872l-.001-.004V8.12a7.248 7.248 0 0 0-4.246-4.825Zm-2.77 7.955h5.763V10c0-.252-.017-.503-.05-.751l-1.16.31a.75.75 0 1 1-.387-1.448l1.16-.31-.003-.006a5.751 5.751 0 0 0-4.56-3.496V5.5a.75.75 0 0 1-1.5 0V4.3c-2.053.271-3.764 1.645-4.545 3.505l1.142.306A.75.75 0 1 1 3.46 9.56L2.307 9.25a5.895 5.895 0 0 0-.057.82v1.179h3.845l4.05-5.277a.75.75 0 0 1 1.19.913L7.985 11.25Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</SvgIcon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GaugeIcon.displayName = 'NhostGaugeIcon';
|
||||||
|
|
||||||
|
export default GaugeIcon;
|
||||||
1
dashboard/src/components/ui/v2/icons/GaugeIcon/index.ts
Normal file
1
dashboard/src/components/ui/v2/icons/GaugeIcon/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default as GaugeIcon } from './GaugeIcon';
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import type { DialogFormProps } from '@/types/common';
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import { Alert } from '@/ui/Alert';
|
import { Alert } from '@/ui/Alert';
|
||||||
import Button from '@/ui/v2/Button';
|
import Button from '@/ui/v2/Button';
|
||||||
@@ -68,7 +68,7 @@ export default function CreateUserForm({
|
|||||||
|
|
||||||
const baseAuthUrl = generateAppServiceUrl(
|
const baseAuthUrl = generateAppServiceUrl(
|
||||||
currentProject?.subdomain,
|
currentProject?.subdomain,
|
||||||
currentProject?.region?.awsName,
|
currentProject?.region,
|
||||||
'auth',
|
'auth',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import ControlledSelect from '@/components/common/ControlledSelect';
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import Form from '@/components/common/Form';
|
import Form from '@/components/common/Form';
|
||||||
import EditUserPasswordForm from '@/components/users/EditUserPasswordForm';
|
import EditUserPasswordForm from '@/components/users/EditUserPasswordForm';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import type { DialogFormProps } from '@/types/common';
|
import type { DialogFormProps } from '@/types/common';
|
||||||
import Avatar from '@/ui/v2/Avatar';
|
import Avatar from '@/ui/v2/Avatar';
|
||||||
import Box from '@/ui/v2/Box';
|
import Box from '@/ui/v2/Box';
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import FormActivityIndicator from '@/components/common/FormActivityIndicator';
|
import FormActivityIndicator from '@/components/common/FormActivityIndicator';
|
||||||
import type { EditUserFormValues } from '@/components/users/EditUserForm';
|
import type { EditUserFormValues } from '@/components/users/EditUserForm';
|
||||||
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
|
|
||||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||||
import Avatar from '@/ui/v2/Avatar';
|
import Avatar from '@/ui/v2/Avatar';
|
||||||
import Chip from '@/ui/v2/Chip';
|
import Chip from '@/ui/v2/Chip';
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user