Compare commits

..

95 Commits

Author SHA1 Message Date
Szilárd Dóró
809a2d35f8 Merge pull request #1940 from nhost/changeset-release/main
chore: update versions
2023-05-22 11:51:04 +02:00
github-actions[bot]
e17ec7fce7 chore: update versions 2023-05-22 07:26:27 +00:00
Szilárd Dóró
241175b158 Merge pull request #1935 from nhost/renovate/prettier-plugin-tailwindcss-0.x
chore(deps): update dependency prettier-plugin-tailwindcss to ^0.3.0
2023-05-22 09:14:39 +02:00
Szilárd Dóró
51ceaf2696 Merge pull request #1950 from nhost/fix/nextjs-react-error
fix(nextjs): don't break Next.js when using SignedIn/SignedOut
2023-05-22 09:00:26 +02:00
Szilárd Dóró
490b77cde4 Merge pull request #1949 from nhost/fix/local-infinite-loop
fix(dashboard): don't enter an infinite loop in local mode
2023-05-21 18:14:05 +02:00
Szilárd Dóró
7fea29a8b4 chore: add changeset 2023-05-21 18:13:34 +02:00
Szilárd Dóró
1a34e011ad fix: don't break Next.js when using SignedIn/SignedOut 2023-05-21 18:08:42 +02:00
Szilárd Dóró
395839f449 chore: check service URL when creating client 2023-05-21 16:34:00 +02:00
Szilárd Dóró
399009d66a fix(gql): don't enter an infinite loop when fetching remote app data 2023-05-21 16:31:57 +02:00
Szilárd Dóró
12eb236c4a chore: add changeset 2023-05-19 17:14:22 +02:00
Szilárd Dóró
2218e5cd5b Merge branch 'main' into renovate/prettier-plugin-tailwindcss-0.x 2023-05-19 17:12:36 +02:00
Szilárd Dóró
2dcf1b38c6 Merge pull request #1945 from nhost/fix/404-not-found
fix(dashboard): don't redirect to 404 on project creation
2023-05-19 16:16:13 +02:00
Szilárd Dóró
ad49c92879 fix: trigger project list refetch properly 2023-05-19 15:31:17 +02:00
Szilárd Dóró
13dd57eeb4 Merge pull request #1943 from nhost/fix/deployment-sorting
fix(dashboard): sync deployment sorting
2023-05-19 15:27:44 +02:00
Szilárd Dóró
1345741b11 fix: don't redirect to 404 2023-05-19 15:26:20 +02:00
renovate[bot]
511615f176 chore(deps): update dependency prettier-plugin-tailwindcss to ^0.3.0 2023-05-19 09:51:56 +00:00
Szilárd Dóró
1198c201f1 Merge pull request #1922 from nhost/renovate/turbo-1.x
chore(deps): update dependency turbo to v1.9.8
2023-05-19 11:48:43 +02:00
Szilárd Dóró
f9b81a2ae9 chore: bump version in the Dockerfile as well 2023-05-19 10:34:21 +02:00
Szilárd Dóró
dff0894f37 Revert "fix: sync deployment sorting"
This reverts commit 80f3645d57.
2023-05-19 09:38:04 +02:00
Szilárd Dóró
80f3645d57 fix: sync deployment sorting 2023-05-19 09:27:24 +02:00
Szilárd Dóró
7ddb9a654e fix: sync deployment sorting 2023-05-19 09:10:44 +02:00
Szilárd Dóró
71f3be15d8 Merge pull request #1941 from nhost/chore/under-the-hood-improvements
chore(dashboard): under the hood improvements
2023-05-19 09:03:46 +02:00
Szilárd Dóró
96a9070836 chore: loosen eslint rules 2023-05-18 17:07:15 +02:00
Szilárd Dóró
329e5a91b9 fix: use the correct deployment ordering 2023-05-18 16:01:44 +02:00
Szilárd Dóró
6d559d6e23 chore: under the hood improvements 2023-05-18 11:39:22 +02:00
renovate[bot]
f4f1450d06 chore(deps): update dependency turbo to v1.9.8 2023-05-18 08:22:54 +00:00
Szilárd Dóró
a1eea9df7d Merge pull request #1932 from nhost/renovate/docusaurus-monorepo
fix(deps): update docusaurus monorepo to v2.4.1
2023-05-18 10:16:23 +02:00
Szilárd Dóró
26f2b665e6 Merge pull request #1924 from nhost/feat/pat
feat(hasura-auth-js): add support for personal access tokens
2023-05-18 10:14:12 +02:00
renovate[bot]
224a5cc805 fix(deps): update docusaurus monorepo to v2.4.1 2023-05-17 11:05:58 +00:00
David Barroso
59125b3c77 Merge pull request #1937 from nhost/dbarroso/react-apollo-example-update
chore: update react-apollo example's dependencies
2023-05-17 13:02:09 +02:00
Szilárd Dóró
5b69e3efd8 fix: don't break builds 2023-05-17 10:41:27 +02:00
Szilárd Dóró
c9a444d048 chore: sync playwright versions 2023-05-17 10:21:03 +02:00
David Barroso
2eeac45718 asd 2023-05-17 09:54:44 +02:00
David Barroso
fe8ca8aba6 Update examples/react-apollo/package.json
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2023-05-17 09:32:13 +02:00
David Barroso
086ee46b08 chore: update react-apollo example's dependencies 2023-05-17 09:21:25 +02:00
Szilárd Dóró
203bc97f51 feat: add useSignInPAT to @nhost/vue 2023-05-16 14:42:55 +02:00
Szilárd Dóró
b24af44aac feat: add support for refreshTokenId 2023-05-16 12:54:51 +02:00
Szilárd Dóró
cdaa6d4e73 chore: remove hash function 2023-05-16 12:16:35 +02:00
Szilárd Dóró
09e2c8f5c7 feat: add hash function 2023-05-16 11:24:52 +02:00
Szilárd Dóró
4581677830 chore: update docs 2023-05-16 09:37:51 +02:00
Szilárd Dóró
7bfa6c9f93 fix: use correct hasura-auth image 2023-05-16 08:59:17 +02:00
Szilárd Dóró
80ef430d70 feat: add personal access token docs 2023-05-16 08:51:22 +02:00
Szilárd Dóró
bad8af0fd1 Merge branch 'main' into feat/pat 2023-05-16 08:35:24 +02:00
Szilárd Dóró
69caa34c43 Merge pull request #1934 from nhost/changeset-release/main
chore: update versions
2023-05-16 08:34:38 +02:00
github-actions[bot]
1d898e2893 chore: update versions 2023-05-15 18:43:10 +00:00
Szilárd Dóró
e87621cbde Merge pull request #1936 from nhost/fix/security-key-url
fix(hasura-auth-js): make the call to the correct endpoint
2023-05-15 20:41:59 +02:00
Szilárd Dóró
0d6fc42158 fix: make the call to the correct endpoint 2023-05-15 19:46:26 +02:00
Szilárd Dóró
f6fbee6b13 Merge pull request #1933 from nhost/fix/project-rename-prevent-404
fix(dashboard): don't redirect to 404 on project rename
2023-05-15 16:59:00 +02:00
Szilárd Dóró
1230b72222 fix: don't redirect to 404 on project rename 2023-05-15 16:05:45 +02:00
Szilárd Dóró
6cc7704555 Merge pull request #1931 from nhost/changeset-release/main
chore: update versions
2023-05-15 15:47:22 +02:00
github-actions[bot]
c0954dec09 chore: update versions 2023-05-15 13:30:42 +00:00
Szilárd Dóró
6c25480a7a Merge pull request #1929 from nhost/fix/build-targets
chore: change build target to ES2019
2023-05-15 15:29:32 +02:00
Szilárd Dóró
da03bf390c chore: change build target to ES2019 2023-05-15 11:09:00 +02:00
Szilárd Dóró
3b513be9f2 Merge pull request #1926 from nhost/changeset-release/main
chore: update versions
2023-05-12 16:55:46 +02:00
github-actions[bot]
e450e9d636 chore: update versions 2023-05-12 14:27:16 +00:00
Szilárd Dóró
ed1ee10879 Merge pull request #1925 from nhost/fix/postgres-connection-string
fix(dashboard): show correct postgres connection string
2023-05-12 16:25:51 +02:00
Szilárd Dóró
a6120bf366 feat: update API
chore: fix tests
2023-05-12 14:59:07 +02:00
Szilárd Dóró
349aac369e chore: add changeset 2023-05-12 14:23:54 +02:00
Szilárd Dóró
5a84362c80 fix: construct postgres connection string 2023-05-12 14:23:05 +02:00
Szilárd Dóró
b23dc058a6 Merge branch 'main' into feat/pat 2023-05-12 14:14:09 +02:00
Szilárd Dóró
ad0dda7493 feat: extend nhost.auth.createPAT with id 2023-05-12 14:09:57 +02:00
Szilárd Dóró
4063507d59 chore: improve CLI example 2023-05-12 14:01:11 +02:00
Szilárd Dóró
f59a77b1c8 Merge pull request #1923 from nhost/changeset-release/main
chore: update versions
2023-05-12 10:25:12 +02:00
github-actions[bot]
30686bc4ce chore: update versions 2023-05-12 08:03:41 +00:00
Szilárd Dóró
0c4ac8d368 Merge pull request #1919 from nhost/chore/update-staging-urls
chore(dashboard): change URL construction
2023-05-12 10:02:32 +02:00
Szilárd Dóró
85439307a9 feat: finalize CLI example 2023-05-12 09:34:22 +02:00
Szilárd Dóró
0d8baa4065 feat: allow PAT creation through the example 2023-05-12 08:56:56 +02:00
Szilárd Dóró
7da0e5e256 Merge pull request #1918 from nhost/changeset-release/main
chore: update versions
2023-05-11 15:30:16 +02:00
Szilárd Dóró
4bca94425e chore: add useful information to the README 2023-05-11 15:26:15 +02:00
Szilárd Dóró
9f948385c0 feat: improve readability, add book relationship 2023-05-11 15:23:59 +02:00
Szilárd Dóró
294c504b61 chore: update pnpm-lock file 2023-05-11 15:10:28 +02:00
Szilárd Dóró
1469ec2969 Merge branch 'main' into feat/pat 2023-05-11 15:09:31 +02:00
github-actions[bot]
8229101efe chore: update versions 2023-05-11 13:07:57 +00:00
Szilárd Dóró
afad1778f8 Merge pull request #1895 from nhost/renovate/react-monorepo
chore(deps): update react monorepo
2023-05-11 15:06:23 +02:00
Szilárd Dóró
28fc7b84c7 chore: update changeset 2023-05-11 13:38:39 +02:00
Szilárd Dóró
3f478a4e3c chore: include vitest in the bump, add changeset 2023-05-11 13:37:34 +02:00
Szilárd Dóró
aa54666941 fix: don't break builds 2023-05-11 12:53:45 +02:00
Szilárd Dóró
20fb69faba chore: change URL construction 2023-05-11 12:46:49 +02:00
renovate[bot]
1caeb2a548 chore(deps): update react monorepo 2023-05-11 10:12:59 +00:00
Szilárd Dóró
6356c5a2c8 Merge pull request #1906 from nhost/chore/bump-pnpm
chore: bump `pnpm` and `turbo` version
2023-05-11 12:10:10 +02:00
Szilárd Dóró
6ec1dd3248 chore: bump lock file again 2023-05-11 11:07:27 +02:00
Szilárd Dóró
8a4b5031dc Merge branch 'main' into chore/bump-pnpm 2023-05-11 11:06:57 +02:00
Szilárd Dóró
726c33d1b2 feat: finalize CLI example 2023-05-10 16:41:49 +02:00
Szilárd Dóró
11b9cfbc0d feat: add an example CLI tool to showcase PATs 2023-05-10 15:35:54 +02:00
Szilárd Dóró
79aaa91e67 chore: update hasura-auth version 2023-05-09 17:19:40 +02:00
Szilárd Dóró
df4d24320a chore: update example metadata 2023-05-09 14:33:49 +02:00
Szilárd Dóró
baa3ef794e feat(examples): add PAT example 2023-05-08 17:03:49 +02:00
Szilárd Dóró
da7ffbe523 feat: add useSignInPAT hook 2023-05-08 16:04:00 +02:00
Szilárd Dóró
15a985e079 fix: don't break dashboard build 2023-05-08 15:29:29 +02:00
Szilárd Dóró
8ff00a4258 chore(ci): bump pnpm version to v8.4.0 2023-05-08 15:19:53 +02:00
Szilárd Dóró
7e27d7c0a1 chore: update changeset, bump turbo version 2023-05-08 15:17:42 +02:00
Szilárd Dóró
49f9b8372a chore: bump pnpm version to v8.4.0 2023-05-08 15:09:29 +02:00
Szilárd Dóró
3ca70554c8 feat: add support for PAT sign in 2023-05-08 14:34:12 +02:00
Szilárd Dóró
077b200510 Merge branch 'main' into feat/pat 2023-05-08 14:04:11 +02:00
Szilárd Dóró
2f220db84a extend machine with PAT sign in 2023-04-27 17:06:26 +02:00
244 changed files with 12479 additions and 9697 deletions

View File

@@ -14,7 +14,7 @@ runs:
steps:
- uses: pnpm/action-setup@v2.2.4
with:
version: 7.17.0
version: 8.4.0
run_install: false
- name: Get pnpm cache directory
id: pnpm-cache-dir

View File

@@ -36,6 +36,7 @@ export default defineConfig({
}
},
build: {
target: 'es2019',
sourcemap: true,
lib: {
entry,

View File

@@ -25,6 +25,7 @@ module.exports = {
'error',
{ allowArrowFunctions: true, allowFunctions: true },
],
'import/no-named-as-default': 'off',
'import/prefer-default-export': 'off',
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
curly: ['error', 'all'],

View File

@@ -1,5 +1,54 @@
# @nhost/dashboard
## 0.16.12
### Patch Changes
- 399009d6: fix(gql): don't enter an infinite loop when fetching remote app data
- 329e5a91: fix(deployments): use the same sorting of deployments everywhere
- 6d559d6e: chore(settings): add under the hood improvements to the settings page
- 12eb236c: chore(deps): bump `prettier-plugin-tailwindcss` to `v0.3.0`
- f9b81a2a: chore(deps): bump `turbo` to `v1.9.8`
- 1345741b: fix(projects): don't redirect to 404 on project creation
- Updated dependencies [7fea29a8]
- @nhost/react-apollo@5.0.23
- @nhost/nextjs@1.13.25
## 0.16.11
### Patch Changes
- 1230b722: fix(projects): don't redirect to 404 on when the project is renamed
- @nhost/react-apollo@5.0.22
- @nhost/nextjs@1.13.24
## 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

View File

@@ -3,7 +3,7 @@ RUN apk add --no-cache libc6-compat
RUN apk update
WORKDIR /app
RUN yarn global add turbo@1.8.6
RUN yarn global add turbo@1.9.8
COPY . .
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_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 --from=pruner /app/out/json/ .
COPY --from=pruner /app/out/pnpm-*.yaml .

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "0.16.6",
"version": "0.16.12",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -15,7 +15,7 @@
"format": "prettier --write \"src/**/*.{js,ts,tsx,jsx,json,md}\" --plugin-search-dir=.",
"storybook": "start-storybook -p 6006 -s public",
"build-storybook": "build-storybook",
"e2e": "npx playwright@1.31.2 install --with-deps && playwright test"
"e2e": "npx playwright@1.33.0 install --with-deps && playwright test"
},
"dependencies": {
"@apollo/client": "^3.7.10",
@@ -88,7 +88,7 @@
"@graphql-codegen/typescript-operations": "^3.0.0",
"@graphql-codegen/typescript-react-apollo": "^3.3.1",
"@next/bundle-analyzer": "^12.3.1",
"@playwright/test": "^1.31.2",
"@playwright/test": "^1.33.0",
"@storybook/addon-actions": "^6.5.14",
"@storybook/addon-essentials": "^6.5.14",
"@storybook/addon-interactions": "^6.5.14",
@@ -105,15 +105,15 @@
"@types/lodash.debounce": "^4.0.7",
"@types/node": "^16.11.7",
"@types/pluralize": "^0.0.29",
"@types/react": "18.2.0",
"@types/react-dom": "18.2.1",
"@types/react": "18.2.6",
"@types/react-dom": "18.2.4",
"@types/react-table": "^7.7.12",
"@types/testing-library__jest-dom": "^5.14.5",
"@types/validator": "^13.7.10",
"@typescript-eslint/eslint-plugin": "^5.43.0",
"@typescript-eslint/parser": "^5.43.0",
"@vitejs/plugin-react": "^4.0.0",
"@vitest/coverage-c8": "^0.30.0",
"@vitest/coverage-c8": "^0.31.0",
"autoprefixer": "^10.4.13",
"babel-loader": "^8.3.0",
"babel-plugin-transform-remove-console": "^6.9.4",
@@ -137,7 +137,7 @@
"postcss": "^8.4.19",
"prettier": "^2.7.1",
"prettier-plugin-organize-imports": "^3.2.0",
"prettier-plugin-tailwindcss": "^0.2.0",
"prettier-plugin-tailwindcss": "^0.3.0",
"react-date-fns-hooks": "^0.9.4",
"require-from-string": "^2.0.2",
"snake-case": "^3.0.4",
@@ -147,8 +147,7 @@
"tsconfig-paths-webpack-plugin": "^4.0.0",
"vite": "^4.0.2",
"vite-tsconfig-paths": "^4.0.3",
"vitest": "^0.30.1",
"webpack": "^5.75.0"
"vitest": "^0.31.0"
},
"browserslist": {
"production": [

View File

@@ -114,6 +114,9 @@ export default function AppDeployments(props: AppDeploymentsProps) {
const { deployments } = deploymentPageData || { deployments: [] };
const { deployments: scheduledOrPendingDeployments } =
scheduledOrPendingDeploymentsData || { deployments: [] };
const isDeploymentInProgress = deployments?.some((deployment) =>
['PENDING', 'SCHEDULED'].includes(deployment.deploymentStatus),
);
const latestDeployment = latestDeploymentData?.deployments[0];
const latestLiveDeployment = latestLiveDeploymentData?.deployments[0];
@@ -135,7 +138,10 @@ export default function AppDeployments(props: AppDeploymentsProps) {
deployment={deployment}
isLive={liveDeploymentId === deployment.id}
showRedeploy={latestDeployment.id === deployment.id}
disableRedeploy={scheduledOrPendingDeployments?.length > 0}
disableRedeploy={
scheduledOrPendingDeployments?.length > 0 ||
isDeploymentInProgress
}
/>
{index !== deployments.length - 1 && <Divider component="li" />}

View File

@@ -21,8 +21,6 @@ import { discordAnnounce } from '@/utils/discordAnnounce';
import { getPreviousApplicationState } from '@/utils/getPreviousApplicationState';
import { getApplicationStatusString } from '@/utils/helpers';
import { triggerToast } from '@/utils/toast';
import { updateOwnCache } from '@/utils/updateOwnCache';
import { useApolloClient } from '@apollo/client';
import { useUserData } from '@nhost/nextjs';
import Image from 'next/image';
import { useState } from 'react';
@@ -32,7 +30,11 @@ import { RemoveApplicationModal } from './RemoveApplicationModal';
import { StagingMetadata } from './StagingMetadata';
export default function ApplicationErrored() {
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
const {
currentWorkspace,
currentProject,
refetch: refetchProject,
} = useCurrentWorkspaceAndProject();
const [changingApplicationStateLoading, setChangingApplicationStateLoading] =
useState(false);
@@ -54,7 +56,6 @@ export default function ApplicationErrored() {
const [showRecreateModal, setShowRecreateModal] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [insertApp] = useInsertApplicationMutation();
const client = useApolloClient();
const { currentDate } = useCurrentDate();
const user = useUserData();
const isOwner = useIsCurrentUserOwner();
@@ -94,7 +95,7 @@ export default function ApplicationErrored() {
});
discordAnnounce(`Recreating: ${currentProject?.name} (${user.email})`);
triggerToast(`Recreating ${currentProject?.name} `);
await updateOwnCache(client);
await refetchProject();
} catch (e) {
triggerToast(`Error trying to recreate: ${currentProject?.name}`);
}

View File

@@ -18,18 +18,14 @@ import { toast } from 'react-hot-toast';
export default function ApplicationInfo() {
const { currentProject } = useCurrentWorkspaceAndProject();
const [deleteApplication] = useDeleteApplicationMutation({
refetchQueries: [GetAllWorkspacesAndProjectsDocument],
refetchQueries: [{ query: GetAllWorkspacesAndProjectsDocument }],
});
const router = useRouter();
async function handleClickRemove() {
try {
await toast.promise(
deleteApplication({
variables: {
appId: currentProject.id,
},
}),
deleteApplication({ variables: { appId: currentProject.id } }),
{
loading: 'Deleting project...',
success: 'The project has been deleted successfully.',

View File

@@ -35,7 +35,7 @@ export default function ApplicationPaused() {
const [showDeletingModal, setShowDeletingModal] = useState(false);
const [unpauseApplication, { loading: changingApplicationStateLoading }] =
useUnpauseApplicationMutation({
refetchQueries: [GetAllWorkspacesAndProjectsDocument],
refetchQueries: [{ query: GetAllWorkspacesAndProjectsDocument }],
});
const { data, loading } = useGetFreeAndActiveProjectsQuery({

View File

@@ -33,7 +33,7 @@ export function HasuraData({ close }: HasuraDataProps) {
? `${getHasuraConsoleServiceUrl()}`
: generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.region,
'hasura',
defaultLocalBackendSlugs,
{ ...defaultRemoteBackendSlugs, hasura: '/console' },

View File

@@ -46,7 +46,7 @@ export function RemoveApplicationModal({
className,
}: RemoveApplicationModalProps) {
const [deleteApplication] = useDeleteApplicationMutation({
refetchQueries: [GetAllWorkspacesAndProjectsDocument],
refetchQueries: [{ query: GetAllWorkspacesAndProjectsDocument }],
});
const [loadingRemove, setLoadingRemove] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject();

View File

@@ -8,8 +8,6 @@ import Button from '@/ui/v2/Button';
import Text from '@/ui/v2/Text';
import { discordAnnounce } from '@/utils/discordAnnounce';
import { triggerToast } from '@/utils/toast';
import { updateOwnCache } from '@/utils/updateOwnCache';
import { useApolloClient } from '@apollo/client';
import { ErrorBoundary } from 'react-error-boundary';
import { useFormContext } from 'react-hook-form';
import { RepoAndBranch } from './RepoAndBranch';
@@ -27,12 +25,11 @@ export function EditRepositorySettingsModal({
const isNotCompleted = !watch('productionBranch') || !watch('repoBaseFolder');
const { closeAlertDialog } = useDialog();
const { currentProject } = useCurrentWorkspaceAndProject();
const { currentProject, refetch: refetchProject } =
useCurrentWorkspaceAndProject();
const [updateApp, { loading }] = useUpdateApplicationMutation();
const client = useApolloClient();
const handleEditGitHubIntegration = async (
data: EditRepositorySettingsFormData,
) => {
@@ -60,7 +57,8 @@ export function EditRepositorySettingsModal({
});
}
await updateOwnCache(client);
await refetchProject();
if (close) {
close();
} else {

View File

@@ -194,7 +194,7 @@ export default function RuleGroupEditor({
<Link
href={`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region?.awsName,
currentProject.region,
'hasura',
)}/console/data/default/schema/${schema}/tables/${table}/permissions`}
underline="hover"

View File

@@ -5,16 +5,20 @@ import { Avatar } from '@/ui/Avatar';
import type { DeploymentStatus } from '@/ui/StatusCircle';
import { StatusCircle } from '@/ui/StatusCircle';
import Button from '@/ui/v2/Button';
import Chip from '@/ui/v2/Chip';
import { Chip } from '@/ui/v2/Chip';
import { ListItem } from '@/ui/v2/ListItem';
import Tooltip from '@/ui/v2/Tooltip';
import { Tooltip } from '@/ui/v2/Tooltip';
import ArrowCounterclockwiseIcon from '@/ui/v2/icons/ArrowCounterclockwiseIcon';
import ChevronRightIcon from '@/ui/v2/icons/ChevronRightIcon';
import type { DeploymentRowFragment } from '@/utils/__generated__/graphql';
import { useInsertDeploymentMutation } from '@/utils/__generated__/graphql';
import {
GetAllWorkspacesAndProjectsDocument,
useInsertDeploymentMutation,
} from '@/utils/__generated__/graphql';
import getServerError from '@/utils/settings/getServerError';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import { formatDistanceToNowStrict, parseISO } from 'date-fns';
import type { MouseEvent } from 'react';
import { toast } from 'react-hot-toast';
import { twMerge } from 'tailwind-merge';
@@ -52,9 +56,39 @@ export default function DeploymentListItem({
})
: '';
const [insertDeployment, { loading }] = useInsertDeploymentMutation();
const [insertDeployment, { loading }] = useInsertDeploymentMutation({
refetchQueries: [{ query: GetAllWorkspacesAndProjectsDocument }],
});
const { commitMessage } = deployment;
async function redeployDeployment(event: MouseEvent<HTMLButtonElement>) {
event.stopPropagation();
event.preventDefault();
const insertDeploymentPromise = insertDeployment({
variables: {
object: {
appId: currentProject?.id,
commitMessage: deployment.commitMessage,
commitSHA: deployment.commitSHA,
commitUserAvatarUrl: deployment.commitUserAvatarUrl,
commitUserName: deployment.commitUserName,
deploymentStatus: 'SCHEDULED',
},
},
});
await toast.promise(
insertDeploymentPromise,
{
loading: 'Scheduling deployment...',
success: 'Deployment has been scheduled successfully.',
error: getServerError('An error occurred when scheduling deployment.'),
},
getToastStyleProps(),
);
}
return (
<ListItem.Root>
<ListItem.Button
@@ -88,7 +122,7 @@ export default function DeploymentListItem({
{showRedeploy && (
<Tooltip
title={
!disableRedeploy && !loading
disableRedeploy || loading
? 'Deployments cannot be re-triggered when a deployment is in progress.'
: ''
}
@@ -100,35 +134,7 @@ export default function DeploymentListItem({
size="small"
color="secondary"
variant="outlined"
onClick={async (event) => {
event.stopPropagation();
event.preventDefault();
const insertDeploymentPromise = insertDeployment({
variables: {
object: {
appId: currentProject?.id,
commitMessage: deployment.commitMessage,
commitSHA: deployment.commitSHA,
commitUserAvatarUrl: deployment.commitUserAvatarUrl,
commitUserName: deployment.commitUserName,
deploymentStatus: 'SCHEDULED',
},
},
});
await toast.promise(
insertDeploymentPromise,
{
loading: 'Scheduling deployment...',
success: 'Deployment has been scheduled successfully.',
error: getServerError(
'An error occurred when scheduling deployment.',
),
},
getToastStyleProps(),
);
}}
onClick={redeployDeployment}
startIcon={
<ArrowCounterclockwiseIcon className={twMerge('h-4 w-4')} />
}

View File

@@ -10,7 +10,6 @@ import Button from '@/ui/v2/Button';
import Text from '@/ui/v2/Text';
import { nhost } from '@/utils/nhost';
import { triggerToast } from '@/utils/toast';
import { updateOwnCache } from '@/utils/updateOwnCache';
import { useApolloClient } from '@apollo/client';
import { alpha } from '@mui/system';
import { useUserData } from '@nhost/nextjs';
@@ -28,13 +27,18 @@ export function InviteAnnounce() {
useSubmitState();
// @FIX: We probably don't want to poll every ten seconds for possible invites. (We can change later depending on how it works in production.) Maybe just on the workspace page?
const { data, loading, error, refetch, startPolling } =
useGetWorkspaceMemberInvitesToManageQuery({
variables: {
userId: user?.id,
},
skip: !isPlatform || !user,
});
const {
data,
loading,
error,
refetch: refetchInvitations,
startPolling,
} = useGetWorkspaceMemberInvitesToManageQuery({
variables: {
userId: user?.id,
},
skip: !isPlatform || !user,
});
useEffect(() => {
startPolling(15000);
@@ -79,9 +83,14 @@ export function InviteAnnounce() {
});
}
await updateOwnCache(client);
await client.refetchQueries({
include: [
GetAllWorkspacesAndProjectsDocument,
GetWorkspaceMemberInvitesToManageDocument,
],
});
await router.push(`/${invite.workspace.slug}`);
await refetch();
await refetchInvitations();
triggerToast('Workspace invite accepted');
return setSubmitState({
error: null,

View File

@@ -207,19 +207,6 @@ export default function WorkspaceAndProjectList({
</div>
))}
</Box>
<Text className="font-medium" color="secondary">
Looking for your old apps? They&apos;re still on{' '}
<Link
href="https://console.nhost.io"
target="_blank"
rel="noreferrer"
underline="always"
>
console.nhost.io
</Link>{' '}
during this beta.
</Text>
</Box>
);
}

View File

@@ -119,6 +119,9 @@ function OverviewDeploymentList() {
const liveDeploymentId = getLastLiveDeployment(deployments);
const { deployments: scheduledOrPendingDeployments } =
scheduledOrPendingDeploymentsData || { deployments: [] };
const isDeploymentInProgress = deployments?.some((deployment) =>
['PENDING', 'SCHEDULED'].includes(deployment.deploymentStatus),
);
return (
<List
@@ -131,7 +134,10 @@ function OverviewDeploymentList() {
deployment={deployment}
isLive={deployment.id === liveDeploymentId}
showRedeploy={index === 0}
disableRedeploy={scheduledOrPendingDeployments?.length > 0}
disableRedeploy={
scheduledOrPendingDeployments?.length > 0 ||
isDeploymentInProgress
}
/>
{index !== deployments.length - 1 && <Divider component="li" />}

View File

@@ -107,7 +107,7 @@ export default function SystemEnvironmentVariableSettings() {
? `${getHasuraConsoleServiceUrl()}/console`
: generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.region,
'hasura',
defaultLocalBackendSlugs,
{ ...defaultRemoteBackendSlugs, hasura: '/console' },

View File

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

View File

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

View File

@@ -217,7 +217,7 @@ export default function AppleProviderSettings() {
id="redirectUrl"
defaultValue={`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/apple/callback`}
className="col-span-2"
@@ -236,7 +236,7 @@ export default function AppleProviderSettings() {
copy(
`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/apple/callback`,
'Redirect URL',

View File

@@ -163,7 +163,7 @@ export default function AzureADProviderSettings() {
id="redirectUrl"
defaultValue={`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/azuread/callback`}
className="col-span-2"
@@ -182,7 +182,7 @@ export default function AzureADProviderSettings() {
copy(
`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/azuread/callback`,
'Redirect URL',

View File

@@ -139,7 +139,7 @@ export default function DiscordProviderSettings() {
label="Redirect URL"
defaultValue={`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/discord/callback`}
disabled
@@ -154,7 +154,7 @@ export default function DiscordProviderSettings() {
copy(
`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/discord/callback`,
'Redirect URL',

View File

@@ -139,7 +139,7 @@ export default function FacebookProviderSettings() {
label="Redirect URL"
defaultValue={`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/facebook/callback`}
disabled
@@ -154,7 +154,7 @@ export default function FacebookProviderSettings() {
copy(
`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/facebook/callback`,
'Redirect URL',

View File

@@ -145,7 +145,7 @@ export default function GitHubProviderSettings() {
label="Redirect URL"
defaultValue={`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/github/callback`}
disabled
@@ -160,7 +160,7 @@ export default function GitHubProviderSettings() {
copy(
`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/github/callback`,
'Redirect URL',

View File

@@ -139,7 +139,7 @@ export default function GoogleProviderSettings() {
label="Redirect URL"
defaultValue={`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/google/callback`}
disabled
@@ -154,7 +154,7 @@ export default function GoogleProviderSettings() {
copy(
`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/google/callback`,
'Redirect URL',

View File

@@ -139,7 +139,7 @@ export default function LinkedInProviderSettings() {
label="Redirect URL"
defaultValue={`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/linkedin/callback`}
disabled
@@ -154,7 +154,7 @@ export default function LinkedInProviderSettings() {
copy(
`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/linkedin/callback`,
'Redirect URL',

View File

@@ -139,7 +139,7 @@ export default function SpotifyProviderSettings() {
label="Redirect URL"
defaultValue={`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/spotify/callback`}
disabled
@@ -154,7 +154,7 @@ export default function SpotifyProviderSettings() {
copy(
`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/spotify/callback`,
'Redirect URL',

View File

@@ -145,7 +145,7 @@ export default function TwitchProviderSettings() {
label="Redirect URL"
defaultValue={`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/twitch/callback`}
disabled
@@ -160,7 +160,7 @@ export default function TwitchProviderSettings() {
copy(
`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/twitch/callback`,
'Redirect URL',

View File

@@ -167,7 +167,7 @@ export default function TwitterProviderSettings() {
id="redirectUrl"
defaultValue={`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/twitter/callback`}
className="col-span-2"
@@ -186,7 +186,7 @@ export default function TwitterProviderSettings() {
copy(
`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/twitter/callback`,
'Redirect URL',

View File

@@ -138,7 +138,7 @@ export default function WindowsLiveProviderSettings() {
label="Redirect URL"
defaultValue={`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/windowslive/callback`}
disabled
@@ -153,7 +153,7 @@ export default function WindowsLiveProviderSettings() {
copy(
`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/windowslive/callback`,
'Redirect URL',

View File

@@ -184,7 +184,7 @@ export default function WorkOsProviderSettings() {
id="redirectUrl"
defaultValue={`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/workos/callback`}
className="col-span-2"
@@ -203,7 +203,7 @@ export default function WorkOsProviderSettings() {
copy(
`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'auth',
)}/signin/provider/workos/callback`,
'Redirect URL',

View File

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

View File

@@ -1,4 +1,4 @@
export * from './Tooltip';
export { default } from './Tooltip';
export { default as Tooltip, default } from './Tooltip';
export * from './useTooltip';
export { default as useTooltip } from './useTooltip';

View File

@@ -68,7 +68,7 @@ export default function CreateUserForm({
const baseAuthUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region?.awsName,
currentProject?.region,
'auth',
);

View File

@@ -4,6 +4,7 @@ import { ApplicationStatus } from '@/types/application';
import { GetWorkspaceAndProjectDocument } from '@/utils/__generated__/graphql';
import { getHasuraAdminSecret } from '@/utils/env';
import { useNhostClient, useUserData } from '@nhost/nextjs';
import type { RefetchOptions } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import { useMemo } from 'react';
@@ -28,7 +29,7 @@ export interface UseCurrentWorkspaceAndProjectReturnType {
/**
* Refetch the query.
*/
refetch: (options?: any) => Promise<any>;
refetch: (options?: RefetchOptions) => Promise<any>;
}
export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndProjectReturnType {
@@ -49,7 +50,7 @@ export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndP
isFetching,
refetch,
} = useQuery(
['currentWorkspaceAndProject', workspaceSlug],
['currentWorkspaceAndProject', workspaceSlug, appSlug],
() =>
client.graphql.request<{
workspaces: Workspace[];
@@ -98,6 +99,7 @@ export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndP
countryCode: null,
city: null,
awsName: null,
domain: null,
},
isProvisioned: true,
createdAt: new Date().toISOString(),
@@ -108,6 +110,11 @@ export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndP
nhostBaseFolder: null,
plan: null,
config: {
observability: {
grafana: {
adminPassword: 'admin',
},
},
hasura: {
adminSecret: getHasuraAdminSecret(),
},

View File

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

View File

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

View File

@@ -0,0 +1,86 @@
import useGitHubModal from '@/components/applications/github/useGitHubModal';
import { useDialog } from '@/components/common/DialogProvider';
import GithubIcon from '@/components/icons/GithubIcon';
import SettingsContainer from '@/components/settings/SettingsContainer';
import { useUI } from '@/context/UIContext';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button';
import Text from '@/ui/v2/Text/Text';
import { useUpdateApplicationMutation } from '@/utils/__generated__/graphql';
import { triggerToast } from '@/utils/toast';
export default function GitConnectionSettings() {
const { maintenanceActive } = useUI();
const { currentProject, refetch } = useCurrentWorkspaceAndProject();
const [updateApp] = useUpdateApplicationMutation();
const { openAlertDialog } = useDialog();
const { openGitHubModal } = useGitHubModal();
function handleConnect() {
openAlertDialog({
title: 'Disconnect GitHub Repository',
payload: (
<p>
Are you sure you want to disconnect{' '}
<b>{currentProject.githubRepository.fullName}</b>?
</p>
),
props: {
primaryButtonText: 'Disconnect GitHub Repository',
primaryButtonColor: 'error',
onPrimaryAction: async () => {
await updateApp({
variables: {
appId: currentProject.id,
app: {
githubRepositoryId: null,
},
},
});
triggerToast(
`Successfully disconnected GitHub repository from ${currentProject.name}.`,
);
await refetch();
},
},
});
}
return (
<SettingsContainer
title="Git Repository"
description="Create Deployments for commits pushed to your Git repository."
docsLink="https://docs.nhost.io/platform/github-integration"
slotProps={{ submitButton: { className: 'hidden' } }}
className="grid grid-cols-5"
>
{!currentProject.githubRepository ? (
<Button
onClick={openGitHubModal}
className="col-span-5 grid grid-flow-col gap-1.5 xs:col-span-3 lg:col-span-2"
startIcon={<GithubIcon className="h-4 w-4 self-center" />}
disabled={maintenanceActive}
>
Connect to GitHub
</Button>
) : (
<Box className="col-span-5 flex flex-row place-content-between items-center rounded-lg border px-4 py-4">
<div className="ml-2 flex flex-row">
<GithubIcon className="mr-1.5 h-7 w-7 self-center" />
<Text className="self-center font-normal">
{currentProject.githubRepository.fullName}
</Text>
</div>
<Button
disabled={maintenanceActive}
variant="borderless"
onClick={handleConnect}
>
Disconnect
</Button>
</Box>
)}
</SettingsContainer>
);
}

View File

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

View File

@@ -20,7 +20,7 @@ subscription ScheduledOrPendingDeploymentsSub($appId: uuid!) {
subscription LatestLiveDeploymentSub($appId: uuid!) {
deployments(
where: { deploymentStatus: { _eq: "DEPLOYED" }, appId: { _eq: $appId } }
order_by: { deploymentEndedAt: desc }
order_by: { deploymentStartedAt: desc }
limit: 1
offset: 0
) {

View File

@@ -36,6 +36,7 @@ fragment Project on apps {
id
countryCode
awsName
domain
city
}
plan {
@@ -47,7 +48,7 @@ fragment Project on apps {
githubRepository {
fullName
}
deployments(limit: 4, order_by: { deploymentEndedAt: desc }) {
deployments(limit: 4, order_by: { deploymentStartedAt: desc }) {
id
commitSHA
commitMessage

View File

@@ -12,7 +12,7 @@ export default function useIsHealthy() {
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region?.awsName,
currentProject?.region,
'auth',
);

View File

@@ -41,7 +41,7 @@ export default function useCreateColumnMutation({
const { currentProject } = useCurrentWorkspaceAndProject();
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.region,
'hasura',
);
const mutationFn = isPlatform ? createColumn : createColumnMigration;

View File

@@ -43,7 +43,7 @@ export default function useCreateRecordMutation<TData extends object = {}>({
const { currentProject } = useCurrentWorkspaceAndProject();
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.region,
'hasura',
);

View File

@@ -39,7 +39,7 @@ export default function useCreateTableMutation({
const { currentProject } = useCurrentWorkspaceAndProject();
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.region,
'hasura',
);
const mutationFn = isPlatform ? createTable : createTableMigration;

View File

@@ -41,7 +41,7 @@ export default function useDatabaseQuery(
const { currentProject } = useCurrentWorkspaceAndProject();
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.region,
'hasura',
);

View File

@@ -42,7 +42,7 @@ export default function useDeleteColumnMutation({
const { currentProject } = useCurrentWorkspaceAndProject();
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.region,
'hasura',
);
const mutationFn = isPlatform ? deleteColumn : deleteColumnMigration;

View File

@@ -39,7 +39,7 @@ export default function useDeleteRecordMutation({
const { currentProject } = useCurrentWorkspaceAndProject();
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.region,
'hasura',
);

View File

@@ -37,7 +37,7 @@ export default function useDeleteTableMutation({
const { currentProject } = useCurrentWorkspaceAndProject();
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.region,
'hasura',
);
const mutationFn = isPlatform ? deleteTable : deleteTableMigration;

View File

@@ -44,7 +44,7 @@ export default function useManagePermissionMutation({
const { currentProject } = useCurrentWorkspaceAndProject();
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.region,
'hasura',
);

View File

@@ -42,7 +42,7 @@ export default function useMetadataQuery(
const { currentProject } = useCurrentWorkspaceAndProject();
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.region,
'hasura',
);

View File

@@ -42,7 +42,7 @@ export default function useTableQuery(
const { currentProject } = useCurrentWorkspaceAndProject();
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.region,
'hasura',
);

View File

@@ -37,7 +37,7 @@ export default function useTrackForeignKeyRelationMutation({
const { currentProject } = useCurrentWorkspaceAndProject();
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.region,
'hasura',
);
const mutationFn = isPlatform

View File

@@ -37,7 +37,7 @@ export default function useTrackTableMutation({
const { currentProject } = useCurrentWorkspaceAndProject();
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.region,
'hasura',
);
const mutationFn = isPlatform ? trackTable : trackTableMigration;

View File

@@ -42,7 +42,7 @@ export default function useUpdateColumnMutation({
const { currentProject } = useCurrentWorkspaceAndProject();
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.region,
'hasura',
);
const mutationFn = isPlatform ? updateColumn : updateColumnMigration;

View File

@@ -43,7 +43,7 @@ export default function useUpdateRecordMutation<TData extends object = {}>({
const { currentProject } = useCurrentWorkspaceAndProject();
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.region,
'hasura',
);

View File

@@ -38,7 +38,7 @@ export default function useUpdateTableMutation({
const { currentProject } = useCurrentWorkspaceAndProject();
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.region,
'hasura',
);
const mutationFn = isPlatform ? updateTable : updateTableMigration;

View File

@@ -1,12 +1,12 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import useIsPlatform from '@/hooks/common/useIsPlatform';
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl/generateAppServiceUrl';
import {
getAuthServiceUrl,
getFunctionsServiceUrl,
getGraphqlServiceUrl,
getStorageServiceUrl,
} from '@/utils/env';
import { isDevOrStaging } from '@/utils/helpers';
import type { NhostNextClientConstructorParams } from '@nhost/nextjs';
import { NhostClient } from '@nhost/nextjs';
@@ -43,11 +43,32 @@ export function useAppClient(
});
}
const authUrl = generateAppServiceUrl(
currentProject.subdomain,
currentProject.region,
'auth',
);
const graphqlUrl = generateAppServiceUrl(
currentProject.subdomain,
currentProject.region,
'graphql',
);
const storageUrl = generateAppServiceUrl(
currentProject.subdomain,
currentProject.region,
'storage',
);
const functionsUrl = generateAppServiceUrl(
currentProject.subdomain,
currentProject.region,
'functions',
);
return new NhostClient({
subdomain: currentProject.subdomain,
region: isDevOrStaging()
? `${currentProject.region.awsName}.staging`
: currentProject.region.awsName,
authUrl,
graphqlUrl,
storageUrl,
functionsUrl,
...options,
});
}

View File

@@ -69,7 +69,7 @@ export default function useFiles({
) => {
const fetchUrl = `${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'storage',
)}/files/${file.id}`;

View File

@@ -10,20 +10,21 @@ import { useMemo } from 'react';
*/
export function useRemoteApplicationGQLClient() {
const { currentProject, loading } = useCurrentWorkspaceAndProject();
const serviceUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region,
'graphql',
);
const userApplicationClient = useMemo(() => {
if (loading) {
if (loading || !serviceUrl) {
return new ApolloClient({ cache: new InMemoryCache() });
}
return new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
uri: generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region.awsName,
'graphql',
),
uri: serviceUrl,
headers: {
'x-hasura-admin-secret':
process.env.NEXT_PUBLIC_ENV === 'dev'
@@ -32,12 +33,7 @@ export function useRemoteApplicationGQLClient() {
},
}),
});
}, [
loading,
currentProject?.subdomain,
currentProject?.region.awsName,
currentProject?.config?.hasura.adminSecret,
]);
}, [loading, serviceUrl, currentProject?.config?.hasura.adminSecret]);
return userApplicationClient;
}

View File

@@ -262,7 +262,7 @@ export default function GraphQLPage() {
const appUrl = generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'graphql',
);

View File

@@ -106,7 +106,7 @@ export default function MetricsPage() {
<Button
href={generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'grafana',
)}
// Both `target` and `rel` are available when `href` is set. This is

View File

@@ -3,7 +3,6 @@ import SettingsLayout from '@/components/settings/SettingsLayout';
import { useGetDatabaseConnectionInfoQuery } from '@/generated/graphql';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import { isDevOrStaging } from '@/utils/helpers';
import type { ReactElement } from 'react';
import SettingsContainer from '@/components/settings/SettingsContainer';
@@ -15,14 +14,17 @@ import type { InputProps } from '@/ui/v2/Input';
import Input from '@/ui/v2/Input';
import InputAdornment from '@/ui/v2/InputAdornment';
import CopyIcon from '@/ui/v2/icons/CopyIcon';
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl/generateAppServiceUrl';
import { copy } from '@/utils/copy';
export default function DatabaseSettingsPage() {
const { currentProject } = useCurrentWorkspaceAndProject();
const postgresHost = `${currentProject.subdomain}.db.${
currentProject.region.awsName
}.${isDevOrStaging() ? 'staging.nhost' : 'nhost'}.run`;
const postgresHost = generateAppServiceUrl(
currentProject.subdomain,
currentProject.region,
'db',
).replace('https://', '');
const { data, loading, error } = useGetDatabaseConnectionInfoQuery({
variables: {

View File

@@ -13,12 +13,12 @@ import {
usePauseApplicationMutation,
useUpdateApplicationMutation,
} from '@/generated/graphql';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import Input from '@/ui/v2/Input';
import { discordAnnounce } from '@/utils/discordAnnounce';
import { slugifyString } from '@/utils/helpers';
import getServerError from '@/utils/settings/getServerError';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import { useApolloClient } from '@apollo/client';
import { yupResolver } from '@hookform/resolvers/yup';
import { useRouter } from 'next/router';
import type { ReactElement } from 'react';
@@ -38,18 +38,22 @@ export type ProjectNameValidationSchema = Yup.InferType<
>;
export default function SettingsGeneralPage() {
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
const {
currentWorkspace,
currentProject,
loading,
refetch: refetchWorkspaceAndProject,
} = useCurrentWorkspaceAndProject();
const isOwner = useIsCurrentUserOwner();
const { openDialog, openAlertDialog, closeDialog } = useDialog();
const [updateApp] = useUpdateApplicationMutation();
const client = useApolloClient();
const [pauseApplication] = usePauseApplicationMutation({
variables: { appId: currentProject?.id },
refetchQueries: [GetAllWorkspacesAndProjectsDocument],
refetchQueries: [{ query: GetAllWorkspacesAndProjectsDocument }],
});
const [deleteApplication] = useDeleteApplicationMutation({
variables: { appId: currentProject?.id },
refetchQueries: [GetAllWorkspacesAndProjectsDocument],
refetchQueries: [{ query: GetAllWorkspacesAndProjectsDocument }],
});
const router = useRouter();
const { maintenanceActive } = useUI();
@@ -98,7 +102,7 @@ export default function SettingsGeneralPage() {
});
try {
await toast.promise(
const { data: updateAppData } = await toast.promise(
updateAppMutation,
{
loading: `Project name is being updated...`,
@@ -109,24 +113,24 @@ export default function SettingsGeneralPage() {
},
getToastStyleProps(),
);
const updateAppResult = updateAppData?.updateApp;
if (!updateAppResult) {
await discordAnnounce('Failed to update project name.');
return;
}
form.reset(undefined, { keepValues: true, keepDirty: false });
await refetchWorkspaceAndProject();
await router.replace(
`/${currentWorkspace.slug}/${updateAppResult.slug}/settings/general`,
);
} catch {
// Note: The toast will handle the error.
}
try {
form.reset(undefined, { keepValues: true, keepDirty: false });
await router.push(
`/${currentWorkspace.slug}/${newProjectSlug}/settings/general`,
);
await client.refetchQueries({
include: [GetAllWorkspacesAndProjectsDocument],
});
} catch (error) {
await discordAnnounce(
error.message ||
'An error occurred while trying to update application cache.',
);
}
}
async function handleDeleteApplication() {
@@ -161,6 +165,10 @@ export default function SettingsGeneralPage() {
await router.push('/');
}
if (loading) {
return <ActivityIndicator label="Loading project..." />;
}
return (
<Container
className="grid max-w-5xl grid-flow-row gap-8 bg-transparent"
@@ -195,7 +203,7 @@ export default function SettingsGeneralPage() {
</Form>
</FormProvider>
{currentProject.plan.isFree && (
{currentProject?.plan.isFree && (
<SettingsContainer
title="Pause Project"
description="While your project is paused, it will not be accessible. You can wake it up anytime after."

View File

@@ -1,104 +1,23 @@
import useGitHubModal from '@/components/applications/github/useGitHubModal';
import { useDialog } from '@/components/common/DialogProvider';
import GithubIcon from '@/components/icons/GithubIcon';
import Container from '@/components/layout/Container';
import SettingsContainer from '@/components/settings/SettingsContainer';
import SettingsLayout from '@/components/settings/SettingsLayout';
import BaseDirectorySettings from '@/components/settings/git/BaseDirectorySettings';
import DeploymentBranchSettings from '@/components/settings/git/DeploymentBranchSettings';
import { useUI } from '@/context/UIContext';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useUpdateApplicationMutation } from '@/generated/graphql';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button';
import Text from '@/ui/v2/Text';
import { triggerToast } from '@/utils/toast';
import { updateOwnCache } from '@/utils/updateOwnCache';
import { useApolloClient } from '@apollo/client';
import { BaseDirectorySettings } from '@/features/projects/settings/git/components/BaseDirectorySettings';
import { DeploymentBranchSettings } from '@/features/projects/settings/git/components/DeploymentBranchSettings';
import { GitConnectionSettings } from '@/features/projects/settings/git/components/GitConnectionSettings';
import type { ReactElement } from 'react';
export default function SettingsGitPage() {
const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject();
const { openGitHubModal } = useGitHubModal();
const { openAlertDialog } = useDialog();
const client = useApolloClient();
const [updateApp] = useUpdateApplicationMutation();
export default function GitSettingsPage() {
return (
<Container
className="grid max-w-5xl grid-flow-row gap-y-6 bg-transparent"
rootClassName="bg-transparent"
>
<SettingsContainer
title="Git Repository"
description="Create Deployments for commits pushed to your Git repository."
docsLink="https://docs.nhost.io/platform/github-integration"
slotProps={{ submitButton: { className: 'hidden' } }}
className="grid grid-cols-5"
>
{!currentProject.githubRepository ? (
<Button
onClick={openGitHubModal}
className="col-span-5 grid grid-flow-col gap-1.5 xs:col-span-3 lg:col-span-2"
startIcon={<GithubIcon className="h-4 w-4 self-center" />}
disabled={maintenanceActive}
>
Connect to GitHub
</Button>
) : (
<Box className="col-span-5 flex flex-row place-content-between items-center rounded-lg border px-4 py-4">
<div className="ml-2 flex flex-row">
<GithubIcon className="mr-1.5 h-7 w-7 self-center" />
<Text className="self-center font-normal">
{currentProject.githubRepository.fullName}
</Text>
</div>
<Button
disabled={maintenanceActive}
variant="borderless"
onClick={() => {
openAlertDialog({
title: 'Disconnect GitHub Repository',
payload: (
<p>
Are you sure you want to disconnect{' '}
<b>{currentProject.githubRepository.fullName}</b>?
</p>
),
props: {
primaryButtonText: 'Disconnect GitHub Repository',
primaryButtonColor: 'error',
onPrimaryAction: async () => {
await updateApp({
variables: {
appId: currentProject.id,
app: {
githubRepositoryId: null,
},
},
});
triggerToast(
`Successfully disconnected GitHub repository from ${currentProject.name}.`,
);
await updateOwnCache(client);
},
},
});
}}
>
Disconnect
</Button>
</Box>
)}
</SettingsContainer>
<GitConnectionSettings />
<DeploymentBranchSettings />
<BaseDirectorySettings />
</Container>
);
}
SettingsGitPage.getLayout = function getLayout(page: ReactElement) {
GitSettingsPage.getLayout = function getLayout(page: ReactElement) {
return <SettingsLayout>{page}</SettingsLayout>;
};

View File

@@ -19,7 +19,7 @@ export default function StoragePage() {
<NhostApolloProvider
graphqlUrl={generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
currentProject.region,
'graphql',
)}
fetchPolicy="cache-first"

View File

@@ -81,12 +81,6 @@ export default function IndexPage() {
</Button>
</NavLink>
</div>
<div>
<Text className="mt-9 opacity-70" sx={{ color: 'common.white' }}>
Looking for your old apps? They&apos;re still on
console.nhost.io during this beta.
</Text>
</div>
</div>
</Box>

View File

@@ -106,7 +106,7 @@ export function NewProjectPageContent({
const { submitState, setSubmitState } = useSubmitState();
const [insertApp] = useInsertApplicationMutation({
refetchQueries: [GetAllWorkspacesAndProjectsDocument],
refetchQueries: [{ query: GetAllWorkspacesAndProjectsDocument }],
});
// options

View File

@@ -54,6 +54,7 @@ export const mockApplication: Project = {
city: 'New York',
countryCode: 'US',
id: '1',
domain: 'nhost.run',
},
createdAt: new Date().toISOString(),
deployments: [],
@@ -70,6 +71,11 @@ export const mockApplication: Project = {
price: 0,
},
config: {
observability: {
grafana: {
adminPassword: 'admin',
},
},
hasura: {
adminSecret: 'nhost-admin-secret',
},

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
import type { ProjectFragment } from '@/utils/__generated__/graphql';
import { test, vi } from 'vitest';
import generateAppServiceUrl, {
defaultLocalBackendSlugs,
@@ -29,31 +30,45 @@ afterEach(() => {
process.env = { ...env };
});
const region: ProjectFragment['region'] = {
id: '1',
awsName: 'eu-west-1',
domain: 'nhost.run',
city: 'Dublin',
countryCode: 'IE',
};
const stagingRegion = { ...region, domain: 'staging.nhost.run' };
test('should generate a per service subdomain in remote mode', () => {
process.env.NEXT_PUBLIC_NHOST_PLATFORM = 'true';
process.env.NEXT_PUBLIC_ENV = 'production';
expect(generateAppServiceUrl('test', 'eu-west-1', 'auth')).toBe(
expect(generateAppServiceUrl('test', region, 'auth')).toBe(
'https://test.auth.eu-west-1.nhost.run/v1',
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'functions')).toBe(
expect(generateAppServiceUrl('test', region, 'db')).toBe(
'https://test.db.eu-west-1.nhost.run',
);
expect(generateAppServiceUrl('test', region, 'functions')).toBe(
'https://test.functions.eu-west-1.nhost.run/v1',
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'graphql')).toBe(
expect(generateAppServiceUrl('test', region, 'graphql')).toBe(
'https://test.graphql.eu-west-1.nhost.run/v1',
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'storage')).toBe(
expect(generateAppServiceUrl('test', region, 'storage')).toBe(
'https://test.storage.eu-west-1.nhost.run/v1',
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'hasura')).toBe(
expect(generateAppServiceUrl('test', region, 'hasura')).toBe(
'https://test.hasura.eu-west-1.nhost.run',
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'grafana')).toBe(
expect(generateAppServiceUrl('test', region, 'grafana')).toBe(
'https://test.grafana.eu-west-1.nhost.run',
);
});
@@ -62,54 +77,58 @@ test('should generate staging subdomains in staging environment', () => {
process.env.NEXT_PUBLIC_NHOST_PLATFORM = 'true';
process.env.NEXT_PUBLIC_ENV = 'staging';
expect(generateAppServiceUrl('test', 'eu-west-1', 'auth')).toBe(
expect(generateAppServiceUrl('test', stagingRegion, 'auth')).toBe(
'https://test.auth.eu-west-1.staging.nhost.run/v1',
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'functions')).toBe(
expect(generateAppServiceUrl('test', stagingRegion, 'db')).toBe(
'https://test.db.eu-west-1.staging.nhost.run',
);
expect(generateAppServiceUrl('test', stagingRegion, 'functions')).toBe(
'https://test.functions.eu-west-1.staging.nhost.run/v1',
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'graphql')).toBe(
expect(generateAppServiceUrl('test', stagingRegion, 'graphql')).toBe(
'https://test.graphql.eu-west-1.staging.nhost.run/v1',
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'storage')).toBe(
expect(generateAppServiceUrl('test', stagingRegion, 'storage')).toBe(
'https://test.storage.eu-west-1.staging.nhost.run/v1',
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'hasura')).toBe(
expect(generateAppServiceUrl('test', stagingRegion, 'hasura')).toBe(
'https://test.hasura.eu-west-1.staging.nhost.run',
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'grafana')).toBe(
expect(generateAppServiceUrl('test', stagingRegion, 'grafana')).toBe(
'https://test.grafana.eu-west-1.staging.nhost.run',
);
});
test('should generate no slug for Hasura and Grafana neither in local mode nor in remote mode', () => {
process.env.NEXT_PUBLIC_NHOST_HASURA_API_URL = 'http://localhost:8082';
process.env.NEXT_PUBLIC_ENV = 'staging';
expect(generateAppServiceUrl('test', 'eu-west-1', 'hasura')).toBe(
expect(generateAppServiceUrl('test', region, 'hasura')).toBe(
'http://localhost:8082',
);
process.env.NEXT_PUBLIC_NHOST_PLATFORM = 'true';
process.env.NEXT_PUBLIC_ENV = 'staging';
expect(generateAppServiceUrl('test', 'eu-west-1', 'hasura')).toBe(
expect(generateAppServiceUrl('test', stagingRegion, 'hasura')).toBe(
'https://test.hasura.eu-west-1.staging.nhost.run',
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'grafana')).toBe(
expect(generateAppServiceUrl('test', stagingRegion, 'grafana')).toBe(
'https://test.grafana.eu-west-1.staging.nhost.run',
);
process.env.NEXT_PUBLIC_ENV = 'production';
expect(generateAppServiceUrl('test', 'eu-west-1', 'hasura')).toBe(
expect(generateAppServiceUrl('test', region, 'hasura')).toBe(
'https://test.hasura.eu-west-1.nhost.run',
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'grafana')).toBe(
expect(generateAppServiceUrl('test', region, 'grafana')).toBe(
'https://test.grafana.eu-west-1.nhost.run',
);
});
@@ -119,48 +138,52 @@ test('should be able to override the default remote backend slugs', () => {
process.env.NEXT_PUBLIC_ENV = 'production';
expect(
generateAppServiceUrl(
'test',
'eu-west-1',
'hasura',
defaultLocalBackendSlugs,
{ ...defaultRemoteBackendSlugs, hasura: '/lorem-ipsum' },
),
generateAppServiceUrl('test', region, 'hasura', defaultLocalBackendSlugs, {
...defaultRemoteBackendSlugs,
hasura: '/lorem-ipsum',
}),
).toBe('https://test.hasura.eu-west-1.nhost.run/lorem-ipsum');
});
test('should construct service URLs based on environment variables', () => {
process.env.NEXT_PUBLIC_NHOST_HASURA_API_URL = 'https://localdev0.nhost.run';
expect(generateAppServiceUrl('test', 'eu-west-1', 'hasura')).toBe(
expect(generateAppServiceUrl('test', region, 'hasura')).toBe(
`https://localdev0.nhost.run`,
);
process.env.NEXT_PUBLIC_NHOST_AUTH_URL =
'https://localdev1.nhost.run/v1/auth';
expect(generateAppServiceUrl('test', 'eu-west-1', 'auth')).toBe(
expect(generateAppServiceUrl('test', region, 'auth')).toBe(
`https://localdev1.nhost.run/v1/auth`,
);
process.env.NEXT_PUBLIC_NHOST_DATABASE_URL =
'https://localdev2.nhost.run/v1/db';
expect(generateAppServiceUrl('test', region, 'db')).toBe(
`https://localdev2.nhost.run/v1/db`,
);
process.env.NEXT_PUBLIC_NHOST_STORAGE_URL =
'https://localdev2.nhost.run/v1/storage';
expect(generateAppServiceUrl('test', 'eu-west-1', 'storage')).toBe(
expect(generateAppServiceUrl('test', region, 'storage')).toBe(
'https://localdev2.nhost.run/v1/storage',
);
process.env.NEXT_PUBLIC_NHOST_GRAPHQL_URL =
'https://localdev3.nhost.run/v1/graphql';
expect(generateAppServiceUrl('test', 'eu-west-1', 'graphql')).toBe(
expect(generateAppServiceUrl('test', region, 'graphql')).toBe(
'https://localdev3.nhost.run/v1/graphql',
);
process.env.NEXT_PUBLIC_NHOST_FUNCTIONS_URL =
'https://localdev4.nhost.run/v1/functions';
expect(generateAppServiceUrl('test', 'eu-west-1', 'functions')).toBe(
expect(generateAppServiceUrl('test', region, 'functions')).toBe(
'https://localdev4.nhost.run/v1/functions',
);
});
@@ -169,19 +192,19 @@ test('should generate a basic subdomain with a custom port if provided', () => {
process.env.NEXT_PUBLIC_NHOST_BACKEND_URL = `http://localhost:1338`;
process.env.NEXT_PUBLIC_NHOST_PLATFORM = 'true';
expect(generateAppServiceUrl('test', 'eu-west-1', 'auth')).toBe(
expect(generateAppServiceUrl('test', region, 'auth')).toBe(
`http://localhost:1338/v1/auth`,
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'storage')).toBe(
expect(generateAppServiceUrl('test', region, 'storage')).toBe(
`http://localhost:1338/v1/files`,
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'graphql')).toBe(
expect(generateAppServiceUrl('test', region, 'graphql')).toBe(
`http://localhost:1338/v1/graphql`,
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'functions')).toBe(
expect(generateAppServiceUrl('test', region, 'functions')).toBe(
`http://localhost:1338/v1/functions`,
);
});

View File

@@ -1,5 +1,7 @@
import type { ProjectFragment } from '@/utils/__generated__/graphql';
import {
getAuthServiceUrl,
getDatabaseServiceUrl,
getFunctionsServiceUrl,
getGraphqlServiceUrl,
getHasuraApiUrl,
@@ -9,6 +11,7 @@ import {
export type NhostService =
| 'auth'
| 'db'
| 'graphql'
| 'functions'
| 'storage'
@@ -22,6 +25,7 @@ export type NhostService =
*/
export const defaultLocalBackendSlugs: Record<NhostService, string> = {
auth: '/v1/auth',
db: '',
graphql: '/v1/graphql',
functions: '/v1/functions',
storage: '/v1/files',
@@ -35,6 +39,7 @@ export const defaultLocalBackendSlugs: Record<NhostService, string> = {
*/
export const defaultRemoteBackendSlugs: Record<NhostService, string> = {
auth: '/v1',
db: '',
graphql: '/v1',
functions: '/v1',
storage: '/v1',
@@ -55,8 +60,8 @@ export const defaultRemoteBackendSlugs: Record<NhostService, string> = {
*/
export default function generateAppServiceUrl(
subdomain: string,
region: string,
service: 'auth' | 'graphql' | 'functions' | 'storage' | 'hasura' | 'grafana',
region: ProjectFragment['region'],
service: NhostService,
localBackendSlugs = defaultLocalBackendSlugs,
remoteBackendSlugs = defaultRemoteBackendSlugs,
) {
@@ -65,6 +70,7 @@ export default function generateAppServiceUrl(
if (!IS_PLATFORM) {
const serviceUrls: Record<typeof service, string> = {
auth: getAuthServiceUrl(),
db: getDatabaseServiceUrl(),
graphql: getGraphqlServiceUrl(),
storage: getStorageServiceUrl(),
functions: getFunctionsServiceUrl(),
@@ -87,9 +93,14 @@ export default function generateAppServiceUrl(
return `${process.env.NEXT_PUBLIC_NHOST_BACKEND_URL}${localBackendSlugs[service]}`;
}
if (process.env.NEXT_PUBLIC_ENV === 'staging') {
return `https://${subdomain}.${service}.${region}.staging.nhost.run${remoteBackendSlugs[service]}`;
}
const constructedDomain = [
subdomain,
service,
region?.awsName,
region?.domain || 'nhost.run',
]
.filter(Boolean)
.join('.');
return `https://${subdomain}.${service}.${region}.nhost.run${remoteBackendSlugs[service]}`;
return `https://${constructedDomain}${remoteBackendSlugs[service]}`;
}

View File

@@ -31,6 +31,13 @@ export function getAuthServiceUrl() {
);
}
/**
* Custom URL of the Database service.
*/
export function getDatabaseServiceUrl() {
return process.env.NEXT_PUBLIC_NHOST_DATABASE_URL || 'local.db.nhost.run';
}
/**
* Custom URL of the GraphQL service.
*/

View File

@@ -1,17 +0,0 @@
import type { ApolloClient, ApolloQueryResult } from '@apollo/client';
import { GetAllWorkspacesAndProjectsDocument } from './__generated__/graphql';
/**
* This function will refetch the main query we use for the cache
* of the user's workspaces and applications.
* @param client The apollo client instance.
*/
export async function updateOwnCache(
client: ApolloClient<any>,
): Promise<ApolloQueryResult<any>[]> {
return client.refetchQueries({
include: [GetAllWorkspacesAndProjectsDocument],
});
}
export default updateOwnCache;

View File

@@ -1,5 +1,11 @@
# @nhost/docs
## 0.2.1
### Patch Changes
- 203bc97f: feat(pat): add support for personal access tokens
## 0.2.0
### Minor Changes

View File

@@ -0,0 +1,82 @@
---
title: Sign In with Personal Access Tokens
sidebar_label: Personal Access Tokens
slug: /authentication/sign-in-with-personal-access-tokens
---
Nhost allows you to sign in users with personal access tokens (PAT) which is a way to sign in users without an email address or password.
## Configuration
:::info
Personal Access Tokens can only be created through Hasura Auth or the [Nhost JavaScript SDK](/reference/javascript) at the moment.
:::
## Create a Personal Access Token
Users must be signed in to create a personal access token. Once a user is signed in, they can create a personal access token.
**Example:** Create a personal access token:
```tsx
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24 * 30) // 30 days
const metadata = { name: 'Example PAT' } // Optional metadata
const { data, error } = await nhost.auth.createPAT(expiresAt, metadata)
// Something unexpected happened
if (error) {
console.error(error)
return
}
console.log(data.id) // The personal access token ID (can be used to delete the token later)
console.log(data.personalAccessToken) // The personal access token
```
Users can create multiple personal access tokens. Each token can have a different expiration date and metadata.
## Sign In
Once a user has created a personal access token, they can use it to sign in.
**Example:** Sign in with a personal access token:
```tsx
const { error, session } = await nhost.auth.signInPAT('<personal-access-token>')
// Something unexpected happened
if (error) {
console.log(error)
return
}
// User is signed in
console.log(session.user)
```
## List or Remove Personal Access Tokens
To list and remove personal access tokens, use GraphQL and set permissions on the `auth.refresh_tokens` table:
**Example:** Get all personal access tokens for a user:
```graphql
query personalAccessTokens($userId: uuid!) {
authRefreshTokens(where: { _and: [{ userId: { _eq: $userId } }, { type: { _eq: pat } }] }) {
id
expiresAt
metadata
}
}
```
**Example:** Remove a personal access token:
```graphql
mutation removePersonalAccessToken($id: uuid!) {
deleteAuthRefreshToken(id: $id) {
id
}
}
```

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/docs",
"version": "0.2.0",
"version": "0.2.1",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@@ -16,9 +16,9 @@
},
"dependencies": {
"@algolia/client-search": "^4.9.1",
"@docusaurus/core": "2.4.0",
"@docusaurus/plugin-sitemap": "2.4.0",
"@docusaurus/preset-classic": "2.4.0",
"@docusaurus/core": "2.4.1",
"@docusaurus/plugin-sitemap": "2.4.1",
"@docusaurus/preset-classic": "2.4.1",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1",
"docusaurus-plugin-image-zoom": "^0.1.1",
@@ -30,7 +30,7 @@
"unist-util-visit": "^2.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.4.0",
"@docusaurus/module-type-aliases": "2.4.1",
"@tsconfig/docusaurus": "^1.0.6",
"typescript": "^4.8.4"
},

View File

@@ -0,0 +1,3 @@
NHOST_ACCOUNT_PAT=34f74930-09c0-4af5-a8d5-28fad78e3414
NHOST_ACCOUNT_EMAIL=cli@nhost.io
NHOST_ACCOUNT_PASSWORD=Admin1234!

5
examples/cli/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.nhost
web/node_modules
node_modules
functions/node_modules
.env

View File

@@ -0,0 +1,9 @@
# @nhost-examples/cli
## 0.1.1
### Patch Changes
- 203bc97f: feat(pat): add support for personal access tokens
- Updated dependencies [7fea29a8]
- @nhost/nhost-js@2.2.5

109
examples/cli/README.md Normal file
View File

@@ -0,0 +1,109 @@
# Node.js CLI tool example with Personal Access Tokens
Todo app that shows how to use:
- [Nhost](https://nhost.io/)
- [Node.js](https://nodejs.org/en/)
- Personal Access Tokens
## Get Started
There is a migration script that creates a service account. The credentials of
this account are used to authenticate the CLI tool.
By default these credentials are used:
Email address: `cli@nhost.io`
Password: `Admin1234!`
1. Clone the repository
```sh
git clone https://github.com/nhost/nhost
cd nhost
```
2. Install dependencies
```sh
pnpm install
```
3. Start the Nhost backend
```sh
nhost up
```
4. Run the help command to see the available commands
```sh
pnpm start --help
```
## Environment Variables
Credentials can be provided through the command line or by using environment
variables. Create a `.env` file in the root folder of the example. See `.env.example`
for an example configuration.
You can specify the following environment variables:
- `NHOST_ACCOUNT_PAT` - The personal access token of the service account. If provided, the email address and password will be ignored.
- `NHOST_ACCOUNT_EMAIL` - The email address of the service account.
- `NHOST_ACCOUNT_PASSWORD` - The password of the service account.
> If email and password are provided, the CLI tool will sign in first to create a personal access token for the account. This token will then be used to authenticate and make requests to the Nhost backend as the service account without having to provide the email address and password. Make sure to copy the token from the output and use it in the `NHOST_ACCOUNT_PAT` environment variable later.
## Commands
Environment variables should be placed in a `.env` file in the root folder of
the example. See `.env.example` for an example configuration.
### Use an existing personal access token to authenticate
```sh
pnpm start --token <personal-access-token>
```
or using environment variables:
```sh
NHOST_ACCOUNT_PAT=<personal-access-token> pnpm start
```
### Create a new personal access token
```sh
pnpm start --email cli@nhost.io --password Admin1234! --create-token --expires-at 2040-01-01 --token-name "CLI Token"
```
or using environment variables:
```sh
NHOST_ACCOUNT_EMAIL=cli@nhost.io NHOST_ACCOUNT_PASSWORD=Admin1234! pnpm start --create-token --expires-at 2040-01-01 --token-name "CLI Token"
```
### Create a new book
```sh
pnpm start --token <personal-access-token> --create-book <title>
```
or using environment variables:
```sh
NHOST_ACCOUNT_PAT=<personal-access-token> pnpm start --create-book <title>
```
### Delete a book
```sh
pnpm start --token <personal-access-token> --delete-book <id>
```
or using environment variables:
```sh
NHOST_ACCOUNT_PAT=<personal-access-token> pnpm start --delete-book <id>
```

View File

@@ -0,0 +1,139 @@
metadata_directory: metadata
services:
auth:
image: nhost/hasura-auth:0.20.0
hasura:
environment:
hasura_graphql_enable_remote_schema_permissions: true
minio:
environment:
minio_root_password: minioaccesskey123123
minio_root_user: minioaccesskey123123
postgres:
environment:
postgres_password: postgres
postgres_user: postgres
auth:
access_control:
email:
allowed_email_domains: ''
allowed_emails: ''
blocked_email_domains: ''
blocked_emails: ''
url:
allowed_redirect_urls: ''
anonymous_users_enabled: false
client_url: http://localhost:3000
disable_new_users: false
email:
enabled: false
passwordless:
enabled: false
signin_email_verified_required: false
template_fetch_url: ''
gravatar:
default: ''
enabled: true
rating: ''
locale:
allowed: en
default: en
password:
hibp_enabled: false
min_length: 3
provider:
apple:
client_id: ''
enabled: false
key_id: ''
private_key: ''
scope: name,email
team_id: ''
bitbucket:
client_id: ''
client_secret: ''
enabled: false
facebook:
client_id: ''
client_secret: ''
enabled: false
scope: email,photos,displayName
github:
client_id: ''
client_secret: ''
enabled: false
scope: user:email
token_url: ''
user_profile_url: ''
gitlab:
base_url: ''
client_id: ''
client_secret: ''
enabled: false
scope: read_user
google:
client_id: ''
client_secret: ''
enabled: false
scope: email,profile
linkedin:
client_id: ''
client_secret: ''
enabled: false
scope: r_emailaddress,r_liteprofile
spotify:
client_id: ''
client_secret: ''
enabled: false
scope: user-read-email,user-read-private
strava:
client_id: ''
client_secret: ''
enabled: false
twilio:
account_sid: ''
auth_token: ''
enabled: false
messaging_service_id: ''
twitter:
consumer_key: ''
consumer_secret: ''
enabled: false
windows_live:
client_id: ''
client_secret: ''
enabled: false
scope: wl.basic,wl.emails,wl.contacts_emails
sms:
enabled: false
passwordless:
enabled: false
provider:
twilio:
account_sid: ''
auth_token: ''
from: ''
messaging_service_id: ''
smtp:
host: mailhog
method: ''
pass: password
port: 1025
secure: false
sender: hasura-auth@example.com
user: user
token:
access:
expires_in: 900
refresh:
expires_in: 43200
user:
allowed_roles: user,me
default_allowed_roles: user,me
default_role: user
mfa:
enabled: false
issuer: nhost
storage:
force_download_for_content_types: text/html,application/javascript
version: 3

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h2>Потвърдете смяната на вашия имейл</h2>
<p>Използвайте посочения линк, за да повърдите смяната на имейл:</p>
<p>
<a href="${link}">
Смени имейл
</a>
</p>
</body>
</html>

View File

@@ -0,0 +1 @@
Потвърждение за смяна на имейл

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h2>Потвърдете вашия имейл</h2>
<p>Използвайте посочения линк, за да потвърдите вашия имейл:</p>
<p>
<a href="${link}">
Потвърдете имейл
</a>
</p>
</body>
</html>

View File

@@ -0,0 +1 @@
Потвърждаване на имейл

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h2>Смяна на парола</h2>
<p>Използвайте посочения линк, за да смените вашата парола:</p>
<p>
<a href="${link}">
Смяна на парола
</a>
</p>
</body>
</html>

View File

@@ -0,0 +1 @@
Смяна на парола

View File

@@ -0,0 +1 @@
Вашият код е ${code}.

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h2>Магически линк за вход</h2>
<p>Използвайте посочения линк за защитен и бърз вход:</p>
<p>
<a href="${link}">
Вход
</a>
</p>
</body>
</html>

View File

@@ -0,0 +1 @@
Магически линк за вход

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h2>Potvrzení změny emailové adresy</h2>
<p>Použijte tento odkaz k potvrzení změny emailové adresy:</p>
<p>
<a href="${link}">
Změnit email
</a>
</p>
</body>
</html>

View File

@@ -0,0 +1 @@
Změna vaší emailové adresy

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h2>Ověření emailové adresy</h2>
<p>Použijte tento odkaz k ověření vaší emailové adresy:</p>
<p>
<a href="${link}">
Ověřit emailovou adresu
</a>
</p>
</body>
</html>

View File

@@ -0,0 +1 @@
Ověření vaší emailové adresy

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h2>Obnova hesla</h2>
<p>Použijte tento odkaz k obnovení vašeho hesla:</p>
<p>
<a href="${link}">
Obnova hesla
</a>
</p>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More