Compare commits
95 Commits
@nhost/das
...
@nhost/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
809a2d35f8 | ||
|
|
e17ec7fce7 | ||
|
|
241175b158 | ||
|
|
51ceaf2696 | ||
|
|
490b77cde4 | ||
|
|
7fea29a8b4 | ||
|
|
1a34e011ad | ||
|
|
395839f449 | ||
|
|
399009d66a | ||
|
|
12eb236c4a | ||
|
|
2218e5cd5b | ||
|
|
2dcf1b38c6 | ||
|
|
ad49c92879 | ||
|
|
13dd57eeb4 | ||
|
|
1345741b11 | ||
|
|
511615f176 | ||
|
|
1198c201f1 | ||
|
|
f9b81a2ae9 | ||
|
|
dff0894f37 | ||
|
|
80f3645d57 | ||
|
|
7ddb9a654e | ||
|
|
71f3be15d8 | ||
|
|
96a9070836 | ||
|
|
329e5a91b9 | ||
|
|
6d559d6e23 | ||
|
|
f4f1450d06 | ||
|
|
a1eea9df7d | ||
|
|
26f2b665e6 | ||
|
|
224a5cc805 | ||
|
|
59125b3c77 | ||
|
|
5b69e3efd8 | ||
|
|
c9a444d048 | ||
|
|
2eeac45718 | ||
|
|
fe8ca8aba6 | ||
|
|
086ee46b08 | ||
|
|
203bc97f51 | ||
|
|
b24af44aac | ||
|
|
cdaa6d4e73 | ||
|
|
09e2c8f5c7 | ||
|
|
4581677830 | ||
|
|
7bfa6c9f93 | ||
|
|
80ef430d70 | ||
|
|
bad8af0fd1 | ||
|
|
69caa34c43 | ||
|
|
1d898e2893 | ||
|
|
e87621cbde | ||
|
|
0d6fc42158 | ||
|
|
f6fbee6b13 | ||
|
|
1230b72222 | ||
|
|
6cc7704555 | ||
|
|
c0954dec09 | ||
|
|
6c25480a7a | ||
|
|
da03bf390c | ||
|
|
3b513be9f2 | ||
|
|
e450e9d636 | ||
|
|
ed1ee10879 | ||
|
|
a6120bf366 | ||
|
|
349aac369e | ||
|
|
5a84362c80 | ||
|
|
b23dc058a6 | ||
|
|
ad0dda7493 | ||
|
|
4063507d59 | ||
|
|
f59a77b1c8 | ||
|
|
30686bc4ce | ||
|
|
0c4ac8d368 | ||
|
|
85439307a9 | ||
|
|
0d8baa4065 | ||
|
|
7da0e5e256 | ||
|
|
4bca94425e | ||
|
|
9f948385c0 | ||
|
|
294c504b61 | ||
|
|
1469ec2969 | ||
|
|
8229101efe | ||
|
|
afad1778f8 | ||
|
|
28fc7b84c7 | ||
|
|
3f478a4e3c | ||
|
|
aa54666941 | ||
|
|
20fb69faba | ||
|
|
1caeb2a548 | ||
|
|
6356c5a2c8 | ||
|
|
6ec1dd3248 | ||
|
|
8a4b5031dc | ||
|
|
726c33d1b2 | ||
|
|
11b9cfbc0d | ||
|
|
79aaa91e67 | ||
|
|
df4d24320a | ||
|
|
baa3ef794e | ||
|
|
da7ffbe523 | ||
|
|
15a985e079 | ||
|
|
8ff00a4258 | ||
|
|
7e27d7c0a1 | ||
|
|
49f9b8372a | ||
|
|
3ca70554c8 | ||
|
|
077b200510 | ||
|
|
2f220db84a |
@@ -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
|
||||
|
||||
@@ -36,6 +36,7 @@ export default defineConfig({
|
||||
}
|
||||
},
|
||||
build: {
|
||||
target: 'es2019',
|
||||
sourcemap: true,
|
||||
lib: {
|
||||
entry,
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 .
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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" />}
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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.',
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -33,7 +33,7 @@ export function HasuraData({ close }: HasuraDataProps) {
|
||||
? `${getHasuraConsoleServiceUrl()}`
|
||||
: generateAppServiceUrl(
|
||||
currentProject?.subdomain,
|
||||
currentProject?.region.awsName,
|
||||
currentProject?.region,
|
||||
'hasura',
|
||||
defaultLocalBackendSlugs,
|
||||
{ ...defaultRemoteBackendSlugs, hasura: '/console' },
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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')} />
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -207,19 +207,6 @@ export default function WorkspaceAndProjectList({
|
||||
</div>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
<Text className="font-medium" color="secondary">
|
||||
Looking for your old apps? They're still on{' '}
|
||||
<Link
|
||||
href="https://console.nhost.io"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
underline="always"
|
||||
>
|
||||
console.nhost.io
|
||||
</Link>{' '}
|
||||
during this beta.
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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" />}
|
||||
|
||||
@@ -107,7 +107,7 @@ export default function SystemEnvironmentVariableSettings() {
|
||||
? `${getHasuraConsoleServiceUrl()}/console`
|
||||
: generateAppServiceUrl(
|
||||
currentProject?.subdomain,
|
||||
currentProject?.region.awsName,
|
||||
currentProject?.region,
|
||||
'hasura',
|
||||
defaultLocalBackendSlugs,
|
||||
{ ...defaultRemoteBackendSlugs, hasura: '/console' },
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './BaseDirectorySettings';
|
||||
export { default } from './BaseDirectorySettings';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './DeploymentBranchSettings';
|
||||
export { default } from './DeploymentBranchSettings';
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './Chip';
|
||||
export { default } from './Chip';
|
||||
export { default as Chip, default } from './Chip';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -68,7 +68,7 @@ export default function CreateUserForm({
|
||||
|
||||
const baseAuthUrl = generateAppServiceUrl(
|
||||
currentProject?.subdomain,
|
||||
currentProject?.region?.awsName,
|
||||
currentProject?.region,
|
||||
'auth',
|
||||
);
|
||||
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './BaseDirectorySettings';
|
||||
export { default as BaseDirectorySettings } from './BaseDirectorySettings';
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './DeploymentBranchSettings';
|
||||
export { default as DeploymentBranchSettings } from './DeploymentBranchSettings';
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as GitConnectionSettings } from './GitConnectionSettings';
|
||||
@@ -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
|
||||
) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,7 +12,7 @@ export default function useIsHealthy() {
|
||||
|
||||
const appUrl = generateAppServiceUrl(
|
||||
currentProject?.subdomain,
|
||||
currentProject?.region?.awsName,
|
||||
currentProject?.region,
|
||||
'auth',
|
||||
);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function useDatabaseQuery(
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const appUrl = generateAppServiceUrl(
|
||||
currentProject?.subdomain,
|
||||
currentProject?.region.awsName,
|
||||
currentProject?.region,
|
||||
'hasura',
|
||||
);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function useDeleteRecordMutation({
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const appUrl = generateAppServiceUrl(
|
||||
currentProject?.subdomain,
|
||||
currentProject?.region.awsName,
|
||||
currentProject?.region,
|
||||
'hasura',
|
||||
);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -44,7 +44,7 @@ export default function useManagePermissionMutation({
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const appUrl = generateAppServiceUrl(
|
||||
currentProject?.subdomain,
|
||||
currentProject?.region.awsName,
|
||||
currentProject?.region,
|
||||
'hasura',
|
||||
);
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function useMetadataQuery(
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const appUrl = generateAppServiceUrl(
|
||||
currentProject?.subdomain,
|
||||
currentProject?.region.awsName,
|
||||
currentProject?.region,
|
||||
'hasura',
|
||||
);
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function useTableQuery(
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const appUrl = generateAppServiceUrl(
|
||||
currentProject?.subdomain,
|
||||
currentProject?.region.awsName,
|
||||
currentProject?.region,
|
||||
'hasura',
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ export default function useFiles({
|
||||
) => {
|
||||
const fetchUrl = `${generateAppServiceUrl(
|
||||
currentProject.subdomain,
|
||||
currentProject.region.awsName,
|
||||
currentProject.region,
|
||||
'storage',
|
||||
)}/files/${file.id}`;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ export default function GraphQLPage() {
|
||||
|
||||
const appUrl = generateAppServiceUrl(
|
||||
currentProject.subdomain,
|
||||
currentProject.region.awsName,
|
||||
currentProject.region,
|
||||
'graphql',
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function StoragePage() {
|
||||
<NhostApolloProvider
|
||||
graphqlUrl={generateAppServiceUrl(
|
||||
currentProject.subdomain,
|
||||
currentProject.region.awsName,
|
||||
currentProject.region,
|
||||
'graphql',
|
||||
)}
|
||||
fetchPolicy="cache-first"
|
||||
|
||||
@@ -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're still on
|
||||
console.nhost.io during this beta.
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ export function NewProjectPageContent({
|
||||
const { submitState, setSubmitState } = useSubmitState();
|
||||
|
||||
const [insertApp] = useInsertApplicationMutation({
|
||||
refetchQueries: [GetAllWorkspacesAndProjectsDocument],
|
||||
refetchQueries: [{ query: GetAllWorkspacesAndProjectsDocument }],
|
||||
});
|
||||
|
||||
// options
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
1258
dashboard/src/utils/__generated__/graphql.ts
generated
1258
dashboard/src/utils/__generated__/graphql.ts
generated
File diff suppressed because it is too large
Load Diff
@@ -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`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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]}`;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
3
examples/cli/.env.example
Normal file
3
examples/cli/.env.example
Normal 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
5
examples/cli/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.nhost
|
||||
web/node_modules
|
||||
node_modules
|
||||
functions/node_modules
|
||||
.env
|
||||
9
examples/cli/CHANGELOG.md
Normal file
9
examples/cli/CHANGELOG.md
Normal 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
109
examples/cli/README.md
Normal 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>
|
||||
```
|
||||
139
examples/cli/nhost/config.yaml
Normal file
139
examples/cli/nhost/config.yaml
Normal 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
|
||||
18
examples/cli/nhost/emails/bg/email-confirm-change/body.html
Normal file
18
examples/cli/nhost/emails/bg/email-confirm-change/body.html
Normal 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>
|
||||
@@ -0,0 +1 @@
|
||||
Потвърждение за смяна на имейл
|
||||
18
examples/cli/nhost/emails/bg/email-verify/body.html
Normal file
18
examples/cli/nhost/emails/bg/email-verify/body.html
Normal 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>
|
||||
1
examples/cli/nhost/emails/bg/email-verify/subject.txt
Normal file
1
examples/cli/nhost/emails/bg/email-verify/subject.txt
Normal file
@@ -0,0 +1 @@
|
||||
Потвърждаване на имейл
|
||||
18
examples/cli/nhost/emails/bg/password-reset/body.html
Normal file
18
examples/cli/nhost/emails/bg/password-reset/body.html
Normal 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>
|
||||
1
examples/cli/nhost/emails/bg/password-reset/subject.txt
Normal file
1
examples/cli/nhost/emails/bg/password-reset/subject.txt
Normal file
@@ -0,0 +1 @@
|
||||
Смяна на парола
|
||||
@@ -0,0 +1 @@
|
||||
Вашият код е ${code}.
|
||||
18
examples/cli/nhost/emails/bg/signin-passwordless/body.html
Normal file
18
examples/cli/nhost/emails/bg/signin-passwordless/body.html
Normal 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>
|
||||
@@ -0,0 +1 @@
|
||||
Магически линк за вход
|
||||
18
examples/cli/nhost/emails/cs/email-confirm-change/body.html
Normal file
18
examples/cli/nhost/emails/cs/email-confirm-change/body.html
Normal 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>
|
||||
@@ -0,0 +1 @@
|
||||
Změna vaší emailové adresy
|
||||
18
examples/cli/nhost/emails/cs/email-verify/body.html
Normal file
18
examples/cli/nhost/emails/cs/email-verify/body.html
Normal 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>
|
||||
1
examples/cli/nhost/emails/cs/email-verify/subject.txt
Normal file
1
examples/cli/nhost/emails/cs/email-verify/subject.txt
Normal file
@@ -0,0 +1 @@
|
||||
Ověření vaší emailové adresy
|
||||
18
examples/cli/nhost/emails/cs/password-reset/body.html
Normal file
18
examples/cli/nhost/emails/cs/password-reset/body.html
Normal 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
Reference in New Issue
Block a user