Compare commits
103 Commits
@nhost/das
...
@nhost/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdc50b32d8 | ||
|
|
3cdca8d4b3 | ||
|
|
c425c9f265 | ||
|
|
3b8473b168 | ||
|
|
8d91f7103f | ||
|
|
11ce93d64b | ||
|
|
3ff1c2b531 | ||
|
|
e4341c3706 | ||
|
|
c2ef17c0a0 | ||
|
|
1045ea0a46 | ||
|
|
5faaf36e26 | ||
|
|
65b6a48d51 | ||
|
|
23e18fb734 | ||
|
|
44be6dc0a5 | ||
|
|
3c35948986 | ||
|
|
7883bbcbd1 | ||
|
|
42fddcf790 | ||
|
|
5d1a444451 | ||
|
|
98d17a3066 | ||
|
|
f70f36be08 | ||
|
|
50fe08624f | ||
|
|
6cb7dd8203 | ||
|
|
f859159ef5 | ||
|
|
32c246b7a9 | ||
|
|
f004fd067a | ||
|
|
82340b5d54 | ||
|
|
8fff3e06bd | ||
|
|
527a661222 | ||
|
|
172fd8dfed | ||
|
|
a99ca90279 | ||
|
|
5892fd7f01 | ||
|
|
0344cc9a6d | ||
|
|
2697e28cf2 | ||
|
|
54231b119f | ||
|
|
7c977e7143 | ||
|
|
7c3019389e | ||
|
|
46f028b9fd | ||
|
|
683e85b89f | ||
|
|
b0f27c908d | ||
|
|
5f2618e183 | ||
|
|
29037147f2 | ||
|
|
95b630a621 | ||
|
|
0fdfd8ad81 | ||
|
|
174b4165b3 | ||
|
|
04257bc09c | ||
|
|
184c341f05 | ||
|
|
52fdce291f | ||
|
|
c43ff40e1f | ||
|
|
4ec2f8f186 | ||
|
|
7ece80a39e | ||
|
|
1327351e1b | ||
|
|
1fbdf630a5 | ||
|
|
93f573ea98 | ||
|
|
ac78629414 | ||
|
|
3403744c22 | ||
|
|
cef11677f4 | ||
|
|
ddadf3399c | ||
|
|
c768341ce8 | ||
|
|
1396cbe4c0 | ||
|
|
76761b4970 | ||
|
|
af33c21d10 | ||
|
|
1b01d56e82 | ||
|
|
229acb1d60 | ||
|
|
0bc9a41e51 | ||
|
|
b4dccd4496 | ||
|
|
31683fa926 | ||
|
|
7107089a29 | ||
|
|
e9d5d0a53e | ||
|
|
4e0b132d20 | ||
|
|
425476759f | ||
|
|
04784d880b | ||
|
|
130131c488 | ||
|
|
a6c7300e14 | ||
|
|
1a84610b74 | ||
|
|
6c43529eff | ||
|
|
63309cbcd6 | ||
|
|
998b1d5963 | ||
|
|
42d2a89de3 | ||
|
|
731f094cf8 | ||
|
|
3454605582 | ||
|
|
e4479afab4 | ||
|
|
6edae34bf0 | ||
|
|
80b6464f60 | ||
|
|
e3880dbe8a | ||
|
|
ea991228e2 | ||
|
|
7cb568be52 | ||
|
|
dacaa7cad7 | ||
|
|
30a688778e | ||
|
|
d4f79c05b4 | ||
|
|
e10d313e37 | ||
|
|
77e8fb471c | ||
|
|
f40a3f23ac | ||
|
|
17dea7e60b | ||
|
|
23527fc388 | ||
|
|
8ec6b85bac | ||
|
|
b067838984 | ||
|
|
7553506e18 | ||
|
|
e58a9f1aaa | ||
|
|
d4bfea963f | ||
|
|
88779ad950 | ||
|
|
90929e9357 | ||
|
|
2f4d5814ed | ||
|
|
6d08b34309 |
22
.github/CODEOWNERS
vendored
22
.github/CODEOWNERS
vendored
@@ -1,14 +1,14 @@
|
|||||||
# Documentation
|
# Documentation
|
||||||
# https://help.github.com/en/articles/about-code-owners
|
# https://help.github.com/en/articles/about-code-owners
|
||||||
|
|
||||||
/packages @szilarddoro
|
/packages @nunopato @onehassan
|
||||||
/packages/docgen @szilarddoro
|
/packages/docgen @nunopato @onehassan
|
||||||
/integrations/stripe-graphql-js @elitan
|
/integrations/stripe-graphql-js @nunopato @onehassan
|
||||||
/.github @szilarddoro
|
/.github @nunopato @onehassan
|
||||||
/dashboard/ @szilarddoro
|
/dashboard/ @nunopato @onehassan
|
||||||
/docs/ @elitan
|
/docs/ @nunopato @onehassan
|
||||||
/config/ @szilarddoro
|
/config/ @nunopato @onehassan
|
||||||
/examples/ @szilarddoro
|
/examples/ @nunopato @onehassan
|
||||||
/examples/codegen-react-apollo @elitan @szilarddoro
|
/examples/codegen-react-apollo @nunopato @onehassan
|
||||||
/examples/codegen-react-query @elitan @szilarddoro
|
/examples/codegen-react-query @nunopato @onehassan
|
||||||
/examples/react-apollo-crm @elitan @szilarddoro
|
/examples/react-apollo-crm @nunopato @onehassan
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: pnpm/action-setup@v2.2.4
|
- uses: pnpm/action-setup@v2.2.4
|
||||||
with:
|
with:
|
||||||
version: 8.6.2
|
version: 8.10.5
|
||||||
run_install: false
|
run_install: false
|
||||||
- name: Get pnpm cache directory
|
- name: Get pnpm cache directory
|
||||||
id: pnpm-cache-dir
|
id: pnpm-cache-dir
|
||||||
|
|||||||
1
.github/labeler.yml
vendored
1
.github/labeler.yml
vendored
@@ -4,7 +4,6 @@ dashboard:
|
|||||||
documentation:
|
documentation:
|
||||||
- any:
|
- any:
|
||||||
- docs/**/*
|
- docs/**/*
|
||||||
- '!docs/docs/reference/docgen/**/*'
|
|
||||||
|
|
||||||
examples:
|
examples:
|
||||||
- examples/**/*
|
- examples/**/*
|
||||||
|
|||||||
23
.github/renovate.json
vendored
23
.github/renovate.json
vendored
@@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
||||||
"extends": [
|
|
||||||
"config:base"
|
|
||||||
],
|
|
||||||
"docker-compose": {
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"ignoreDeps": [
|
|
||||||
"pnpm",
|
|
||||||
"node",
|
|
||||||
"@types/node"
|
|
||||||
],
|
|
||||||
"labels": [
|
|
||||||
"dependencies"
|
|
||||||
],
|
|
||||||
"enabledManagers": [
|
|
||||||
"npm",
|
|
||||||
"dockerfile",
|
|
||||||
"docker-compose",
|
|
||||||
"github-actions"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@@ -106,6 +106,8 @@ jobs:
|
|||||||
# * Run every `lint` script in the workspace . Dependencies build is cached by Turborepo
|
# * Run every `lint` script in the workspace . Dependencies build is cached by Turborepo
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: pnpm run lint:all
|
run: pnpm run lint:all
|
||||||
|
- name: Audit for vulnerabilities
|
||||||
|
run: pnpx audit-ci --config ./audit-ci.jsonc
|
||||||
|
|
||||||
e2e:
|
e2e:
|
||||||
name: 'E2E (Package: ${{ matrix.package.path }})'
|
name: 'E2E (Package: ${{ matrix.package.path }})'
|
||||||
@@ -146,7 +148,7 @@ jobs:
|
|||||||
run: echo "NHOST_TEST_DASHBOARD_URL=https://${{ steps.fetch-dashboard-preview-url.outputs.preview_url }}" >> $GITHUB_ENV
|
run: echo "NHOST_TEST_DASHBOARD_URL=https://${{ steps.fetch-dashboard-preview-url.outputs.preview_url }}" >> $GITHUB_ENV
|
||||||
# * Run the `ci` script of the current package of the matrix. Dependencies build is cached by Turborepo
|
# * Run the `ci` script of the current package of the matrix. Dependencies build is cached by Turborepo
|
||||||
- name: Run e2e tests
|
- name: Run e2e tests
|
||||||
timeout-minutes: 15
|
timeout-minutes: 20
|
||||||
run: pnpm --filter="${{ matrix.package.name }}" run e2e
|
run: pnpm --filter="${{ matrix.package.name }}" run e2e
|
||||||
- id: file-name
|
- id: file-name
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -23,7 +23,7 @@ node_modules/
|
|||||||
tmp/
|
tmp/
|
||||||
.pnpm-store
|
.pnpm-store
|
||||||
.turbo
|
.turbo
|
||||||
.env
|
.env*
|
||||||
.secrets
|
.secrets
|
||||||
out/
|
out/
|
||||||
|
|
||||||
|
|||||||
3
.npmrc
3
.npmrc
@@ -1 +1,2 @@
|
|||||||
prefer-workspace-packages = true
|
prefer-workspace-packages = true
|
||||||
|
auto-install-peers = false
|
||||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.organizeImports": true
|
|
||||||
},
|
|
||||||
"eslint.workingDirectories": ["./dashboard"],
|
|
||||||
"typescript.tsdk": "node_modules/typescript/lib"
|
|
||||||
}
|
|
||||||
6
audit-ci.jsonc
Normal file
6
audit-ci.jsonc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
// $schema provides code completion hints to IDEs.
|
||||||
|
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
|
||||||
|
"moderate": true,
|
||||||
|
"allowlist": ["trim-newlines"]
|
||||||
|
}
|
||||||
@@ -16,3 +16,6 @@ NEXT_PUBLIC_STRIPE_PK=<nhost_stripe_public_key>
|
|||||||
NEXT_PUBLIC_GITHUB_APP_INSTALL_URL=<github_app_install_url>
|
NEXT_PUBLIC_GITHUB_APP_INSTALL_URL=<github_app_install_url>
|
||||||
NEXT_PUBLIC_ANALYTICS_WRITE_KEY=<analytics_write_key>
|
NEXT_PUBLIC_ANALYTICS_WRITE_KEY=<analytics_write_key>
|
||||||
NEXT_PUBLIC_NHOST_BRAGI_WEBSOCKET=<nhost_bragi_websocket>
|
NEXT_PUBLIC_NHOST_BRAGI_WEBSOCKET=<nhost_bragi_websocket>
|
||||||
|
|
||||||
|
CODEGEN_GRAPHQL_URL=https://local.graphql.nhost.run/v1
|
||||||
|
CODEGEN_HASURA_ADMIN_SECRET=nhost-admin-secret
|
||||||
|
|||||||
@@ -1,5 +1,59 @@
|
|||||||
# @nhost/dashboard
|
# @nhost/dashboard
|
||||||
|
|
||||||
|
## 1.6.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 8d91f71: chore: update deps and enable pnpm audit
|
||||||
|
- 3b8473b: chore: update turbo to `1.11.3` and pnpm to `8.10.5` in Dockerfile
|
||||||
|
- Updated dependencies [8d91f71]
|
||||||
|
- @nhost/react-apollo@7.0.2
|
||||||
|
- @nhost/nextjs@2.0.2
|
||||||
|
|
||||||
|
## 1.6.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 3ff1c2b53: fix: show upgrade option for pro projects
|
||||||
|
|
||||||
|
## 1.5.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- c2ef17c0a: feat: add support for new Team plan
|
||||||
|
|
||||||
|
## 1.4.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 7883bbcbd: feat: don't show deprecated plans
|
||||||
|
- 44be6dc0a: feat: set redirectTo during sign-in to support preview environments
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 3c3594898: fix: allow access to graphite when configured running in local dashboard
|
||||||
|
- 32c246b7a: chore: update docs icon
|
||||||
|
|
||||||
|
## 1.3.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 174b4165b: chore: use env variables when running graphql codegen
|
||||||
|
- 7c977e714: chore: change `Allowed Roles` to `Default Allowed Roles`
|
||||||
|
- 46f028b9f: fix: remove hardcoded ai version setting
|
||||||
|
|
||||||
|
## 1.3.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- af33c21d1: chore: remove backendUrl deprecation notice and remove all references to `providersUpdated`
|
||||||
|
|
||||||
|
## 1.3.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 04784d880: Fix graphite's default version
|
||||||
|
|
||||||
## 1.2.0
|
## 1.2.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ RUN apk add --no-cache libc6-compat
|
|||||||
RUN apk update
|
RUN apk update
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN yarn global add turbo@1.10.11
|
RUN yarn global add turbo@1.11.3
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN turbo prune --scope="@nhost/dashboard" --docker
|
RUN turbo prune --scope="@nhost/dashboard" --docker
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ ENV NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL __NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL_
|
|||||||
ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL __NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__
|
ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL __NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__
|
||||||
ENV NEXT_PUBLIC_NHOST_HASURA_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__
|
ENV NEXT_PUBLIC_NHOST_HASURA_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__
|
||||||
|
|
||||||
RUN yarn global add pnpm@8.6.2
|
RUN yarn global add pnpm@8.10.5
|
||||||
COPY .gitignore .gitignore
|
COPY .gitignore .gitignore
|
||||||
COPY --from=pruner /app/out/json/ .
|
COPY --from=pruner /app/out/json/ .
|
||||||
COPY --from=pruner /app/out/pnpm-*.yaml .
|
COPY --from=pruner /app/out/pnpm-*.yaml .
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ test('should be able to create then delete a personal access token', async () =>
|
|||||||
const patName = faker.lorem.slug(3);
|
const patName = faker.lorem.slug(3);
|
||||||
|
|
||||||
await page.getByRole('textbox', { name: /name/i }).fill(patName);
|
await page.getByRole('textbox', { name: /name/i }).fill(patName);
|
||||||
await page.getByRole('button', { name: /expiration/i }).click();
|
await page.getByLabel('Expiration').click();
|
||||||
await page.getByRole('option', { name: /7 days/i }).click();
|
await page.getByRole('option', { name: /7 days/i }).click();
|
||||||
await page.getByRole('button', { name: /create/i }).click();
|
await page.getByRole('button', { name: /create/i }).click();
|
||||||
|
|
||||||
|
|||||||
@@ -138,7 +138,8 @@ test('should create a table with an identity column', async () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.getByRole('button', { name: /identity/i }).click();
|
// await page.getByRole('button', { name: /identity/i }).click();
|
||||||
|
await page.getByLabel('Identity').click();
|
||||||
await page.getByRole('option', { name: /id/i }).click();
|
await page.getByRole('option', { name: /id/i }).click();
|
||||||
|
|
||||||
// create table
|
// create table
|
||||||
@@ -194,26 +195,18 @@ test('should create table with foreign key constraint', async () => {
|
|||||||
|
|
||||||
await page.getByRole('button', { name: /add foreign key/i }).click();
|
await page.getByRole('button', { name: /add foreign key/i }).click();
|
||||||
|
|
||||||
// select column in current table
|
await page.locator('#columnName').click();
|
||||||
await page
|
|
||||||
.getByRole('button', { name: /column/i })
|
|
||||||
.first()
|
|
||||||
.click();
|
|
||||||
await page.getByRole('option', { name: /author_id/i }).click();
|
await page.getByRole('option', { name: /author_id/i }).click();
|
||||||
|
|
||||||
// select reference schema
|
// select reference schema
|
||||||
await page.getByRole('button', { name: /schema/i }).click();
|
await page.getByLabel('Schema').click();
|
||||||
await page.getByRole('option', { name: /public/i }).click();
|
await page.getByRole('option', { name: /public/i }).click();
|
||||||
|
|
||||||
// select reference table
|
// select reference table
|
||||||
await page.getByRole('button', { name: /table/i }).click();
|
await page.getByLabel('Table').click();
|
||||||
await page.getByRole('option', { name: firstTableName, exact: true }).click();
|
await page.getByRole('option', { name: firstTableName, exact: true }).click();
|
||||||
|
|
||||||
// select reference column
|
await page.locator('#referencedColumn').click();
|
||||||
await page
|
|
||||||
.getByRole('button', { name: /column/i })
|
|
||||||
.nth(1)
|
|
||||||
.click();
|
|
||||||
await page.getByRole('option', { name: /id/i }).click();
|
await page.getByRole('option', { name: /id/i }).click();
|
||||||
|
|
||||||
await page.getByRole('button', { name: /add/i }).click();
|
await page.getByRole('button', { name: /add/i }).click();
|
||||||
|
|||||||
@@ -113,27 +113,21 @@ test('should not be able to delete a table if other tables have foreign keys ref
|
|||||||
await page.getByRole('button', { name: /add foreign key/i }).click();
|
await page.getByRole('button', { name: /add foreign key/i }).click();
|
||||||
|
|
||||||
// select column in current table
|
// select column in current table
|
||||||
await page
|
await page.locator('#columnName').click();
|
||||||
.getByRole('button', { name: /column/i })
|
|
||||||
.first()
|
|
||||||
.click();
|
|
||||||
await page.getByRole('option', { name: /author_id/i }).click();
|
await page.getByRole('option', { name: /author_id/i }).click();
|
||||||
|
|
||||||
// select reference schema
|
// select reference schema
|
||||||
await page.getByRole('button', { name: /schema/i }).click();
|
await page.getByLabel('Schema').click();
|
||||||
await page.getByRole('option', { name: /public/i }).click();
|
await page.getByRole('option', { name: /public/i }).click();
|
||||||
|
|
||||||
// select reference table
|
// select reference table
|
||||||
await page.getByRole('button', { name: /table/i }).click();
|
await page.getByLabel('Table').click();
|
||||||
await page.getByRole('option', { name: firstTableName, exact: true }).click();
|
await page.getByRole('option', { name: firstTableName, exact: true }).click();
|
||||||
|
|
||||||
// select reference column
|
// select reference column
|
||||||
await page
|
await page.locator('#referencedColumn').click();
|
||||||
.getByRole('button', { name: /column/i })
|
|
||||||
.nth(1)
|
|
||||||
.click();
|
|
||||||
await page.getByRole('option', { name: /id/i }).click();
|
await page.getByRole('option', { name: /id/i }).click();
|
||||||
|
|
||||||
await page.getByRole('button', { name: /add/i }).click();
|
await page.getByRole('button', { name: /add/i }).click();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ test("should show the project's region and subdomain", async () => {
|
|||||||
|
|
||||||
test('should not have a GitHub repository connected', async () => {
|
test('should not have a GitHub repository connected', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: /connect to github/i }),
|
page.getByRole('button', { name: /connect to github/i }).first(),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,8 @@ export async function prepareTable({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// select the first column as primary key
|
// select the first column as primary key
|
||||||
await page.getByRole('button', { name: /primary key/i }).click();
|
// await page.getByRole('button', { name: /primary key/i }).click();
|
||||||
|
await page.getByLabel('Primary Key').click();
|
||||||
await page.getByRole('option', { name: primaryKey, exact: true }).click();
|
await page.getByRole('option', { name: primaryKey, exact: true }).click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
schema:
|
schema:
|
||||||
- https://local.graphql.nhost.run/v1:
|
- ${CODEGEN_GRAPHQL_URL}:
|
||||||
headers:
|
headers:
|
||||||
x-hasura-admin-secret: nhost-admin-secret
|
x-hasura-admin-secret: ${CODEGEN_HASURA_ADMIN_SECRET}
|
||||||
generates:
|
generates:
|
||||||
src/utils/__generated__/graphql.ts:
|
src/utils/__generated__/graphql.ts:
|
||||||
documents:
|
documents:
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
query InitQuery {
|
|
||||||
root {
|
|
||||||
enableServices
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"projectId": 2596,
|
|
||||||
"token": "U2FsdGVkX19+V8BJnVR0xLEC+42OW5qZl/A0i6beAaRmJoIhFh5Yf6eIKBzLbV9h",
|
|
||||||
"outputDirectoryPath": "src/hypertune"
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/dashboard",
|
"name": "@nhost/dashboard",
|
||||||
"version": "1.2.0",
|
"version": "1.6.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
@@ -10,67 +10,66 @@
|
|||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint --max-warnings 0",
|
"lint": "next lint --max-warnings 0",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"codegen": "graphql-codegen --config graphql.config.yaml --errors-only",
|
"codegen": "DOTENV_CONFIG_PATH=./.env.local graphql-codegen -r dotenv/config --config graphql.config.yaml --errors-only",
|
||||||
"codegen-graphite": "graphql-codegen --config graphite.graphql.config.yaml --errors-only",
|
"codegen-graphite": "graphql-codegen --config graphite.graphql.config.yaml --errors-only",
|
||||||
"format": "prettier --write \"src/**/*.{js,ts,tsx,jsx,json,md}\" --plugin-search-dir=.",
|
"format": "prettier --write \"src/**/*.{js,ts,tsx,jsx,json,md}\" --plugin-search-dir=.",
|
||||||
"storybook": "start-storybook -p 6006 -s public",
|
"storybook": "start-storybook -p 6006 -s public",
|
||||||
"build-storybook": "build-storybook",
|
"build-storybook": "build-storybook",
|
||||||
"install-browsers": "pnpm dlx playwright@1.31.0 install --with-deps",
|
"install-browsers": "pnpm playwright install && pnpm playwright install-deps",
|
||||||
"e2e": "pnpm install-browsers && pnpm dlx playwright@1.31.0 test"
|
"e2e": "pnpm install-browsers && pnpm playwright test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.7.10",
|
"@apollo/client": "^3.8.9",
|
||||||
"@codemirror/lang-sql": "^6.5.4",
|
"@codemirror/lang-sql": "^6.5.5",
|
||||||
"@emotion/cache": "^11.10.5",
|
"@emotion/cache": "^11.11.0",
|
||||||
"@emotion/react": "^11.10.5",
|
"@emotion/react": "^11.11.3",
|
||||||
"@emotion/server": "^11.4.0",
|
"@emotion/server": "^11.11.0",
|
||||||
"@emotion/styled": "^11.10.5",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@fontsource/inter": "^5.0.0",
|
"@fontsource/inter": "^5.0.16",
|
||||||
"@fontsource/roboto-mono": "^5.0.0",
|
"@fontsource/roboto-mono": "^5.0.16",
|
||||||
"@graphiql/react": "^0.18.0",
|
"@graphiql/react": "^0.20.2",
|
||||||
"@graphiql/toolkit": "^0.8.2",
|
"@graphiql/toolkit": "^0.9.1",
|
||||||
"@headlessui/react": "^1.6.5",
|
"@headlessui/react": "^1.7.18",
|
||||||
"@heroicons/react": "^1.0.6",
|
"@heroicons/react": "^1.0.6",
|
||||||
"@hookform/resolvers": "^3.0.0",
|
"@hookform/resolvers": "^3.3.4",
|
||||||
"@mui/base": "^5.0.0-alpha.106",
|
"@mui/base": "5.0.0-beta.31",
|
||||||
"@mui/material": "^5.10.14",
|
"@mui/material": "^5.15.4",
|
||||||
"@mui/system": "^5.10.14",
|
"@mui/system": "^5.15.4",
|
||||||
"@mui/x-date-pickers": "^5.0.8",
|
"@mui/x-date-pickers": "^5.0.20",
|
||||||
"@nhost/nextjs": "workspace:*",
|
"@nhost/nextjs": "workspace:*",
|
||||||
"@nhost/react-apollo": "workspace:*",
|
"@nhost/react-apollo": "workspace:*",
|
||||||
"@segment/snippet": "^4.15.3",
|
"@segment/snippet": "^4.16.2",
|
||||||
"@stripe/react-stripe-js": "^2.0.0",
|
"@stripe/react-stripe-js": "^2.4.0",
|
||||||
"@stripe/stripe-js": "^1.35.0",
|
"@stripe/stripe-js": "^1.54.2",
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
"@tanstack/react-query": "^4.16.1",
|
"@tanstack/react-query": "^4.36.1",
|
||||||
"@tanstack/react-table": "^8.5.30",
|
"@tanstack/react-table": "^8.11.6",
|
||||||
"@tanstack/react-virtual": "^3.0.0-beta.23",
|
"@tanstack/react-virtual": "^3.0.1",
|
||||||
"@uiw/codemirror-theme-github": "^4.21.20",
|
"@uiw/codemirror-theme-github": "^4.21.21",
|
||||||
"@uiw/react-codemirror": "^4.21.20",
|
"@uiw/react-codemirror": "^4.21.21",
|
||||||
"analytics-node": "^6.2.0",
|
"analytics-node": "^6.2.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.30.0",
|
||||||
"generate-password": "^1.7.0",
|
"generate-password": "^1.7.1",
|
||||||
"graphiql": "^3.0.0",
|
"graphiql": "^3.1.0",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "16.8.1",
|
||||||
"graphql-request": "^6.0.0",
|
"graphql-request": "^6.1.0",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
"graphql-ws": "^5.11.2",
|
"graphql-ws": "^5.14.3",
|
||||||
"hypertune": "^1.4.4",
|
"just-kebab-case": "^4.2.0",
|
||||||
"just-kebab-case": "^4.1.1",
|
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"next": "^12.3.1",
|
"next": "^14.0.4",
|
||||||
"next-seo": "^6.0.0",
|
"next-seo": "^6.4.0",
|
||||||
"node-pg-format": "^1.3.5",
|
"node-pg-format": "^1.3.5",
|
||||||
"pluralize": "^8.0.0",
|
"pluralize": "^8.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-children-utilities": "^2.9.0",
|
"react-children-utilities": "^2.10.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-error-boundary": "^4.0.0",
|
"react-error-boundary": "^4.0.12",
|
||||||
"react-hook-form": "^7.42.1",
|
"react-hook-form": "^7.49.3",
|
||||||
"react-hot-toast": "^2.4.0",
|
"react-hot-toast": "^2.4.1",
|
||||||
"react-intersection-observer": "^9.5.2",
|
"react-intersection-observer": "^9.5.3",
|
||||||
"react-is": "18.2.0",
|
"react-is": "18.2.0",
|
||||||
"react-loading-skeleton": "^2.2.0",
|
"react-loading-skeleton": "^2.2.0",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
@@ -82,87 +81,87 @@
|
|||||||
"rehype-highlight": "^7.0.0",
|
"rehype-highlight": "^7.0.0",
|
||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
"shell-quote": "^1.8.1",
|
"shell-quote": "^1.8.1",
|
||||||
"slugify": "^1.6.5",
|
"slugify": "^1.6.6",
|
||||||
"stripe": "^10.17.0",
|
"stripe": "^10.17.0",
|
||||||
"tailwind-merge": "^1.8.0",
|
"tailwind-merge": "^1.14.0",
|
||||||
"utility-types": "^3.10.0",
|
"utility-types": "^3.10.0",
|
||||||
"validator": "^13.7.0",
|
"validator": "^13.11.0",
|
||||||
"yup": "^1.0.2",
|
"yup": "^1.3.3",
|
||||||
"yup-password": "^0.2.2"
|
"yup-password": "^0.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.2",
|
"@babel/core": "^7.23.7",
|
||||||
"@faker-js/faker": "^7.6.0",
|
"@faker-js/faker": "^7.6.0",
|
||||||
"@graphql-codegen/cli": "^3.0.0",
|
"@graphql-codegen/cli": "^3.3.1",
|
||||||
"@graphql-codegen/typescript": "^3.0.0",
|
"@graphql-codegen/typescript": "^3.0.4",
|
||||||
"@graphql-codegen/typescript-operations": "^3.0.0",
|
"@graphql-codegen/typescript-operations": "^3.0.4",
|
||||||
"@graphql-codegen/typescript-react-apollo": "^3.3.1",
|
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
|
||||||
"@next/bundle-analyzer": "^12.3.1",
|
"@next/bundle-analyzer": "^12.3.4",
|
||||||
"@playwright/test": "1.31.0",
|
"@playwright/test": "1.41.0",
|
||||||
"@storybook/addon-actions": "^6.5.14",
|
"@storybook/addon-actions": "^6.5.16",
|
||||||
"@storybook/addon-essentials": "^6.5.14",
|
"@storybook/addon-essentials": "^6.5.16",
|
||||||
"@storybook/addon-interactions": "^6.5.14",
|
"@storybook/addon-interactions": "^6.5.16",
|
||||||
"@storybook/addon-links": "^6.5.14",
|
"@storybook/addon-links": "^6.5.16",
|
||||||
"@storybook/addon-postcss": "^2.0.0",
|
"@storybook/addon-postcss": "^2.0.0",
|
||||||
"@storybook/builder-webpack5": "^6.5.14",
|
"@storybook/builder-webpack5": "^6.5.16",
|
||||||
"@storybook/manager-webpack5": "^6.5.14",
|
"@storybook/manager-webpack5": "^6.5.16",
|
||||||
"@storybook/react": "^6.5.14",
|
"@storybook/react": "^6.5.16",
|
||||||
"@storybook/testing-library": "^0.2.0",
|
"@storybook/testing-library": "^0.2.2",
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
"@testing-library/dom": "^9.0.0",
|
"@testing-library/dom": "^9.3.4",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^14.1.2",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@types/ace": "^0.0.48",
|
"@types/ace": "^0.0.48",
|
||||||
"@types/bcryptjs": "^2.4.2",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/jest": "^29.5.3",
|
"@types/jest": "^29.5.11",
|
||||||
"@types/lodash.debounce": "^4.0.7",
|
"@types/lodash.debounce": "^4.0.9",
|
||||||
"@types/node": "^16.11.7",
|
"@types/node": "^16.18.70",
|
||||||
"@types/pluralize": "^0.0.30",
|
"@types/pluralize": "^0.0.30",
|
||||||
"@types/react": "^18.2.14",
|
"@types/react": "^18.2.47",
|
||||||
"@types/react-dom": "^18.2.6",
|
"@types/react-dom": "^18.2.18",
|
||||||
"@types/react-table": "^7.7.12",
|
"@types/react-table": "^7.7.19",
|
||||||
"@types/shell-quote": "^1.7.1",
|
"@types/shell-quote": "^1.7.5",
|
||||||
"@types/testing-library__jest-dom": "^5.14.5",
|
"@types/testing-library__jest-dom": "^5.14.9",
|
||||||
"@types/validator": "^13.7.10",
|
"@types/validator": "^13.11.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
"@typescript-eslint/eslint-plugin": "^6.18.1",
|
||||||
"@typescript-eslint/parser": "^6.15.0",
|
"@typescript-eslint/parser": "^6.18.1",
|
||||||
"@vitejs/plugin-react": "^4.0.0",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"@vitest/coverage-v8": "^0.32.0",
|
"@vitest/coverage-v8": "^0.32.4",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.16",
|
||||||
"babel-loader": "^8.3.0",
|
"babel-loader": "^8.3.0",
|
||||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||||
"csstype": "^3.0.10",
|
"csstype": "^3.1.3",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.3.1",
|
||||||
"encoding": "^0.1.13",
|
"encoding": "^0.1.13",
|
||||||
"eslint": "^8.28.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-config-airbnb": "19.0.4",
|
"eslint-config-airbnb": "19.0.4",
|
||||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||||
"eslint-config-next": "^13.0.2",
|
"eslint-config-next": "^13.5.6",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.10.0",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.29.1",
|
||||||
"eslint-plugin-jsx-a11y": "^6.6.1",
|
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||||
"eslint-plugin-react": "^7.31.11",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"jsdom": "^22.0.0",
|
"jsdom": "^22.1.0",
|
||||||
"lint-staged": ">=13",
|
"lint-staged": "^15.2.0",
|
||||||
"msw": "^1.0.1",
|
"msw": "^1.3.2",
|
||||||
"msw-storybook-addon": "^1.6.3",
|
"msw-storybook-addon": "^1.10.0",
|
||||||
"node-fetch": "^3.3.0",
|
"node-fetch": "^3.3.2",
|
||||||
"postcss": "^8.4.19",
|
"postcss": "^8.4.33",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.8.8",
|
||||||
"prettier-plugin-organize-imports": "^3.2.0",
|
"prettier-plugin-organize-imports": "^3.2.4",
|
||||||
"prettier-plugin-tailwindcss": "^0.4.0",
|
"prettier-plugin-tailwindcss": "^0.4.1",
|
||||||
"react-date-fns-hooks": "^0.9.4",
|
"react-date-fns-hooks": "^0.9.4",
|
||||||
"require-from-string": "^2.0.2",
|
"require-from-string": "^2.0.2",
|
||||||
"snake-case": "^3.0.4",
|
"snake-case": "^3.0.4",
|
||||||
"storybook-addon-next-router": "^4.0.1",
|
"storybook-addon-next-router": "^4.0.2",
|
||||||
"tailwindcss": "^3.1.2",
|
"tailwindcss": "^3.4.1",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.2",
|
||||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
"tsconfig-paths-webpack-plugin": "^4.1.0",
|
||||||
"vite": "^4.0.2",
|
"vite": "^5.0.12",
|
||||||
"vite-tsconfig-paths": "^4.0.3",
|
"vite-tsconfig-paths": "^4.2.3",
|
||||||
"vitest": "^0.32.0"
|
"vitest": "^0.32.4"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|||||||
7
dashboard/public/logos/Note.svg
Normal file
7
dashboard/public/logos/Note.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 6H10" stroke="#21324B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M6 8H10" stroke="#21324B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M6 10H8" stroke="#21324B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M9.79289 13.5H3C2.86739 13.5 2.74021 13.4473 2.64645 13.3536C2.55268 13.2598 2.5 13.1326 2.5 13V3C2.5 2.86739 2.55268 2.74021 2.64645 2.64645C2.74021 2.55268 2.86739 2.5 3 2.5H13C13.1326 2.5 13.2598 2.55268 13.3536 2.64645C13.4473 2.74021 13.5 2.86739 13.5 3V9.79289C13.5 9.85855 13.4871 9.92357 13.4619 9.98423C13.4368 10.0449 13.4 10.1 13.3536 10.1464L10.1464 13.3536C10.1 13.4 10.0449 13.4368 9.98423 13.4619C9.92357 13.4871 9.85855 13.5 9.79289 13.5V13.5Z" stroke="#21324B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M13.4548 9.99948H10V13.4545" stroke="#21324B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
7
dashboard/public/logos/light/Note.svg
Normal file
7
dashboard/public/logos/light/Note.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 6H10" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M6 8H10" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M6 10H8" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M9.79289 13.5H3C2.86739 13.5 2.74021 13.4473 2.64645 13.3536C2.55268 13.2598 2.5 13.1326 2.5 13V3C2.5 2.86739 2.55268 2.74021 2.64645 2.64645C2.74021 2.55268 2.86739 2.5 3 2.5H13C13.1326 2.5 13.2598 2.55268 13.3536 2.64645C13.4473 2.74021 13.5 2.86739 13.5 3V9.79289C13.5 9.85855 13.4871 9.92357 13.4619 9.98423C13.4368 10.0449 13.4 10.1 13.3536 10.1464L10.1464 13.3536C10.1 13.4 10.0449 13.4368 9.98423 13.4619C9.92357 13.4871 9.85855 13.5 9.79289 13.5V13.5Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M13.4548 9.99948H10V13.4545" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -4,9 +4,17 @@ import type { DetailedHTMLProps, HTMLProps } from 'react';
|
|||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export interface ContactUsProps
|
export interface ContactUsProps
|
||||||
extends DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> {}
|
extends DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> {
|
||||||
|
isTeam?: boolean;
|
||||||
|
isOwner?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export default function FeedbackForm({ className, ...props }: ContactUsProps) {
|
export default function FeedbackForm({
|
||||||
|
className,
|
||||||
|
isTeam,
|
||||||
|
isOwner,
|
||||||
|
...props
|
||||||
|
}: ContactUsProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
@@ -19,6 +27,30 @@ export default function FeedbackForm({ className, ...props }: ContactUsProps) {
|
|||||||
Contact us
|
Contact us
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
{isTeam && isOwner && (
|
||||||
|
<Text>
|
||||||
|
If this is a new Team project, or you need to manage members, reach
|
||||||
|
out to us on discord or via email at{' '}
|
||||||
|
<Link
|
||||||
|
href="mailto:support@nhost.io"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
underline="hover"
|
||||||
|
>
|
||||||
|
support@nhost.io
|
||||||
|
</Link>{' '}
|
||||||
|
so we can have your dedicated channel set up.
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isTeam && !isOwner && (
|
||||||
|
<Text>
|
||||||
|
As part of a team plan you can reach out to us on the private channel
|
||||||
|
for this workspace. If you haven't been added to the channel, ask
|
||||||
|
the workspace owner to add you.
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
<Text>
|
<Text>
|
||||||
To report issues with Nhost, please open a GitHub issue in the{' '}
|
To report issues with Nhost, please open a GitHub issue in the{' '}
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { Alert } from '@/components/ui/v2/Alert';
|
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
|
||||||
|
|
||||||
export default function DepricationNotice() {
|
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
|
||||||
|
|
||||||
return (
|
|
||||||
!currentProject?.providersUpdated && (
|
|
||||||
<Alert severity="warning" className="grid place-content-center">
|
|
||||||
<Text color="warning" className="max-w-3xl text-sm">
|
|
||||||
On December 1st the old backend domain will cease to work. You need to
|
|
||||||
make sure your client is instantiated using the subdomain and region
|
|
||||||
and update your oauth2 settings. You can find more information{' '}
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline"
|
|
||||||
href="https://github.com/nhost/nhost/discussions/2303"
|
|
||||||
>
|
|
||||||
here
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</Text>
|
|
||||||
</Alert>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { default as ContactUs } from './DepricationNotice';
|
|
||||||
@@ -17,7 +17,7 @@ function NavLink(
|
|||||||
ref: ForwardedRef<HTMLAnchorElement>,
|
ref: ForwardedRef<HTMLAnchorElement>,
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<NextLink href={href} passHref>
|
<NextLink href={href} passHref legacyBehavior>
|
||||||
<Link className={twMerge('font-display', className)} ref={ref} {...props}>
|
<Link className={twMerge('font-display', className)} ref={ref} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default function ThemeSwitcher({
|
|||||||
listbox: { className: 'min-w-0 w-full' },
|
listbox: { className: 'min-w-0 w-full' },
|
||||||
popper: {
|
popper: {
|
||||||
disablePortal: false,
|
disablePortal: false,
|
||||||
className: 'z-[10000] w-[270px] w-full',
|
className: 'z-[10000] w-[270px]',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default function UpgradeToProBanner({
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{ backgroundColor: 'primary.light' }}
|
sx={{ backgroundColor: 'primary.light' }}
|
||||||
className="flex flex-col justify-between space-y-4 rounded-md p-4 lg:flex-row lg:items-center lg:space-y-0"
|
className="flex flex-col justify-between p-4 space-y-4 rounded-md lg:flex-row lg:items-center lg:space-y-0"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col justify-between space-y-4">
|
<div className="flex flex-col justify-between space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -81,13 +81,13 @@ export default function UpgradeToProBanner({
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
underline="hover"
|
underline="hover"
|
||||||
className="text-center font-medium"
|
className="font-medium text-center"
|
||||||
sx={{
|
sx={{
|
||||||
color: 'text.secondary',
|
color: 'text.secondary',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
See all features
|
See all features
|
||||||
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
|
<ArrowSquareOutIcon className="w-4 h-4 ml-1" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -97,6 +97,7 @@ export default function UpgradeToProBanner({
|
|||||||
width={300}
|
width={300}
|
||||||
height={140}
|
height={140}
|
||||||
objectFit="contain"
|
objectFit="contain"
|
||||||
|
alt='Upgrade to Pro illustration'
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import type {
|
|||||||
DataBrowserGridCellProps,
|
DataBrowserGridCellProps,
|
||||||
} from '@/features/database/dataGrid/types/dataBrowser';
|
} from '@/features/database/dataGrid/types/dataBrowser';
|
||||||
import { triggerToast } from '@/utils/toast';
|
import { triggerToast } from '@/utils/toast';
|
||||||
import type { FocusEvent, KeyboardEvent, MouseEvent } from 'react';
|
import type { FocusEvent, JSXElementConstructor, KeyboardEvent, MouseEvent, ReactElement, ReactNode, ReactPortal } from 'react';
|
||||||
import {
|
import {
|
||||||
Children,
|
Children,
|
||||||
cloneElement,
|
cloneElement,
|
||||||
@@ -320,7 +320,7 @@ function DataGridCellContent<TData extends object = {}, TValue = unknown>({
|
|||||||
sx={{ backgroundColor: 'transparent' }}
|
sx={{ backgroundColor: 'transparent' }}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{Children.map(children, (child) => {
|
{Children.map(children, (child: ReactNode | ReactPortal | ReactElement<unknown, string | JSXElementConstructor<any>>) => {
|
||||||
if (!isValidElement(child)) {
|
if (!isValidElement(child)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { Dropdown } from '@/components/ui/v2/Dropdown';
|
|||||||
import { GraphiteIcon } from '@/components/ui/v2/icons/GraphiteIcon';
|
import { GraphiteIcon } from '@/components/ui/v2/icons/GraphiteIcon';
|
||||||
import { DevAssistant } from '@/features/ai/DevAssistant';
|
import { DevAssistant } from '@/features/ai/DevAssistant';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCurrentUserOwner';
|
||||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { ApplicationStatus } from '@/types/application';
|
import { ApplicationStatus } from '@/types/application';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||||
@@ -37,6 +38,8 @@ export default function Header({ className, ...props }: HeaderProps) {
|
|||||||
const { currentProject, refetch: refetchProject } =
|
const { currentProject, refetch: refetchProject } =
|
||||||
useCurrentWorkspaceAndProject();
|
useCurrentWorkspaceAndProject();
|
||||||
|
|
||||||
|
const isOwner = useIsCurrentUserOwner();
|
||||||
|
|
||||||
const isProjectUpdating =
|
const isProjectUpdating =
|
||||||
currentProject?.appStates[0]?.stateId === ApplicationStatus.Updating;
|
currentProject?.appStates[0]?.stateId === ApplicationStatus.Updating;
|
||||||
|
|
||||||
@@ -114,7 +117,11 @@ export default function Header({ className, ...props }: HeaderProps) {
|
|||||||
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||||
>
|
>
|
||||||
<ContactUs className="max-w-md" />
|
<ContactUs
|
||||||
|
className="max-w-md"
|
||||||
|
isTeam={currentProject?.plan?.name === 'Team'}
|
||||||
|
isOwner={isOwner}
|
||||||
|
/>
|
||||||
</Dropdown.Content>
|
</Dropdown.Content>
|
||||||
</Dropdown.Root>
|
</Dropdown.Root>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import DepricationNotice from '@/components/common/DepricationNotice/DepricationNotice';
|
|
||||||
import type { ProjectLayoutProps } from '@/components/layout/ProjectLayout';
|
import type { ProjectLayoutProps } from '@/components/layout/ProjectLayout';
|
||||||
import { ProjectLayout } from '@/components/layout/ProjectLayout';
|
import { ProjectLayout } from '@/components/layout/ProjectLayout';
|
||||||
import type { SettingsSidebarProps } from '@/components/layout/SettingsSidebar';
|
import type { SettingsSidebarProps } from '@/components/layout/SettingsSidebar';
|
||||||
@@ -50,7 +49,6 @@ export default function SettingsLayout({
|
|||||||
>
|
>
|
||||||
<RetryableErrorBoundary>
|
<RetryableErrorBoundary>
|
||||||
<div className="flex flex-col space-y-2">
|
<div className="flex flex-col space-y-2">
|
||||||
<DepricationNotice />
|
|
||||||
{hasGitRepo && (
|
{hasGitRepo && (
|
||||||
<Alert
|
<Alert
|
||||||
severity="warning"
|
severity="warning"
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
|
||||||
import { Alert } from '@/components/ui/v2/Alert';
|
|
||||||
import { Button } from '@/components/ui/v2/Button';
|
|
||||||
import { ArrowSquareOutIcon } from '@/components/ui/v2/icons/ArrowSquareOutIcon';
|
|
||||||
import { Link } from '@/components/ui/v2/Link';
|
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
|
||||||
import { useConfirmProvidersUpdatedMutation } from '@/utils/__generated__/graphql';
|
|
||||||
import { useTheme } from '@mui/material';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import toast from 'react-hot-toast';
|
|
||||||
|
|
||||||
export default function ProvidersUpdatedAlert() {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { openAlertDialog } = useDialog();
|
|
||||||
const [confirmed, setConfirmed] = useState(true);
|
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
|
||||||
|
|
||||||
const [confirmProvidersUpdated] = useConfirmProvidersUpdatedMutation({
|
|
||||||
variables: { id: currentProject?.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
async function handleSubmitConfirmation() {
|
|
||||||
const confirmProvidersUpdatedPromise = confirmProvidersUpdated();
|
|
||||||
|
|
||||||
await toast.promise(
|
|
||||||
confirmProvidersUpdatedPromise,
|
|
||||||
{
|
|
||||||
loading: 'Confirming...',
|
|
||||||
success: 'Your settings have been updated successfully.',
|
|
||||||
error: 'An error occurred while trying to confirm the message.',
|
|
||||||
},
|
|
||||||
getToastStyleProps(),
|
|
||||||
);
|
|
||||||
|
|
||||||
setConfirmed(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleOpenConfirmationDialog() {
|
|
||||||
openAlertDialog({
|
|
||||||
title: 'Confirm all providers updated?',
|
|
||||||
payload: (
|
|
||||||
<Text variant="subtitle1" component="span">
|
|
||||||
Please make sure to update all providers before continuing. Your
|
|
||||||
sign-in flows might break if you don't.
|
|
||||||
</Text>
|
|
||||||
),
|
|
||||||
props: {
|
|
||||||
onPrimaryAction: handleSubmitConfirmation,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Alert
|
|
||||||
severity="warning"
|
|
||||||
className="grid items-center grid-flow-row gap-2 p-4 place-items-center lg:grid-flow-col lg:place-content-between"
|
|
||||||
>
|
|
||||||
<div className="grid grid-flow-row gap-1 text-left">
|
|
||||||
<Text className="font-semibold">
|
|
||||||
Please update the Redirect URL for all providers being used
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text className="text-sm+">
|
|
||||||
We are deprecating your project's old DNS name in favor of
|
|
||||||
individual DNS names for each service. Please make sure to update your
|
|
||||||
providers to use the new auth specific URL under <b>Redirect URL</b>{' '}
|
|
||||||
before the 1st of February 2023.{' '}
|
|
||||||
<Link
|
|
||||||
href="https://github.com/nhost/nhost/discussions/1319"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
underline="hover"
|
|
||||||
className="font-medium"
|
|
||||||
>
|
|
||||||
Read the discussion here.
|
|
||||||
<ArrowSquareOutIcon className="w-4 h-4 ml-1" />
|
|
||||||
</Link>
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="borderless"
|
|
||||||
className={
|
|
||||||
theme.palette.mode === 'dark'
|
|
||||||
? 'text-white hover:bg-brown'
|
|
||||||
: 'text-black hover:bg-orange-300'
|
|
||||||
}
|
|
||||||
onClick={handleOpenConfirmationDialog}
|
|
||||||
>
|
|
||||||
I have updated all Redirect URLs
|
|
||||||
</Button>
|
|
||||||
</Alert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { default as ProvidersUpdatedAlert } from './ProvidersUpdatedAlert';
|
|
||||||
@@ -8,9 +8,9 @@ import { Input, inputClasses } from '@/components/ui/v2/Input';
|
|||||||
import { OptionBase } from '@/components/ui/v2/Option';
|
import { OptionBase } from '@/components/ui/v2/Option';
|
||||||
import { OptionGroupBase } from '@/components/ui/v2/OptionGroup';
|
import { OptionGroupBase } from '@/components/ui/v2/OptionGroup';
|
||||||
import type { StyledComponent } from '@emotion/styled';
|
import type { StyledComponent } from '@emotion/styled';
|
||||||
import type { UseAutocompleteProps } from '@mui/base/AutocompleteUnstyled';
|
import type { UseAutocompleteProps } from '@mui/base/useAutocomplete';
|
||||||
import { createFilterOptions } from '@mui/base/AutocompleteUnstyled';
|
import { createFilterOptions } from '@mui/base/useAutocomplete';
|
||||||
import PopperUnstyled from '@mui/base/PopperUnstyled';
|
import { Popper } from '@mui/base'
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import type { AutocompleteProps as MaterialAutocompleteProps } from '@mui/material/Autocomplete';
|
import type { AutocompleteProps as MaterialAutocompleteProps } from '@mui/material/Autocomplete';
|
||||||
import MaterialAutocomplete, {
|
import MaterialAutocomplete, {
|
||||||
@@ -142,7 +142,7 @@ const StyledOptionBase = styled(OptionBase)(({ theme }) => ({
|
|||||||
gap: theme.spacing(0.5),
|
gap: theme.spacing(0.5),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const AutocompletePopper = styled(PopperUnstyled)(({ theme }) => ({
|
export const AutocompletePopper = styled(Popper)(({ theme }) => ({
|
||||||
zIndex: theme.zIndex.modal + 1,
|
zIndex: theme.zIndex.modal + 1,
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
minWidth: 320,
|
minWidth: 320,
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import type {
|
import type { BoxProps as MaterialBoxProps } from '@mui/material/Box';
|
||||||
BoxProps as MaterialBoxProps,
|
|
||||||
BoxTypeMap,
|
|
||||||
} from '@mui/material/Box';
|
|
||||||
import MaterialBox from '@mui/material/Box';
|
import MaterialBox from '@mui/material/Box';
|
||||||
|
import { type BoxTypeMap } from '@mui/system';
|
||||||
import type { ForwardedRef, PropsWithoutRef } from 'react';
|
import type { ForwardedRef, PropsWithoutRef } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export interface ChipProps extends MaterialChipProps {
|
|||||||
/**
|
/**
|
||||||
* Custom component for the root node.
|
* Custom component for the root node.
|
||||||
*/
|
*/
|
||||||
component?: string | ElementType;
|
component?: ElementType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Chip = styled(MaterialChip)<ChipProps>(({ theme }) => ({
|
const Chip = styled(MaterialChip)<ChipProps>(({ theme }) => ({
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export interface HelperTextProps extends MaterialFormHelperTextProps {
|
|||||||
/**
|
/**
|
||||||
* Custom component for the root node.
|
* Custom component for the root node.
|
||||||
*/
|
*/
|
||||||
component?: string | ElementType;
|
component?: ElementType;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HelperText = styled(MaterialFormHelperText)<HelperTextProps>({
|
const HelperText = styled(MaterialFormHelperText)<HelperTextProps>({
|
||||||
|
|||||||
@@ -1,41 +1,42 @@
|
|||||||
import type { OptionUnstyledProps } from '@mui/base/OptionUnstyled';
|
import {
|
||||||
import OptionUnstyled, {
|
Option as BaseOption,
|
||||||
optionUnstyledClasses,
|
optionClasses as baseOptionClasses,
|
||||||
} from '@mui/base/OptionUnstyled';
|
type OptionProps as BaseOptionProps,
|
||||||
|
} from '@mui/base';
|
||||||
import { darken, styled } from '@mui/material';
|
import { darken, styled } from '@mui/material';
|
||||||
import type { ForwardedRef } from 'react';
|
import type { ForwardedRef } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import OptionBase from './OptionBase';
|
import OptionBase from './OptionBase';
|
||||||
|
|
||||||
export interface OptionProps<TValue extends {}>
|
export interface OptionProps<TValue extends {}>
|
||||||
extends OptionUnstyledProps<TValue> {}
|
extends BaseOptionProps<TValue> {}
|
||||||
|
|
||||||
const StyledOption = styled(OptionUnstyled)(({ theme }) => ({
|
const StyledOption = styled(BaseOption)(({ theme }) => ({
|
||||||
transition: theme.transitions.create(['background-color']),
|
transition: theme.transitions.create(['background-color']),
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
[`&.${optionUnstyledClasses.selected}`]: {
|
[`&.${baseOptionClasses.selected}`]: {
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
? `${darken(theme.palette.action.hover, 0.1)} !important`
|
? `${darken(theme.palette.action.hover, 0.1)} !important`
|
||||||
: `${darken(theme.palette.action.hover, 0.05)} !important`,
|
: `${darken(theme.palette.action.hover, 0.05)} !important`,
|
||||||
},
|
},
|
||||||
[`&.${optionUnstyledClasses.selected}:hover, &.${optionUnstyledClasses.selected}.${optionUnstyledClasses.highlighted}`]:
|
[`&.${baseOptionClasses.selected}:hover, &.${baseOptionClasses.selected}.${baseOptionClasses.highlighted}`]:
|
||||||
{
|
{
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
? `${darken(theme.palette.action.hover, 0.25)} !important`
|
? `${darken(theme.palette.action.hover, 0.25)} !important`
|
||||||
: `${darken(theme.palette.action.hover, 0.075)} !important`,
|
: `${darken(theme.palette.action.hover, 0.075)} !important`,
|
||||||
},
|
},
|
||||||
[`&.${optionUnstyledClasses.highlighted}, &:hover`]: {
|
[`&.${baseOptionClasses.highlighted}, &:hover`]: {
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
? `${darken(theme.palette.action.hover, 0.15)} !important`
|
? `${darken(theme.palette.action.hover, 0.15)} !important`
|
||||||
: `${theme.palette.action.hover} !important`,
|
: `${theme.palette.action.hover} !important`,
|
||||||
},
|
},
|
||||||
[`&.${optionUnstyledClasses.disabled}`]: {
|
[`&.${baseOptionClasses.disabled}`]: {
|
||||||
color: theme.palette.text.disabled,
|
color: theme.palette.text.disabled,
|
||||||
},
|
},
|
||||||
[`&.${optionUnstyledClasses.disabled}:hover`]: {
|
[`&.${baseOptionClasses.disabled}:hover`]: {
|
||||||
backgroundColor: 'transparent !important',
|
backgroundColor: 'transparent !important',
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import type { OptionGroupUnstyledProps } from '@mui/base/OptionGroupUnstyled';
|
import {
|
||||||
import OptionGroupUnstyled from '@mui/base/OptionGroupUnstyled';
|
OptionGroup as BaseOptionGroup,
|
||||||
|
type OptionGroupProps as BaseOptionGroupProps,
|
||||||
|
} from '@mui/base';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import type { ForwardedRef } from 'react';
|
import type { ForwardedRef } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import OptionGroupBase from './OptionGroupBase';
|
import OptionGroupBase from './OptionGroupBase';
|
||||||
|
|
||||||
export interface OptionGroupProps extends OptionGroupUnstyledProps {}
|
export interface OptionGroupProps extends BaseOptionGroupProps {}
|
||||||
|
|
||||||
const StyledGroupRoot = styled('li')(({ theme }) => ({
|
const StyledGroupRoot = styled('li')(({ theme }) => ({
|
||||||
listStyle: 'none',
|
listStyle: 'none',
|
||||||
@@ -25,7 +27,7 @@ function OptionGroup(
|
|||||||
...externalSlots,
|
...externalSlots,
|
||||||
};
|
};
|
||||||
|
|
||||||
return <OptionGroupUnstyled {...props} ref={ref} slots={slots} />;
|
return <BaseOptionGroup {...props} ref={ref} slots={slots} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
OptionGroup.displayName = 'NhostOptionGroup';
|
OptionGroup.displayName = 'NhostOptionGroup';
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { FormControlProps } from '@/components/ui/v2/FormControl';
|
import type { FormControlProps } from '@/components/ui/v2/FormControl';
|
||||||
import { FormControl } from '@/components/ui/v2/FormControl';
|
import { FormControl } from '@/components/ui/v2/FormControl';
|
||||||
import PopperUnstyled from '@mui/base/PopperUnstyled';
|
import { Popper as BasePopper } from '@mui/base/Popper';
|
||||||
import type { SelectUnstyledProps } from '@mui/base/SelectUnstyled';
|
import type { SelectProps as BaseSelectProps } from '@mui/base/Select';
|
||||||
import SelectUnstyled from '@mui/base/SelectUnstyled';
|
import { Select as BaseSelect } from '@mui/base/Select';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/system';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type { ForwardedRef, PropsWithoutRef } from 'react';
|
import type { ForwardedRef, PropsWithoutRef } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
@@ -11,7 +11,7 @@ import type { ToggleButtonProps } from './ToggleButton';
|
|||||||
import ToggleButton from './ToggleButton';
|
import ToggleButton from './ToggleButton';
|
||||||
|
|
||||||
export interface SelectProps<TValue extends {}>
|
export interface SelectProps<TValue extends {}>
|
||||||
extends SelectUnstyledProps<TValue>,
|
extends BaseSelectProps<TValue, false>,
|
||||||
Pick<
|
Pick<
|
||||||
FormControlProps,
|
FormControlProps,
|
||||||
| 'fullWidth'
|
| 'fullWidth'
|
||||||
@@ -25,7 +25,7 @@ export interface SelectProps<TValue extends {}>
|
|||||||
/**
|
/**
|
||||||
* Props for component slots.
|
* Props for component slots.
|
||||||
*/
|
*/
|
||||||
slotProps?: SelectUnstyledProps<TValue>['slotProps'] & {
|
slotProps?: BaseSelectProps<TValue, false>['slotProps'] & {
|
||||||
root?: Partial<PropsWithoutRef<ToggleButtonProps>>;
|
root?: Partial<PropsWithoutRef<ToggleButtonProps>>;
|
||||||
label?: Partial<FormControlProps['labelProps']>;
|
label?: Partial<FormControlProps['labelProps']>;
|
||||||
formControl?: Partial<FormControlProps>;
|
formControl?: Partial<FormControlProps>;
|
||||||
@@ -59,8 +59,8 @@ const StyledListbox = styled('ul')(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledPopper = styled(PopperUnstyled)`
|
const StyledPopper = styled(BasePopper)`
|
||||||
z-index: 10;
|
z-index: 9999;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function Select<TValue>(
|
function Select<TValue>(
|
||||||
@@ -80,7 +80,7 @@ function Select<TValue>(
|
|||||||
}: SelectProps<TValue>,
|
}: SelectProps<TValue>,
|
||||||
ref: ForwardedRef<HTMLButtonElement>,
|
ref: ForwardedRef<HTMLButtonElement>,
|
||||||
) {
|
) {
|
||||||
const slots: SelectUnstyledProps<TValue>['slots'] = {
|
const slots: BaseSelectProps<TValue, false>['slots'] = {
|
||||||
root: ToggleButton,
|
root: ToggleButton,
|
||||||
popper: StyledPopper,
|
popper: StyledPopper,
|
||||||
listbox: StyledListbox,
|
listbox: StyledListbox,
|
||||||
@@ -107,7 +107,7 @@ function Select<TValue>(
|
|||||||
htmlFor: props.id,
|
htmlFor: props.id,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectUnstyled
|
<BaseSelect
|
||||||
aria-label={typeof label === 'string' ? label : undefined}
|
aria-label={typeof label === 'string' ? label : undefined}
|
||||||
{...props}
|
{...props}
|
||||||
className={clsx(error && 'error')}
|
className={clsx(error && 'error')}
|
||||||
@@ -117,7 +117,6 @@ function Select<TValue>(
|
|||||||
...slotProps,
|
...slotProps,
|
||||||
root: {
|
root: {
|
||||||
...slotProps?.root,
|
...slotProps?.root,
|
||||||
placeholder,
|
|
||||||
},
|
},
|
||||||
listbox: {
|
listbox: {
|
||||||
...slotProps?.listbox,
|
...slotProps?.listbox,
|
||||||
@@ -132,7 +131,7 @@ function Select<TValue>(
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</SelectUnstyled>
|
</BaseSelect>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { ChevronDownIcon } from '@/components/ui/v2/icons/ChevronDownIcon';
|
import { ChevronDownIcon } from '@/components/ui/v2/icons/ChevronDownIcon';
|
||||||
import { ChevronUpIcon } from '@/components/ui/v2/icons/ChevronUpIcon';
|
import { ChevronUpIcon } from '@/components/ui/v2/icons/ChevronUpIcon';
|
||||||
import type { ButtonUnstyledProps } from '@mui/base/ButtonUnstyled';
|
import {
|
||||||
import ButtonUnstyled from '@mui/base/ButtonUnstyled';
|
Button as ButtonUnstyled,
|
||||||
import { selectUnstyledClasses } from '@mui/base/SelectUnstyled';
|
type ButtonProps as ButtonUnstyledProps,
|
||||||
|
} from '@mui/base';
|
||||||
|
import { selectClasses as selectUnstyledClasses } from '@mui/base/Select';
|
||||||
import type { SxProps } from '@mui/material';
|
import type { SxProps } from '@mui/material';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import type { Theme } from '@mui/system';
|
import type { Theme } from '@mui/system';
|
||||||
@@ -24,6 +26,7 @@ export interface ToggleButtonProps
|
|||||||
Omit<DetailedHTMLProps<HTMLProps<HTMLSpanElement>, HTMLSpanElement>, 'as'>
|
Omit<DetailedHTMLProps<HTMLProps<HTMLSpanElement>, HTMLSpanElement>, 'as'>
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
placeholder?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledButton = styled(ButtonUnstyled)(({ theme }) => ({
|
const StyledButton = styled(ButtonUnstyled)(({ theme }) => ({
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import type { FormControlLabelProps } from '@/components/ui/v2/FormControlLabel';
|
import type { FormControlLabelProps } from '@/components/ui/v2/FormControlLabel';
|
||||||
import { FormControlLabel } from '@/components/ui/v2/FormControlLabel';
|
import { FormControlLabel } from '@/components/ui/v2/FormControlLabel';
|
||||||
import SwitchUnstyled, {
|
import {
|
||||||
switchUnstyledClasses,
|
Switch as BaseSwitch,
|
||||||
} from '@mui/base/SwitchUnstyled';
|
switchClasses as baseSwitchClasses,
|
||||||
import type { SwitchUnstyledProps } from '@mui/base/SwitchUnstyled/SwitchUnstyled.types';
|
} from '@mui/base';
|
||||||
|
import type { SwitchProps as BaseSwitchProps } from '@mui/base/Switch';
|
||||||
import { styled } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import type { ForwardedRef, PropsWithoutRef } from 'react';
|
import type { ForwardedRef, PropsWithoutRef } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
export interface SwitchProps extends SwitchUnstyledProps {
|
export interface SwitchProps extends BaseSwitchProps {
|
||||||
/**
|
/**
|
||||||
* Label to be displayed next to the checkbox.
|
* Label to be displayed next to the checkbox.
|
||||||
*/
|
*/
|
||||||
@@ -16,11 +17,11 @@ export interface SwitchProps extends SwitchUnstyledProps {
|
|||||||
/**
|
/**
|
||||||
* Props to be passed to the internal components.
|
* Props to be passed to the internal components.
|
||||||
*/
|
*/
|
||||||
slotProps?: SwitchUnstyledProps['slotProps'] & {
|
slotProps?: BaseSwitchProps['slotProps'] & {
|
||||||
/**
|
/**
|
||||||
* Props to be passed to the `Switch` component.
|
* Props to be passed to the `Switch` component.
|
||||||
*/
|
*/
|
||||||
root?: Partial<SwitchUnstyledProps>;
|
root?: Partial<BaseSwitchProps>;
|
||||||
/**
|
/**
|
||||||
* Props to be passed to the `FormControlLabel` component.
|
* Props to be passed to the `FormControlLabel` component.
|
||||||
*/
|
*/
|
||||||
@@ -35,23 +36,23 @@ const StyledFormControlLabel = styled(FormControlLabel)(({ theme }) => ({
|
|||||||
justifyContent: 'start',
|
justifyContent: 'start',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledSwitch = styled(SwitchUnstyled)(({ theme }) => ({
|
const StyledSwitch = styled(BaseSwitch)(({ theme }) => ({
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
width: '40px',
|
width: '40px',
|
||||||
height: '24px',
|
height: '24px',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
|
||||||
[`&.${switchUnstyledClasses.disabled}`]: {
|
[`&.${baseSwitchClasses.disabled}`]: {
|
||||||
cursor: 'not-allowed',
|
cursor: 'not-allowed',
|
||||||
|
|
||||||
[`& .${switchUnstyledClasses.track}`]: {
|
[`& .${baseSwitchClasses.track}`]: {
|
||||||
backgroundColor: theme.palette.grey[200],
|
backgroundColor: theme.palette.grey[200],
|
||||||
color: theme.palette.grey[200],
|
color: theme.palette.grey[200],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
[`& .${switchUnstyledClasses.track}`]: {
|
[`& .${baseSwitchClasses.track}`]: {
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
? theme.palette.grey[500]
|
? theme.palette.grey[500]
|
||||||
@@ -63,7 +64,7 @@ const StyledSwitch = styled(SwitchUnstyled)(({ theme }) => ({
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
},
|
},
|
||||||
|
|
||||||
[` & .${switchUnstyledClasses.thumb}`]: {
|
[` & .${baseSwitchClasses.thumb}`]: {
|
||||||
display: 'block',
|
display: 'block',
|
||||||
width: '18px',
|
width: '18px',
|
||||||
height: '18px',
|
height: '18px',
|
||||||
@@ -77,24 +78,24 @@ const StyledSwitch = styled(SwitchUnstyled)(({ theme }) => ({
|
|||||||
transitionDuration: '120ms',
|
transitionDuration: '120ms',
|
||||||
},
|
},
|
||||||
|
|
||||||
[`&.${switchUnstyledClasses.focusVisible} .${switchUnstyledClasses.thumb}`]: {
|
[`&.${baseSwitchClasses.focusVisible} .${baseSwitchClasses.thumb}`]: {
|
||||||
backgroundColor: theme.palette.action.focus,
|
backgroundColor: theme.palette.action.focus,
|
||||||
boxShadow: '0 0 1px 8px rgba(0, 0, 0, 0.25)',
|
boxShadow: '0 0 1px 8px rgba(0, 0, 0, 0.25)',
|
||||||
},
|
},
|
||||||
|
|
||||||
[`&.${switchUnstyledClasses.checked}`]: {
|
[`&.${baseSwitchClasses.checked}`]: {
|
||||||
[`.${switchUnstyledClasses.thumb}`]: {
|
[`.${baseSwitchClasses.thumb}`]: {
|
||||||
left: '19px',
|
left: '19px',
|
||||||
top: '3px',
|
top: '3px',
|
||||||
backgroundColor: theme.palette.common.white,
|
backgroundColor: theme.palette.common.white,
|
||||||
},
|
},
|
||||||
|
|
||||||
[`.${switchUnstyledClasses.track}`]: {
|
[`.${baseSwitchClasses.track}`]: {
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.main,
|
||||||
},
|
},
|
||||||
|
|
||||||
[`&.${switchUnstyledClasses.disabled}`]: {
|
[`&.${baseSwitchClasses.disabled}`]: {
|
||||||
[`.${switchUnstyledClasses.track}`]: {
|
[`.${baseSwitchClasses.track}`]: {
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
@@ -104,7 +105,7 @@ const StyledSwitch = styled(SwitchUnstyled)(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
[`& .${switchUnstyledClasses.input}`]: {
|
[`& .${baseSwitchClasses.input}`]: {
|
||||||
cursor: 'inherit',
|
cursor: 'inherit',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import {
|
|||||||
} from '@/features/ai/DevAssistant/state';
|
} from '@/features/ai/DevAssistant/state';
|
||||||
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
|
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
|
import { useIsGraphiteEnabled } from '@/features/projects/common/hooks/useIsGraphiteEnabled';
|
||||||
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||||
import {
|
import {
|
||||||
useSendDevMessageMutation,
|
useSendDevMessageMutation,
|
||||||
@@ -33,6 +35,7 @@ export type Message = Omit<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
export default function DevAssistant() {
|
export default function DevAssistant() {
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
const { currentProject, currentWorkspace } = useCurrentWorkspaceAndProject();
|
const { currentProject, currentWorkspace } = useCurrentWorkspaceAndProject();
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -45,6 +48,8 @@ export default function DevAssistant() {
|
|||||||
const [startDevSession] = useStartDevSessionMutation({ client: adminClient });
|
const [startDevSession] = useStartDevSessionMutation({ client: adminClient });
|
||||||
const [sendDevMessage] = useSendDevMessageMutation({ client: adminClient });
|
const [sendDevMessage] = useSendDevMessageMutation({ client: adminClient });
|
||||||
|
|
||||||
|
const { isGraphiteEnabled } = useIsGraphiteEnabled();
|
||||||
|
|
||||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
@@ -141,7 +146,7 @@ export default function DevAssistant() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (currentProject.plan.isFree) {
|
if (isPlatform && currentProject?.plan?.isFree) {
|
||||||
return (
|
return (
|
||||||
<Box className="p-4">
|
<Box className="p-4">
|
||||||
<UpgradeToProBanner
|
<UpgradeToProBanner
|
||||||
@@ -157,7 +162,12 @@ export default function DevAssistant() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentProject.plan.isFree && !currentProject.config?.ai) {
|
if (
|
||||||
|
(isPlatform &&
|
||||||
|
!currentProject?.plan?.isFree &&
|
||||||
|
!currentProject.config?.ai) ||
|
||||||
|
!isGraphiteEnabled
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<Box className="p-4">
|
<Box className="p-4">
|
||||||
<Alert className="grid w-full grid-flow-col place-content-between items-center gap-2">
|
<Alert className="grid w-full grid-flow-col place-content-between items-center gap-2">
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import messagesState, {
|
||||||
messagesState,
|
|
||||||
type ProjectMessage,
|
type ProjectMessage,
|
||||||
} from '@/features/ai/DevAssistant/state';
|
} from '@/features/ai/DevAssistant/state/messages';
|
||||||
import { selectorFamily } from 'recoil';
|
import { selectorFamily } from 'recoil';
|
||||||
|
|
||||||
const projectMessagesState = selectorFamily<ProjectMessage[], string>({
|
const projectMessagesState = selectorFamily<ProjectMessage[], string>({
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { Form } from '@/components/form/Form';
|
|||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { Alert } from '@/components/ui/v2/Alert';
|
import { Alert } from '@/components/ui/v2/Alert';
|
||||||
import { filterOptions } from '@/components/ui/v2/Autocomplete';
|
|
||||||
import { Box } from '@/components/ui/v2/Box';
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
|
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
|
||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
@@ -95,8 +94,8 @@ export default function AISettings() {
|
|||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
version: {
|
version: {
|
||||||
label: '0.1.0-beta4',
|
label: ai?.version ?? availableVersions?.at(0)?.label,
|
||||||
value: '0.1.0-beta4',
|
value: ai?.version ?? availableVersions?.at(0)?.value,
|
||||||
},
|
},
|
||||||
webhookSecret: '',
|
webhookSecret: '',
|
||||||
organization: '',
|
organization: '',
|
||||||
@@ -110,12 +109,17 @@ export default function AISettings() {
|
|||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { register, formState, reset, watch } = form;
|
const { register, formState, reset, watch, setValue } = form;
|
||||||
|
|
||||||
|
const aiSettingsFormValues = watch();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ai) {
|
if (ai) {
|
||||||
reset({
|
reset({
|
||||||
version: { label: ai?.version, value: ai?.version },
|
version: {
|
||||||
|
label: ai?.version,
|
||||||
|
value: ai?.version,
|
||||||
|
},
|
||||||
webhookSecret: ai?.webhookSecret,
|
webhookSecret: ai?.webhookSecret,
|
||||||
synchPeriodMinutes: ai?.autoEmbeddings?.synchPeriodMinutes,
|
synchPeriodMinutes: ai?.autoEmbeddings?.synchPeriodMinutes,
|
||||||
apiKey: ai?.openai?.apiKey,
|
apiKey: ai?.openai?.apiKey,
|
||||||
@@ -130,10 +134,27 @@ export default function AISettings() {
|
|||||||
setAIServiceEnabled(!!ai);
|
setAIServiceEnabled(!!ai);
|
||||||
}, [ai, reset]);
|
}, [ai, reset]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
!loadingGraphiteVersionsData &&
|
||||||
|
availableVersions.length > 0 &&
|
||||||
|
!ai &&
|
||||||
|
!aiSettingsFormValues.version.value
|
||||||
|
) {
|
||||||
|
setValue('version', availableVersions?.at(0));
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
ai,
|
||||||
|
setValue,
|
||||||
|
availableVersions,
|
||||||
|
aiSettingsFormValues,
|
||||||
|
loadingGraphiteVersionsData,
|
||||||
|
]);
|
||||||
|
|
||||||
const toggleAIService = async (enabled: boolean) => {
|
const toggleAIService = async (enabled: boolean) => {
|
||||||
setAIServiceEnabled(enabled);
|
setAIServiceEnabled(enabled);
|
||||||
|
|
||||||
if (!enabled) {
|
if (!enabled && ai) {
|
||||||
openDialog({
|
openDialog({
|
||||||
title: 'Confirm Disabling the AI service',
|
title: 'Confirm Disabling the AI service',
|
||||||
component: (
|
component: (
|
||||||
@@ -203,8 +224,6 @@ export default function AISettings() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const aiSettingsFormValues = watch();
|
|
||||||
|
|
||||||
const getAIResourcesCost = () => {
|
const getAIResourcesCost = () => {
|
||||||
const vCPUs = `${
|
const vCPUs = `${
|
||||||
aiSettingsFormValues.compute.cpu / RESOURCE_VCPU_MULTIPLIER
|
aiSettingsFormValues.compute.cpu / RESOURCE_VCPU_MULTIPLIER
|
||||||
@@ -240,37 +259,54 @@ export default function AISettings() {
|
|||||||
className="flex flex-col"
|
className="flex flex-col"
|
||||||
>
|
>
|
||||||
<Box className="space-y-4">
|
<Box className="space-y-4">
|
||||||
<Box className="space-y-2">
|
{availableVersions.length > 0 && (
|
||||||
<Box className="flex flex-row items-center space-x-2">
|
<Box className="space-y-2">
|
||||||
<Text className="text-lg font-semibold">Version</Text>
|
<Box className="flex flex-row items-center space-x-2">
|
||||||
<Tooltip title="Version of the service to use.">
|
<Text className="text-lg font-semibold">Version</Text>
|
||||||
<InfoIcon
|
<Tooltip title="Version of the service to use.">
|
||||||
aria-label="Info"
|
<InfoIcon
|
||||||
className="h-4 w-4"
|
aria-label="Info"
|
||||||
color="primary"
|
className="h-4 w-4"
|
||||||
/>
|
color="primary"
|
||||||
</Tooltip>
|
/>
|
||||||
</Box>
|
</Tooltip>
|
||||||
<ControlledAutocomplete
|
</Box>
|
||||||
id="version"
|
<ControlledAutocomplete
|
||||||
name="version"
|
id="version"
|
||||||
filterOptions={(options, state) => {
|
name="version"
|
||||||
if (state.inputValue === ai?.version) {
|
autoHighlight
|
||||||
return options;
|
isOptionEqualToValue={() => false}
|
||||||
|
filterOptions={(options, { inputValue }) => {
|
||||||
|
const inputValueLower = inputValue.toLowerCase();
|
||||||
|
const matched = [];
|
||||||
|
const otherOptions = [];
|
||||||
|
|
||||||
|
options.forEach((option) => {
|
||||||
|
const optionLabelLower = option.label.toLowerCase();
|
||||||
|
|
||||||
|
if (optionLabelLower.startsWith(inputValueLower)) {
|
||||||
|
matched.push(option);
|
||||||
|
} else {
|
||||||
|
otherOptions.push(option);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = [...matched, ...otherOptions];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
className="col-span-4"
|
||||||
|
options={availableVersions}
|
||||||
|
error={!!formState.errors?.version?.message}
|
||||||
|
helperText={formState.errors?.version?.message}
|
||||||
|
showCustomOption="auto"
|
||||||
|
customOptionLabel={(value) =>
|
||||||
|
`Use custom value: "${value}"`
|
||||||
}
|
}
|
||||||
return filterOptions(options, state);
|
/>
|
||||||
}}
|
</Box>
|
||||||
fullWidth
|
)}
|
||||||
className="col-span-4"
|
|
||||||
options={availableVersions}
|
|
||||||
error={!!formState.errors?.version?.message}
|
|
||||||
helperText={formState.errors?.version?.message}
|
|
||||||
showCustomOption="auto"
|
|
||||||
customOptionLabel={(value) =>
|
|
||||||
`Use custom value: "${value}"`
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box className="space-y-2">
|
<Box className="space-y-2">
|
||||||
<Box className="flex flex-row items-center space-x-2">
|
<Box className="flex flex-row items-center space-x-2">
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete
|
|||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { filterOptions } from '@/components/ui/v2/Autocomplete';
|
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetAuthenticationSettingsDocument,
|
GetAuthenticationSettingsDocument,
|
||||||
@@ -134,12 +133,26 @@ export default function AuthServiceVersionSettings() {
|
|||||||
<ControlledAutocomplete
|
<ControlledAutocomplete
|
||||||
id="version"
|
id="version"
|
||||||
name="version"
|
name="version"
|
||||||
filterOptions={(options, state) => {
|
autoHighlight
|
||||||
if (state.inputValue === version) {
|
isOptionEqualToValue={() => false}
|
||||||
return options;
|
filterOptions={(options, { inputValue }) => {
|
||||||
}
|
const inputValueLower = inputValue.toLowerCase();
|
||||||
|
const matched = [];
|
||||||
|
const otherOptions = [];
|
||||||
|
|
||||||
return filterOptions(options, state);
|
options.forEach((option) => {
|
||||||
|
const optionLabelLower = option.label.toLowerCase();
|
||||||
|
|
||||||
|
if (optionLabelLower.startsWith(inputValueLower)) {
|
||||||
|
matched.push(option);
|
||||||
|
} else {
|
||||||
|
otherOptions.push(option);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = [...matched, ...otherOptions];
|
||||||
|
|
||||||
|
return result;
|
||||||
}}
|
}}
|
||||||
fullWidth
|
fullWidth
|
||||||
className="lg:col-span-2"
|
className="lg:col-span-2"
|
||||||
|
|||||||
@@ -196,14 +196,14 @@ export default function EditUserForm({
|
|||||||
className="flex flex-col overflow-hidden border-t-1 lg:flex-auto lg:content-between"
|
className="flex flex-col overflow-hidden border-t-1 lg:flex-auto lg:content-between"
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
>
|
>
|
||||||
<Box className="flex-auto divide-y overflow-y-auto">
|
<Box className="flex-auto overflow-y-auto divide-y">
|
||||||
<Box
|
<Box
|
||||||
component="section"
|
component="section"
|
||||||
className="grid grid-flow-col p-6 lg:grid-cols-7"
|
className="grid grid-flow-col p-6 lg:grid-cols-7"
|
||||||
>
|
>
|
||||||
<div className="col-span-6 grid grid-flow-col place-content-start items-center gap-4">
|
<div className="grid items-center grid-flow-col col-span-6 gap-4 place-content-start">
|
||||||
<Avatar className="h-12 w-12" src={user.avatarUrl} />
|
<Avatar className="w-12 h-12" src={user.avatarUrl} />
|
||||||
<div className="grid grid-flow-row items-center">
|
<div className="grid items-center grid-flow-row">
|
||||||
<Text className="text-lg font-medium">{user.displayName}</Text>
|
<Text className="text-lg font-medium">{user.displayName}</Text>
|
||||||
<Text className="text-sm+ font-normal" color="secondary">
|
<Text className="text-sm+ font-normal" color="secondary">
|
||||||
{user.email}
|
{user.email}
|
||||||
@@ -225,7 +225,7 @@ export default function EditUserForm({
|
|||||||
Actions
|
Actions
|
||||||
</Button>
|
</Button>
|
||||||
</Dropdown.Trigger>
|
</Dropdown.Trigger>
|
||||||
<Dropdown.Content menu className="h-full w-full">
|
<Dropdown.Content menu className="w-full h-full">
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
className="font-medium"
|
className="font-medium"
|
||||||
sx={{ color: 'error.main' }}
|
sx={{ color: 'error.main' }}
|
||||||
@@ -253,11 +253,11 @@ export default function EditUserForm({
|
|||||||
component="section"
|
component="section"
|
||||||
className="grid grid-flow-row grid-cols-4 gap-8 p-6"
|
className="grid grid-flow-row grid-cols-4 gap-8 p-6"
|
||||||
>
|
>
|
||||||
<InputLabel as="h3" className="col-span-1 self-center">
|
<InputLabel as="h3" className="self-center col-span-1">
|
||||||
User ID
|
User ID
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<div className="col-span-3 grid grid-flow-col items-center justify-start gap-2">
|
<div className="grid items-center justify-start grid-flow-col col-span-3 gap-2">
|
||||||
<Text className="truncate font-medium">{user.id}</Text>
|
<Text className="font-medium truncate">{user.id}</Text>
|
||||||
<IconButton
|
<IconButton
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
@@ -267,18 +267,18 @@ export default function EditUserForm({
|
|||||||
copy(user.id, 'User ID');
|
copy(user.id, 'User ID');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon className="h-4 w-4" />
|
<CopyIcon className="w-4 h-4" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<InputLabel as="h3" className="col-span-1 self-center ">
|
<InputLabel as="h3" className="self-center col-span-1 ">
|
||||||
Created At
|
Created At
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Text className="col-span-3 font-medium">
|
<Text className="col-span-3 font-medium">
|
||||||
{format(new Date(user.createdAt), 'yyyy-MM-dd HH:mm:ss')}
|
{format(new Date(user.createdAt), 'yyyy-MM-dd HH:mm:ss')}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<InputLabel as="h3" className="col-span-1 self-center ">
|
<InputLabel as="h3" className="self-center col-span-1 ">
|
||||||
Last Seen
|
Last Seen
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Text className="col-span-3 font-medium">
|
<Text className="col-span-3 font-medium">
|
||||||
@@ -336,14 +336,14 @@ export default function EditUserForm({
|
|||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="col-span-1 my-1 grid grid-flow-col grid-cols-8 items-center">
|
<div className="grid items-center grid-flow-col grid-cols-8 col-span-1 my-1">
|
||||||
<div className="col-span-2 ">
|
<div className="col-span-2 ">
|
||||||
<InputLabel as="h3">Password</InputLabel>
|
<InputLabel as="h3">Password</InputLabel>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
className="col-span-6 place-self-start px-2"
|
className="col-span-6 px-2 place-self-start"
|
||||||
onClick={handleChangeUserPassword}
|
onClick={handleChangeUserPassword}
|
||||||
>
|
>
|
||||||
Change
|
Change
|
||||||
@@ -392,12 +392,12 @@ export default function EditUserForm({
|
|||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
component="section"
|
component="section"
|
||||||
className="grid place-content-start gap-4 p-6 lg:grid-cols-4"
|
className="grid gap-4 p-6 place-content-start lg:grid-cols-4"
|
||||||
>
|
>
|
||||||
<div className="col-span-1 items-center self-center align-middle">
|
<div className="items-center self-center col-span-1 align-middle">
|
||||||
<InputLabel as="h3">OAuth Providers</InputLabel>
|
<InputLabel as="h3">OAuth Providers</InputLabel>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-3 grid w-full grid-flow-row gap-y-6">
|
<div className="grid w-full grid-flow-row col-span-3 gap-y-6">
|
||||||
{user.userProviders.length === 0 && (
|
{user.userProviders.length === 0 && (
|
||||||
<div className="grid grid-flow-col place-content-between gap-x-1">
|
<div className="grid grid-flow-col place-content-between gap-x-1">
|
||||||
<Text className="font-normal" color="disabled">
|
<Text className="font-normal" color="disabled">
|
||||||
@@ -408,10 +408,10 @@ export default function EditUserForm({
|
|||||||
|
|
||||||
{user.userProviders.map((provider) => (
|
{user.userProviders.map((provider) => (
|
||||||
<div
|
<div
|
||||||
className="grid grid-flow-col place-content-between gap-3"
|
className="grid grid-flow-col gap-3 place-content-between"
|
||||||
key={provider.id}
|
key={provider.id}
|
||||||
>
|
>
|
||||||
<div className="span-cols-1 grid grid-flow-col gap-2">
|
<div className="grid grid-flow-col gap-2 span-cols-1">
|
||||||
<Image
|
<Image
|
||||||
src={
|
src={
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
@@ -424,6 +424,7 @@ export default function EditUserForm({
|
|||||||
}
|
}
|
||||||
width={25}
|
width={25}
|
||||||
height={25}
|
height={25}
|
||||||
|
alt='Oauth provider logo'
|
||||||
/>
|
/>
|
||||||
<Text className="font-medium capitalize">
|
<Text className="font-medium capitalize">
|
||||||
{getReadableProviderName(provider.providerId)}
|
{getReadableProviderName(provider.providerId)}
|
||||||
@@ -436,7 +437,7 @@ export default function EditUserForm({
|
|||||||
{!isAnonymous && (
|
{!isAnonymous && (
|
||||||
<Box
|
<Box
|
||||||
component="section"
|
component="section"
|
||||||
className="grid grid-flow-row gap-y-10 p-6"
|
className="grid grid-flow-row p-6 gap-y-10"
|
||||||
>
|
>
|
||||||
<ControlledSelect
|
<ControlledSelect
|
||||||
{...register('defaultRole')}
|
{...register('defaultRole')}
|
||||||
@@ -456,11 +457,11 @@ export default function EditUserForm({
|
|||||||
</Option>
|
</Option>
|
||||||
))}
|
))}
|
||||||
</ControlledSelect>
|
</ControlledSelect>
|
||||||
<div className="grid grid-flow-row place-content-start gap-6 lg:grid-flow-col lg:grid-cols-8">
|
<div className="grid grid-flow-row gap-6 place-content-start lg:grid-flow-col lg:grid-cols-8">
|
||||||
<InputLabel as="h3" className="col-span-2">
|
<InputLabel as="h3" className="col-span-2">
|
||||||
Allowed Roles
|
Allowed Roles
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<div className="col-span-3 grid grid-flow-row gap-6">
|
<div className="grid grid-flow-row col-span-3 gap-6">
|
||||||
{roles.map((role, i) => (
|
{roles.map((role, i) => (
|
||||||
<ControlledCheckbox
|
<ControlledCheckbox
|
||||||
id={`roles.${i}`}
|
id={`roles.${i}`}
|
||||||
@@ -476,7 +477,7 @@ export default function EditUserForm({
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box className="grid w-full flex-shrink-0 snap-end grid-flow-col justify-between gap-3 place-self-end border-t-1 p-2">
|
<Box className="grid justify-between flex-shrink-0 w-full grid-flow-col gap-3 p-2 snap-end place-self-end border-t-1">
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
|
|||||||
@@ -226,12 +226,12 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
|||||||
|
|
||||||
if (!users) {
|
if (!users) {
|
||||||
return (
|
return (
|
||||||
<div className="h-screen w-screen overflow-hidden">
|
<div className="w-screen h-screen overflow-hidden">
|
||||||
<div className="absolute top-0 left-0 z-50 block h-full w-full">
|
<div className="absolute top-0 left-0 z-50 block w-full h-full">
|
||||||
<span className="top50percent relative top-1/2 mx-auto my-0 block">
|
<span className="relative block mx-auto my-0 top50percent top-1/2">
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
label="Loading users..."
|
label="Loading users..."
|
||||||
className="my-auto flex items-center justify-center"
|
className="flex items-center justify-center my-auto"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -269,7 +269,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
|||||||
}}
|
}}
|
||||||
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
|
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
|
||||||
>
|
>
|
||||||
<UserIcon className="h-4 w-4" />
|
<UserIcon className="w-4 h-4" />
|
||||||
<Text className="font-medium">View User</Text>
|
<Text className="font-medium">View User</Text>
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
|
|
||||||
@@ -280,7 +280,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
|||||||
sx={{ color: 'error.main' }}
|
sx={{ color: 'error.main' }}
|
||||||
onClick={() => handleDeleteUser(user)}
|
onClick={() => handleDeleteUser(user)}
|
||||||
>
|
>
|
||||||
<TrashIcon className="h-4 w-4" />
|
<TrashIcon className="w-4 h-4" />
|
||||||
<Text className="font-medium" color="error">
|
<Text className="font-medium" color="error">
|
||||||
Delete User
|
Delete User
|
||||||
</Text>
|
</Text>
|
||||||
@@ -294,14 +294,14 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
|||||||
onClick={() => handleViewUser(user)}
|
onClick={() => handleViewUser(user)}
|
||||||
aria-label={`View ${user.displayName}`}
|
aria-label={`View ${user.displayName}`}
|
||||||
>
|
>
|
||||||
<div className="col-span-2 grid grid-flow-col place-content-start gap-4">
|
<div className="grid grid-flow-col col-span-2 gap-4 place-content-start">
|
||||||
<Avatar
|
<Avatar
|
||||||
src={user.avatarUrl}
|
src={user.avatarUrl}
|
||||||
alt={`Avatar of ${user.displayName}`}
|
alt={`Avatar of ${user.displayName}`}
|
||||||
/>
|
/>
|
||||||
<div className="grid grid-flow-row items-center">
|
<div className="grid items-center grid-flow-row">
|
||||||
<div className="grid grid-flow-col items-center gap-2">
|
<div className="grid items-center grid-flow-col gap-2">
|
||||||
<Text className="truncate font-medium leading-5">
|
<Text className="font-medium leading-5 truncate">
|
||||||
{user.displayName}
|
{user.displayName}
|
||||||
</Text>
|
</Text>
|
||||||
{user.disabled && (
|
{user.disabled && (
|
||||||
@@ -314,7 +314,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Text className="truncate font-normal" color="secondary">
|
<Text className="font-normal truncate" color="secondary">
|
||||||
{user.email}
|
{user.email}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
@@ -334,7 +334,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
|||||||
: '-'}
|
: '-'}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<div className="col-span-2 hidden grid-flow-col place-content-start gap-3 px-4 lg:grid">
|
<div className="hidden grid-flow-col col-span-2 gap-3 px-4 place-content-start lg:grid">
|
||||||
{user.userProviders.length === 0 && (
|
{user.userProviders.length === 0 && (
|
||||||
<Text className="col-span-3 font-medium">-</Text>
|
<Text className="col-span-3 font-medium">-</Text>
|
||||||
)}
|
)}
|
||||||
@@ -362,6 +362,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
|||||||
}
|
}
|
||||||
width={16}
|
width={16}
|
||||||
height={16}
|
height={16}
|
||||||
|
alt='Oauth provider logo'
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -102,10 +102,10 @@ export default function BaseColumnForm({
|
|||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
onSubmit={handleExternalSubmit}
|
onSubmit={handleExternalSubmit}
|
||||||
className="flex flex-auto flex-col content-between overflow-hidden border-t-1"
|
className="flex flex-col content-between flex-auto overflow-hidden border-t-1"
|
||||||
>
|
>
|
||||||
<div className="flex-auto overflow-y-auto">
|
<div className="flex-auto overflow-y-auto">
|
||||||
<section className="grid grid-cols-8 py-3 px-6">
|
<section className="grid grid-cols-8 px-6 py-3">
|
||||||
<Input
|
<Input
|
||||||
{...register('name', {
|
{...register('name', {
|
||||||
onChange: (event) => {
|
onChange: (event) => {
|
||||||
@@ -184,7 +184,7 @@ export default function BaseColumnForm({
|
|||||||
</Text>
|
</Text>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
className="col-span-8 m-0 w-full py-3 sm:col-span-6 sm:col-start-3 sm:ml-1"
|
className="w-full col-span-8 py-3 m-0 sm:col-span-6 sm:col-start-3 sm:ml-1"
|
||||||
onChange={(_event, checked) => {
|
onChange={(_event, checked) => {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
setDefaultValueInputText('');
|
setDefaultValueInputText('');
|
||||||
@@ -197,7 +197,7 @@ export default function BaseColumnForm({
|
|||||||
|
|
||||||
<Box
|
<Box
|
||||||
component="section"
|
component="section"
|
||||||
className="grid grid-cols-8 border-t-1 py-3 px-6"
|
className="grid grid-cols-8 px-6 py-3 border-t-1"
|
||||||
>
|
>
|
||||||
<ControlledAutocomplete
|
<ControlledAutocomplete
|
||||||
id="defaultValue"
|
id="defaultValue"
|
||||||
@@ -249,7 +249,7 @@ export default function BaseColumnForm({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<ControlledCheckbox
|
<ControlledCheckbox
|
||||||
className="col-span-8 m-0 w-full py-3 sm:col-span-6 sm:col-start-3 sm:ml-1"
|
className="w-full col-span-8 py-3 m-0 sm:col-span-6 sm:col-start-3 sm:ml-1"
|
||||||
name="isNullable"
|
name="isNullable"
|
||||||
label={
|
label={
|
||||||
<span className="inline-grid grid-flow-row">
|
<span className="inline-grid grid-flow-row">
|
||||||
@@ -269,7 +269,7 @@ export default function BaseColumnForm({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<ControlledCheckbox
|
<ControlledCheckbox
|
||||||
className="col-span-8 m-0 w-full py-3 sm:col-span-6 sm:col-start-3 sm:ml-1"
|
className="w-full col-span-8 py-3 m-0 sm:col-span-6 sm:col-start-3 sm:ml-1"
|
||||||
name="isUnique"
|
name="isUnique"
|
||||||
label={
|
label={
|
||||||
<span className="inline-grid grid-flow-row">
|
<span className="inline-grid grid-flow-row">
|
||||||
@@ -306,7 +306,7 @@ export default function BaseColumnForm({
|
|||||||
</Box>
|
</Box>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Box className="grid flex-shrink-0 grid-flow-col justify-between gap-3 border-t-1 p-2">
|
<Box className="grid justify-between flex-shrink-0 grid-flow-col gap-3 p-2 border-t-1">
|
||||||
<Button
|
<Button
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
|
|||||||
@@ -107,9 +107,9 @@ export default function BaseForeignKeyForm({
|
|||||||
selectedColumn?.isPrimary || selectedColumn?.isUnique || false,
|
selectedColumn?.isPrimary || selectedColumn?.isUnique || false,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className="flex flex-auto flex-col content-between overflow-hidden pb-4"
|
className="flex flex-col content-between flex-auto pb-4 overflow-hidden"
|
||||||
>
|
>
|
||||||
<Box className="grid flex-auto grid-flow-row gap-4 overflow-y-auto border-t-1 py-4">
|
<Box className="grid flex-auto grid-flow-row gap-4 py-4 overflow-y-auto border-t-1">
|
||||||
<Box component="section" className="grid grid-flow-row gap-4 px-6">
|
<Box component="section" className="grid grid-flow-row gap-4 px-6">
|
||||||
<Text variant="h3">From</Text>
|
<Text variant="h3">From</Text>
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ export default function BaseForeignKeyForm({
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box className="grid flex-shrink-0 grid-flow-row gap-2 border-t-1 px-6 pt-4">
|
<Box className="grid flex-shrink-0 grid-flow-row gap-2 px-6 pt-4 border-t-1">
|
||||||
<Button loading={isSubmitting} disabled={isSubmitting} type="submit">
|
<Button loading={isSubmitting} disabled={isSubmitting} type="submit">
|
||||||
{submitButtonText}
|
{submitButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import { Text } from '@/components/ui/v2/Text';
|
|||||||
import { useMetadataQuery } from '@/features/database/dataGrid/hooks/useMetadataQuery';
|
import { useMetadataQuery } from '@/features/database/dataGrid/hooks/useMetadataQuery';
|
||||||
import { useTableQuery } from '@/features/database/dataGrid/hooks/useTableQuery';
|
import { useTableQuery } from '@/features/database/dataGrid/hooks/useTableQuery';
|
||||||
import { getTruncatedText } from '@/utils/getTruncatedText';
|
import { getTruncatedText } from '@/utils/getTruncatedText';
|
||||||
import type { AutocompleteGroupedOption } from '@mui/base/AutocompleteUnstyled';
|
import type { AutocompleteGroupedOption } from '@mui/base/useAutocomplete';
|
||||||
import { useAutocomplete } from '@mui/base/AutocompleteUnstyled';
|
import { useAutocomplete } from '@mui/base/useAutocomplete';
|
||||||
import type { AutocompleteRenderGroupParams } from '@mui/material/Autocomplete';
|
import type { AutocompleteRenderGroupParams } from '@mui/material/Autocomplete';
|
||||||
import { autocompleteClasses } from '@mui/material/Autocomplete';
|
import { autocompleteClasses } from '@mui/material/Autocomplete';
|
||||||
import type {
|
import type {
|
||||||
@@ -366,11 +366,11 @@ function ColumnAutocomplete(
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ArrowLeftIcon className="h-4 w-4" />
|
<ArrowLeftIcon className="w-4 h-4" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Text className="direction-rtl truncate text-left">
|
<Text className="text-left truncate direction-rtl">
|
||||||
<Text component="span" color="disabled">
|
<Text component="span" color="disabled">
|
||||||
{defaultTable}
|
{defaultTable}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { triggerToast } from '@/utils/toast';
|
|||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
import type * as Yup from 'yup';
|
||||||
|
|
||||||
export interface CreateColumnFormProps
|
export interface CreateColumnFormProps
|
||||||
extends Pick<BaseColumnFormProps, 'onCancel' | 'location'> {
|
extends Pick<BaseColumnFormProps, 'onCancel' | 'location'> {
|
||||||
@@ -50,7 +51,9 @@ export default function CreateColumnForm({
|
|||||||
resetForeignKeyError();
|
resetForeignKeyError();
|
||||||
}
|
}
|
||||||
|
|
||||||
const form = useForm<BaseColumnFormValues>({
|
const form = useForm<
|
||||||
|
BaseColumnFormValues | Yup.InferType<typeof baseColumnValidationSchema>
|
||||||
|
>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: '',
|
name: '',
|
||||||
type: null,
|
type: null,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
import type * as Yup from 'yup';
|
||||||
|
|
||||||
export interface CreateForeignKeyFormProps
|
export interface CreateForeignKeyFormProps
|
||||||
extends Pick<
|
extends Pick<
|
||||||
@@ -34,7 +35,10 @@ export default function CreateForeignKeyForm({
|
|||||||
}: CreateForeignKeyFormProps) {
|
}: CreateForeignKeyFormProps) {
|
||||||
const [error, setError] = useState<Error>(null);
|
const [error, setError] = useState<Error>(null);
|
||||||
|
|
||||||
const form = useForm<BaseForeignKeyFormValues>({
|
const form = useForm<
|
||||||
|
| BaseForeignKeyFormValues
|
||||||
|
| Yup.InferType<typeof baseForeignKeyValidationSchema>
|
||||||
|
>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: null,
|
id: null,
|
||||||
name: '',
|
name: '',
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { triggerToast } from '@/utils/toast';
|
|||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
import type * as Yup from 'yup';
|
||||||
|
|
||||||
export interface CreateTableFormProps
|
export interface CreateTableFormProps
|
||||||
extends Pick<BaseTableFormProps, 'onCancel' | 'location'> {
|
extends Pick<BaseTableFormProps, 'onCancel' | 'location'> {
|
||||||
@@ -53,7 +54,9 @@ export default function CreateTableForm({
|
|||||||
|
|
||||||
const error = createTableError || trackTableError || foreignKeyError;
|
const error = createTableError || trackTableError || foreignKeyError;
|
||||||
|
|
||||||
const form = useForm<BaseTableFormValues>({
|
const form = useForm<
|
||||||
|
BaseTableFormValues | Yup.InferType<typeof baseTableValidationSchema>
|
||||||
|
>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ function DataBrowserSidebarContent({
|
|||||||
) {
|
) {
|
||||||
openDrawer({
|
openDrawer({
|
||||||
title: (
|
title: (
|
||||||
<span className="inline-grid grid-flow-col items-center gap-2">
|
<span className="inline-grid items-center grid-flow-col gap-2">
|
||||||
Permissions
|
Permissions
|
||||||
<InlineCode className="!text-sm+ font-normal">{table}</InlineCode>
|
<InlineCode className="!text-sm+ font-normal">{table}</InlineCode>
|
||||||
<Chip label="Preview" size="small" color="info" component="span" />
|
<Chip label="Preview" size="small" color="info" component="span" />
|
||||||
@@ -263,12 +263,12 @@ function DataBrowserSidebarContent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="flex h-full flex-col justify-between">
|
<Box className="flex flex-col justify-between h-full">
|
||||||
<Box className="flex flex-col px-2">
|
<Box className="flex flex-col px-2">
|
||||||
{schemas && schemas.length > 0 && (
|
{schemas && schemas.length > 0 && (
|
||||||
<Select
|
<Select
|
||||||
renderValue={(option) => (
|
renderValue={(option) => (
|
||||||
<span className="grid grid-flow-col items-center gap-1">
|
<span className="grid items-center grid-flow-col gap-1">
|
||||||
{option?.label}
|
{option?.label}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -281,7 +281,7 @@ function DataBrowserSidebarContent({
|
|||||||
>
|
>
|
||||||
{schemas.map((schema) => (
|
{schemas.map((schema) => (
|
||||||
<Option
|
<Option
|
||||||
className="grid grid-flow-col items-center gap-1"
|
className="grid items-center grid-flow-col gap-1"
|
||||||
value={schema.schema_name}
|
value={schema.schema_name}
|
||||||
key={schema.schema_name}
|
key={schema.schema_name}
|
||||||
>
|
>
|
||||||
@@ -295,7 +295,7 @@ function DataBrowserSidebarContent({
|
|||||||
</Text>
|
</Text>
|
||||||
{(isSchemaLocked(schema.schema_name) || isGitHubConnected) && (
|
{(isSchemaLocked(schema.schema_name) || isGitHubConnected) && (
|
||||||
<LockIcon
|
<LockIcon
|
||||||
className="h-3 w-3"
|
className="w-3 h-3"
|
||||||
sx={{ color: 'text.secondary' }}
|
sx={{ color: 'text.secondary' }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -317,7 +317,7 @@ function DataBrowserSidebarContent({
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
underline="hover"
|
underline="hover"
|
||||||
className="grid grid-flow-col items-center justify-start gap-1"
|
className="grid items-center justify-start grid-flow-col gap-1"
|
||||||
>
|
>
|
||||||
Learn More <ArrowRightIcon />
|
Learn More <ArrowRightIcon />
|
||||||
</Link>
|
</Link>
|
||||||
@@ -327,7 +327,7 @@ function DataBrowserSidebarContent({
|
|||||||
<Button
|
<Button
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
endIcon={<PlusIcon />}
|
endIcon={<PlusIcon />}
|
||||||
className="mt-1 w-full justify-between px-2"
|
className="justify-between w-full px-2 mt-1"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openDrawer({
|
openDrawer({
|
||||||
title: 'Create a New Table',
|
title: 'Create a New Table',
|
||||||
@@ -396,7 +396,7 @@ function DataBrowserSidebarContent({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<UsersIcon
|
<UsersIcon
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
sx={{ color: 'text.secondary' }}
|
sx={{ color: 'text.secondary' }}
|
||||||
/>
|
/>
|
||||||
<span>View Permissions</span>
|
<span>View Permissions</span>
|
||||||
@@ -426,7 +426,7 @@ function DataBrowserSidebarContent({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<PencilIcon
|
<PencilIcon
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
sx={{ color: 'text.secondary' }}
|
sx={{ color: 'text.secondary' }}
|
||||||
/>
|
/>
|
||||||
<span>Edit Table</span>
|
<span>Edit Table</span>
|
||||||
@@ -449,7 +449,7 @@ function DataBrowserSidebarContent({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<UsersIcon
|
<UsersIcon
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
sx={{ color: 'text.secondary' }}
|
sx={{ color: 'text.secondary' }}
|
||||||
/>
|
/>
|
||||||
<span>Edit Permissions</span>
|
<span>Edit Permissions</span>
|
||||||
@@ -473,7 +473,7 @@ function DataBrowserSidebarContent({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<TrashIcon
|
<TrashIcon
|
||||||
className="h-4 w-4"
|
className="w-4 h-4"
|
||||||
sx={{ color: 'error.main' }}
|
sx={{ color: 'error.main' }}
|
||||||
/>
|
/>
|
||||||
<span>Delete Table</span>
|
<span>Delete Table</span>
|
||||||
@@ -521,7 +521,7 @@ function DataBrowserSidebarContent({
|
|||||||
component={NavLink}
|
component={NavLink}
|
||||||
href={sqlEditorHref}
|
href={sqlEditorHref}
|
||||||
>
|
>
|
||||||
<div className="flex w-full flex-row items-center justify-center space-x-4">
|
<div className="flex flex-row items-center justify-center w-full space-x-4">
|
||||||
<TerminalIcon />
|
<TerminalIcon />
|
||||||
<span className="flex">SQL Editor</span>
|
<span className="flex">SQL Editor</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { triggerToast } from '@/utils/toast';
|
|||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
import type * as Yup from 'yup';
|
||||||
|
|
||||||
export interface EditColumnFormProps
|
export interface EditColumnFormProps
|
||||||
extends Pick<BaseColumnFormProps, 'onCancel' | 'location'> {
|
extends Pick<BaseColumnFormProps, 'onCancel' | 'location'> {
|
||||||
@@ -78,7 +79,9 @@ export default function EditColumnForm({
|
|||||||
comment: originalColumn.comment || null,
|
comment: originalColumn.comment || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const form = useForm<BaseColumnFormValues>({
|
const form = useForm<
|
||||||
|
BaseColumnFormValues | Yup.InferType<typeof baseColumnValidationSchema>
|
||||||
|
>({
|
||||||
defaultValues: columnValues,
|
defaultValues: columnValues,
|
||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
resolver: yupResolver(baseColumnValidationSchema),
|
resolver: yupResolver(baseColumnValidationSchema),
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import type { ForeignKeyRelation } from '@/features/database/dataGrid/types/data
|
|||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
import type * as Yup from 'yup';
|
||||||
|
|
||||||
export interface EditForeignKeyFormProps
|
export interface EditForeignKeyFormProps
|
||||||
extends Pick<
|
extends Pick<
|
||||||
@@ -40,7 +41,10 @@ export default function EditForeignKeyForm({
|
|||||||
}: EditForeignKeyFormProps) {
|
}: EditForeignKeyFormProps) {
|
||||||
const [error, setError] = useState<Error>(null);
|
const [error, setError] = useState<Error>(null);
|
||||||
|
|
||||||
const form = useForm<BaseForeignKeyFormValues>({
|
const form = useForm<
|
||||||
|
| BaseForeignKeyFormValues
|
||||||
|
| Yup.InferType<typeof baseForeignKeyValidationSchema>
|
||||||
|
>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: foreignKeyRelation.id,
|
id: foreignKeyRelation.id,
|
||||||
name: foreignKeyRelation.name,
|
name: foreignKeyRelation.name,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { yupResolver } from '@hookform/resolvers/yup';
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
import type * as Yup from 'yup';
|
||||||
|
|
||||||
export interface EditTableFormProps
|
export interface EditTableFormProps
|
||||||
extends Pick<BaseTableFormProps, 'onCancel' | 'location'> {
|
extends Pick<BaseTableFormProps, 'onCancel' | 'location'> {
|
||||||
@@ -85,7 +86,9 @@ export default function EditTableForm({
|
|||||||
resetUpdateError();
|
resetUpdateError();
|
||||||
}
|
}
|
||||||
|
|
||||||
const form = useForm<BaseTableFormValues>({
|
const form = useForm<
|
||||||
|
BaseTableFormValues | Yup.InferType<typeof baseTableValidationSchema>
|
||||||
|
>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: originalTable.table_name,
|
name: originalTable.table_name,
|
||||||
columns: [],
|
columns: [],
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete
|
|||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { filterOptions } from '@/components/ui/v2/Autocomplete';
|
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetPostgresSettingsDocument,
|
GetPostgresSettingsDocument,
|
||||||
@@ -136,12 +135,26 @@ export default function DatabaseServiceVersionSettings() {
|
|||||||
<ControlledAutocomplete
|
<ControlledAutocomplete
|
||||||
id="version"
|
id="version"
|
||||||
name="version"
|
name="version"
|
||||||
filterOptions={(options, state) => {
|
autoHighlight
|
||||||
if (state.inputValue === version) {
|
isOptionEqualToValue={() => false}
|
||||||
return options;
|
filterOptions={(options, { inputValue }) => {
|
||||||
}
|
const inputValueLower = inputValue.toLowerCase();
|
||||||
|
const matched = [];
|
||||||
|
const otherOptions = [];
|
||||||
|
|
||||||
return filterOptions(options, state);
|
options.forEach((option) => {
|
||||||
|
const optionLabelLower = option.label.toLowerCase();
|
||||||
|
|
||||||
|
if (optionLabelLower.startsWith(inputValueLower)) {
|
||||||
|
matched.push(option);
|
||||||
|
} else {
|
||||||
|
otherOptions.push(option);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = [...matched, ...otherOptions];
|
||||||
|
|
||||||
|
return result;
|
||||||
}}
|
}}
|
||||||
fullWidth
|
fullWidth
|
||||||
className="lg:col-span-2"
|
className="lg:col-span-2"
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default function AuthDomain() {
|
|||||||
|
|
||||||
const [updateConfig] = useUpdateConfigMutation();
|
const [updateConfig] = useUpdateConfigMutation();
|
||||||
|
|
||||||
const form = useForm<{ capacity: number }>({
|
const form = useForm<Yup.InferType<typeof validationSchema>>({
|
||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
defaultValues: { capacity },
|
defaultValues: { capacity },
|
||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete
|
|||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { filterOptions } from '@/components/ui/v2/Autocomplete';
|
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetHasuraSettingsDocument,
|
GetHasuraSettingsDocument,
|
||||||
@@ -136,12 +135,26 @@ export default function HasuraServiceVersionSettings() {
|
|||||||
<ControlledAutocomplete
|
<ControlledAutocomplete
|
||||||
id="version"
|
id="version"
|
||||||
name="version"
|
name="version"
|
||||||
filterOptions={(options, state) => {
|
autoHighlight
|
||||||
if (state.inputValue === version) {
|
isOptionEqualToValue={() => false}
|
||||||
return options;
|
filterOptions={(options, { inputValue }) => {
|
||||||
}
|
const inputValueLower = inputValue.toLowerCase();
|
||||||
|
const matched = [];
|
||||||
|
const otherOptions = [];
|
||||||
|
|
||||||
return filterOptions(options, state);
|
options.forEach((option) => {
|
||||||
|
const optionLabelLower = option.label.toLowerCase();
|
||||||
|
|
||||||
|
if (optionLabelLower.startsWith(inputValueLower)) {
|
||||||
|
matched.push(option);
|
||||||
|
} else {
|
||||||
|
otherOptions.push(option);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = [...matched, ...otherOptions];
|
||||||
|
|
||||||
|
return result;
|
||||||
}}
|
}}
|
||||||
fullWidth
|
fullWidth
|
||||||
className="lg:col-span-2"
|
className="lg:col-span-2"
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { Box } from '@/components/ui/v2/Box';
|
|||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
import { Checkbox } from '@/components/ui/v2/Checkbox';
|
import { Checkbox } from '@/components/ui/v2/Checkbox';
|
||||||
import { BaseDialog } from '@/components/ui/v2/Dialog';
|
import { BaseDialog } from '@/components/ui/v2/Dialog';
|
||||||
import { Link } from '@/components/ui/v2/Link';
|
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { useAppState } from '@/features/projects/common/hooks/useAppState';
|
import { useAppState } from '@/features/projects/common/hooks/useAppState';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
@@ -85,6 +84,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
|
|||||||
|
|
||||||
const currentPlan = plans.find((plan) => plan.id === app.plan.id);
|
const currentPlan = plans.find((plan) => plan.id === app.plan.id);
|
||||||
const selectedPlan = plans.find((plan) => plan.id === selectedPlanId);
|
const selectedPlan = plans.find((plan) => plan.id === selectedPlanId);
|
||||||
|
const higherPlans = plans.filter((plan) => plan.price > currentPlan.price);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!pollingCurrentProject || state === ApplicationStatus.Paused) {
|
if (!pollingCurrentProject || state === ApplicationStatus.Paused) {
|
||||||
@@ -201,53 +201,6 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app.plan.id !== plans.find((plan) => plan.isFree)?.id) {
|
|
||||||
return (
|
|
||||||
<Box className="mx-auto w-full max-w-xl rounded-lg p-6 text-left">
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<div className="mx-auto">
|
|
||||||
<Image
|
|
||||||
src="/assets/upgrade.svg"
|
|
||||||
alt="Nhost Logo"
|
|
||||||
width={72}
|
|
||||||
height={72}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Text variant="h3" component="h2" className="mt-2 text-center">
|
|
||||||
Downgrade is not available
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text className="mt-1 text-center">
|
|
||||||
You can't downgrade from a paid plan to a free plan here.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text className="text-center">
|
|
||||||
Please contact us at{' '}
|
|
||||||
<Link href="mailto:info@nhost.io">info@nhost.io</Link> if you want
|
|
||||||
to downgrade.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<div className="mt-6 grid grid-flow-row gap-2">
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
color="secondary"
|
|
||||||
className="mx-auto w-full max-w-sm"
|
|
||||||
onClick={() => {
|
|
||||||
if (close) {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
closeAlertDialog();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="w-full max-w-xl rounded-lg p-6 text-left">
|
<Box className="w-full max-w-xl rounded-lg p-6 text-left">
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
@@ -277,7 +230,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
|
|||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
{plans
|
{higherPlans
|
||||||
.filter((plan) => plan.id !== app.plan.id)
|
.filter((plan) => plan.id !== app.plan.id)
|
||||||
.map((plan) => (
|
.map((plan) => (
|
||||||
<div className="mt-4" key={plan.id}>
|
<div className="mt-4" key={plan.id}>
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export default function WorkspaceAndProjectList({
|
|||||||
placeholder="Find Project"
|
placeholder="Find Project"
|
||||||
startAdornment={
|
startAdornment={
|
||||||
<SearchIcon
|
<SearchIcon
|
||||||
className="ml-2 -mr-1 h-4 w-4 shrink-0"
|
className="w-4 h-4 ml-2 -mr-1 shrink-0"
|
||||||
sx={{ color: 'text.disabled' }}
|
sx={{ color: 'text.disabled' }}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@ export default function WorkspaceAndProjectList({
|
|||||||
</NavLink>
|
</NavLink>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box className="my-8 grid grid-flow-row gap-8">
|
<Box className="grid grid-flow-row gap-8 my-8">
|
||||||
{filteredWorkspaces.map((workspace) => (
|
{filteredWorkspaces.map((workspace) => (
|
||||||
<div key={workspace.slug}>
|
<div key={workspace.slug}>
|
||||||
<NavLink href={`/${workspace.slug}`} passHref>
|
<NavLink href={`/${workspace.slug}`} passHref>
|
||||||
@@ -147,7 +147,7 @@ export default function WorkspaceAndProjectList({
|
|||||||
secondaryAction={
|
secondaryAction={
|
||||||
<div className="grid grid-flow-col gap-px">
|
<div className="grid grid-flow-col gap-px">
|
||||||
{latestDeployment && (
|
{latestDeployment && (
|
||||||
<div className="mr-2 flex self-center align-middle">
|
<div className="flex self-center mr-2 align-middle">
|
||||||
<StatusCircle
|
<StatusCircle
|
||||||
status={
|
status={
|
||||||
latestDeployment.deploymentStatus as DeploymentStatus
|
latestDeployment.deploymentStatus as DeploymentStatus
|
||||||
@@ -171,10 +171,11 @@ export default function WorkspaceAndProjectList({
|
|||||||
<NavLink
|
<NavLink
|
||||||
href={`${workspace?.slug}/${project.slug}`}
|
href={`${workspace?.slug}/${project.slug}`}
|
||||||
passHref
|
passHref
|
||||||
|
className='w-full'
|
||||||
>
|
>
|
||||||
<ListItem.Button className="rounded-none">
|
<ListItem.Button className="rounded-none">
|
||||||
<ListItem.Avatar>
|
<ListItem.Avatar>
|
||||||
<div className="h-10 w-10 overflow-hidden rounded-lg">
|
<div className="w-10 h-10 overflow-hidden rounded-lg">
|
||||||
<Image
|
<Image
|
||||||
src="/logos/new.svg"
|
src="/logos/new.svg"
|
||||||
alt="Nhost Logo"
|
alt="Nhost Logo"
|
||||||
|
|||||||
@@ -48,14 +48,14 @@ export default function WorkspaceSidebar({
|
|||||||
<List className="grid grid-flow-row gap-2">
|
<List className="grid grid-flow-row gap-2">
|
||||||
{workspaces.map(({ id, name, slug }) => (
|
{workspaces.map(({ id, name, slug }) => (
|
||||||
<ListItem.Root key={id}>
|
<ListItem.Root key={id}>
|
||||||
<NavLink href={`/${slug}`} passHref>
|
<NavLink href={`/${slug}`} passHref className='w-full'>
|
||||||
<ListItem.Button
|
<ListItem.Button
|
||||||
dense
|
dense
|
||||||
aria-label={`View ${name}`}
|
aria-label={`View ${name}`}
|
||||||
className="!p-1"
|
className="!p-1"
|
||||||
>
|
>
|
||||||
<ListItem.Avatar className="h-8 w-8">
|
<ListItem.Avatar className="w-8 h-8">
|
||||||
<div className="inline-block h-8 w-8 overflow-hidden rounded-lg">
|
<div className="inline-block w-8 h-8 overflow-hidden rounded-lg">
|
||||||
<Image
|
<Image
|
||||||
src="/logos/new.svg"
|
src="/logos/new.svg"
|
||||||
alt="Nhost Logo"
|
alt="Nhost Logo"
|
||||||
@@ -107,7 +107,7 @@ export default function WorkspaceSidebar({
|
|||||||
<div className="grid grid-flow-row gap-2">
|
<div className="grid grid-flow-row gap-2">
|
||||||
<Resource
|
<Resource
|
||||||
text="Documentation"
|
text="Documentation"
|
||||||
logo="Question"
|
logo="Note"
|
||||||
link="https://docs.nhost.io"
|
link="https://docs.nhost.io"
|
||||||
/>
|
/>
|
||||||
<Resource
|
<Resource
|
||||||
@@ -131,7 +131,7 @@ export default function WorkspaceSidebar({
|
|||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="grid grid-flow-col gap-1"
|
className="grid w-full grid-flow-col gap-1"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
startIcon={<GitHubIcon />}
|
startIcon={<GitHubIcon />}
|
||||||
@@ -147,7 +147,7 @@ export default function WorkspaceSidebar({
|
|||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
className="grid grid-flow-col gap-1"
|
className="grid w-full grid-flow-col gap-1"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
aria-labelledby="discord-button-label"
|
aria-labelledby="discord-button-label"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/
|
|||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||||
import { getHasuraAdminSecret } from '@/utils/env';
|
import { getHasuraAdminSecret } from '@/utils/env';
|
||||||
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
|
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
export default function useAdminApolloClient() {
|
export default function useAdminApolloClient() {
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||||
@@ -12,18 +13,24 @@ export default function useAdminApolloClient() {
|
|||||||
'graphql',
|
'graphql',
|
||||||
);
|
);
|
||||||
|
|
||||||
const adminClient = new ApolloClient({
|
const projectAdminSecret = currentProject.config?.hasura?.adminSecret;
|
||||||
cache: new InMemoryCache(),
|
|
||||||
link: new HttpLink({
|
const adminClient = useMemo(
|
||||||
uri: serviceUrl,
|
() =>
|
||||||
headers: {
|
new ApolloClient({
|
||||||
'x-hasura-admin-secret':
|
cache: new InMemoryCache(),
|
||||||
process.env.NEXT_PUBLIC_ENV === 'dev'
|
link: new HttpLink({
|
||||||
? getHasuraAdminSecret()
|
uri: serviceUrl,
|
||||||
: currentProject?.config?.hasura.adminSecret,
|
headers: {
|
||||||
},
|
'x-hasura-admin-secret':
|
||||||
}),
|
process.env.NEXT_PUBLIC_ENV === 'dev'
|
||||||
});
|
? getHasuraAdminSecret()
|
||||||
|
: projectAdminSecret,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
[serviceUrl, projectAdminSecret],
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
adminClient,
|
adminClient,
|
||||||
|
|||||||
@@ -114,7 +114,6 @@ export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndP
|
|||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
desiredState: ApplicationStatus.Live,
|
desiredState: ApplicationStatus.Live,
|
||||||
featureFlags: [],
|
featureFlags: [],
|
||||||
providersUpdated: true,
|
|
||||||
repositoryProductionBranch: null,
|
repositoryProductionBranch: null,
|
||||||
nhostBaseFolder: null,
|
nhostBaseFolder: null,
|
||||||
plan: null,
|
plan: null,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export default function useIsCurrentUserOwner() {
|
|||||||
const { currentWorkspace, loading } = useCurrentWorkspaceAndProject();
|
const { currentWorkspace, loading } = useCurrentWorkspaceAndProject();
|
||||||
const currentUser = useUserData();
|
const currentUser = useUserData();
|
||||||
|
|
||||||
if (loading || !currentWorkspace.workspaceMembers || !currentUser) {
|
if (loading || !currentWorkspace?.workspaceMembers || !currentUser) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as useIsGraphiteEnabled } from './useIsGraphiteEnabled';
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
|
||||||
|
import { useGetGraphiteSessionsQuery } from '@/utils/__generated__/graphite.graphql';
|
||||||
|
|
||||||
|
export default function useIsGraphiteEnabled() {
|
||||||
|
const { adminClient } = useAdminApolloClient();
|
||||||
|
|
||||||
|
const { error } = useGetGraphiteSessionsQuery({
|
||||||
|
client: adminClient,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
isGraphiteEnabled: !error,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
const planDescriptions = {
|
const planDescriptions = {
|
||||||
Starter: '1 GB database, 5 GB of file storage, 10 GB of network traffic.',
|
Starter: '1 GB database, 5 GB of file storage, 10 GB of network traffic.',
|
||||||
Pro: '10 GB database, 25 GB of file storage, 50 GB of network traffic, and backups.',
|
Pro: '10 GB database, 25 GB of file storage, 50 GB of network traffic, and backups.',
|
||||||
|
Team: 'Reach out to us at support@nhost.io to have your private channel set up.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default planDescriptions;
|
export default planDescriptions;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export default function AuthDomain() {
|
|||||||
|
|
||||||
const [updateConfig] = useUpdateConfigMutation();
|
const [updateConfig] = useUpdateConfigMutation();
|
||||||
|
|
||||||
const form = useForm<{ auth_fqdn: string }>({
|
const form = useForm<Yup.InferType<typeof validationSchema>>({
|
||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
defaultValues: { auth_fqdn: null },
|
defaultValues: { auth_fqdn: null },
|
||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export default function HasuraDomain() {
|
|||||||
|
|
||||||
const [updateConfig] = useUpdateConfigMutation();
|
const [updateConfig] = useUpdateConfigMutation();
|
||||||
|
|
||||||
const form = useForm<{ hasura_fqdn: string }>({
|
const form = useForm<Yup.InferType<typeof validationSchema>>({
|
||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
defaultValues: { hasura_fqdn: null },
|
defaultValues: { hasura_fqdn: null },
|
||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export default function RunServicePortDomain({
|
|||||||
const runServicePort = service.config.ports.find((p) => p.port === port);
|
const runServicePort = service.config.ports.find((p) => p.port === port);
|
||||||
const initialValue = runServicePort?.ingresses?.[0]?.fqdn?.[0];
|
const initialValue = runServicePort?.ingresses?.[0]?.fqdn?.[0];
|
||||||
|
|
||||||
const form = useForm<{ runServicePortFQDN: string }>({
|
const form = useForm<RunServicePortFormValues>({
|
||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
runServicePortFQDN: initialValue,
|
runServicePortFQDN: initialValue,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default function ServerlessFunctionsDomain() {
|
|||||||
|
|
||||||
const [updateConfig] = useUpdateConfigMutation();
|
const [updateConfig] = useUpdateConfigMutation();
|
||||||
|
|
||||||
const form = useForm<{ functions_fqdn: string }>({
|
const form = useForm<ServerlessFunctionsDomainFormValues>({
|
||||||
reValidateMode: 'onSubmit',
|
reValidateMode: 'onSubmit',
|
||||||
defaultValues: { functions_fqdn: null },
|
defaultValues: { functions_fqdn: null },
|
||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ export default function OverviewTopBar() {
|
|||||||
const isPlatform = useIsPlatform();
|
const isPlatform = useIsPlatform();
|
||||||
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
|
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const isOwner = useIsCurrentUserOwner();
|
const isOwner = useIsCurrentUserOwner();
|
||||||
const isPro = !currentProject?.plan?.isFree;
|
const isStarter = currentProject?.plan?.name === 'Starter';
|
||||||
|
const isPro = currentProject?.plan?.name === 'Pro';
|
||||||
const { openDialog } = useDialog();
|
const { openDialog } = useDialog();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
|
|
||||||
@@ -65,7 +66,6 @@ export default function OverviewTopBar() {
|
|||||||
>
|
>
|
||||||
{currentProject.name}
|
{currentProject.name}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{currentProject.creator && (
|
{currentProject.creator && (
|
||||||
<Text
|
<Text
|
||||||
color="secondary"
|
color="secondary"
|
||||||
@@ -81,15 +81,14 @@ export default function OverviewTopBar() {
|
|||||||
ago
|
ago
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mt-1 inline-grid grid-flow-col items-center justify-start gap-2 md:mt-0">
|
<div className="mt-1 inline-grid grid-flow-col items-center justify-start gap-2 md:mt-0">
|
||||||
<Chip
|
<Chip
|
||||||
size="small"
|
size="small"
|
||||||
label={isPro ? 'Pro' : 'Starter'}
|
label={currentProject.plan.name}
|
||||||
color={isPro ? 'primary' : 'default'}
|
color={!isStarter ? 'primary' : 'default'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!isPro && isOwner && (
|
{(isStarter || isPro) && isOwner && (
|
||||||
<Button
|
<Button
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export interface RoleSettingsFormValues {
|
|||||||
*/
|
*/
|
||||||
authUserDefaultRole: string;
|
authUserDefaultRole: string;
|
||||||
/**
|
/**
|
||||||
* Allowed roles for the project.
|
* Default Allowed roles for the project.
|
||||||
*/
|
*/
|
||||||
authUserDefaultAllowedRoles: Role[];
|
authUserDefaultAllowedRoles: Role[];
|
||||||
}
|
}
|
||||||
@@ -169,8 +169,8 @@ export default function RoleSettings() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsContainer
|
<SettingsContainer
|
||||||
title="Allowed Roles"
|
title="Default Allowed Roles"
|
||||||
description="Allowed roles are roles users get automatically when they sign up."
|
description="Default Allowed Roles are roles users get automatically when they sign up."
|
||||||
docsLink="https://docs.nhost.io/authentication/users#allowed-roles"
|
docsLink="https://docs.nhost.io/authentication/users#allowed-roles"
|
||||||
rootClassName="gap-0"
|
rootClassName="gap-0"
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ function AllWorkspaceApps() {
|
|||||||
|
|
||||||
if (currentWorkspace?.projects?.length === 0) {
|
if (currentWorkspace?.projects?.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Box className="flex flex-row border-y py-4">
|
<Box className="flex flex-row py-4 border-y">
|
||||||
<Text className="text-xs" color="secondary">
|
<Text className="text-xs" color="secondary">
|
||||||
No projects on this workspace.
|
No projects on this workspace.
|
||||||
</Text>
|
</Text>
|
||||||
@@ -45,11 +45,12 @@ function AllWorkspaceApps() {
|
|||||||
<NavLink
|
<NavLink
|
||||||
href={`${currentWorkspace?.slug}/${project.slug}`}
|
href={`${currentWorkspace?.slug}/${project.slug}`}
|
||||||
passHref
|
passHref
|
||||||
|
className='w-full'
|
||||||
>
|
>
|
||||||
<ListItem.Button className="grid grid-flow-col items-center justify-between gap-2">
|
<ListItem.Button className="grid items-center justify-between grid-flow-col gap-2">
|
||||||
<div className="grid grid-flow-col items-center justify-start gap-2">
|
<div className="grid items-center justify-start grid-flow-col gap-2">
|
||||||
<ListItem.Avatar>
|
<ListItem.Avatar>
|
||||||
<div className="h-8 w-8 overflow-hidden rounded-lg">
|
<div className="w-8 h-8 overflow-hidden rounded-lg">
|
||||||
<Image
|
<Image
|
||||||
src="/logos/new.svg"
|
src="/logos/new.svg"
|
||||||
alt="Nhost Logo"
|
alt="Nhost Logo"
|
||||||
@@ -80,7 +81,7 @@ function AllWorkspaceApps() {
|
|||||||
|
|
||||||
<Chip
|
<Chip
|
||||||
size="small"
|
size="small"
|
||||||
label={project.plan.isFree ? 'Starter' : 'Pro'}
|
label={project.plan.name}
|
||||||
color={project.plan.isFree ? 'default' : 'primary'}
|
color={project.plan.isFree ? 'default' : 'primary'}
|
||||||
/>
|
/>
|
||||||
</ListItem.Button>
|
</ListItem.Button>
|
||||||
@@ -99,8 +100,8 @@ export default function WorkspaceApps() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-9">
|
<div className="mt-9">
|
||||||
<div className="mx-auto max-w-3xl font-display">
|
<div className="max-w-3xl mx-auto font-display">
|
||||||
<div className="mb-4 grid grid-flow-col items-center justify-between gap-2">
|
<div className="grid items-center justify-between grid-flow-col gap-2 mb-4">
|
||||||
<Text className="text-lg font-medium">Projects</Text>
|
<Text className="text-lg font-medium">Projects</Text>
|
||||||
|
|
||||||
{!loading && (
|
{!loading && (
|
||||||
|
|||||||
@@ -13,20 +13,18 @@ import { Tooltip } from '@/components/ui/v2/Tooltip';
|
|||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { useHostName } from '@/features/projects/common/hooks/useHostName';
|
import { useHostName } from '@/features/projects/common/hooks/useHostName';
|
||||||
import { InfoCard } from '@/features/projects/overview/components/InfoCard';
|
import { InfoCard } from '@/features/projects/overview/components/InfoCard';
|
||||||
import {
|
import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||||
COST_PER_VCPU,
|
|
||||||
MAX_SERVICES_CPU,
|
|
||||||
MAX_SERVICES_MEM,
|
|
||||||
MAX_SERVICE_REPLICAS,
|
|
||||||
MIN_SERVICES_CPU,
|
|
||||||
MIN_SERVICES_MEM,
|
|
||||||
} from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
|
||||||
import { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection';
|
import { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection';
|
||||||
import { EnvironmentFormSection } from '@/features/services/components/ServiceForm/components/EnvironmentFormSection';
|
import { EnvironmentFormSection } from '@/features/services/components/ServiceForm/components/EnvironmentFormSection';
|
||||||
import { PortsFormSection } from '@/features/services/components/ServiceForm/components/PortsFormSection';
|
import { PortsFormSection } from '@/features/services/components/ServiceForm/components/PortsFormSection';
|
||||||
import { ReplicasFormSection } from '@/features/services/components/ServiceForm/components/ReplicasFormSection';
|
import { ReplicasFormSection } from '@/features/services/components/ServiceForm/components/ReplicasFormSection';
|
||||||
import { StorageFormSection } from '@/features/services/components/ServiceForm/components/StorageFormSection';
|
import { StorageFormSection } from '@/features/services/components/ServiceForm/components/StorageFormSection';
|
||||||
import type { DialogFormProps } from '@/types/common';
|
|
||||||
|
import {
|
||||||
|
validationSchema,
|
||||||
|
type ServiceFormProps,
|
||||||
|
type ServiceFormValues,
|
||||||
|
} from '@/features/services/components/ServiceForm/ServiceFormTypes';
|
||||||
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
@@ -42,72 +40,9 @@ import { useEffect, useState } from 'react';
|
|||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import { parse } from 'shell-quote';
|
import { parse } from 'shell-quote';
|
||||||
import * as Yup from 'yup';
|
|
||||||
import { ServiceConfirmationDialog } from './components/ServiceConfirmationDialog';
|
import { ServiceConfirmationDialog } from './components/ServiceConfirmationDialog';
|
||||||
import { ServiceDetailsDialog } from './components/ServiceDetailsDialog';
|
import { ServiceDetailsDialog } from './components/ServiceDetailsDialog';
|
||||||
|
|
||||||
export enum PortTypes {
|
|
||||||
HTTP = 'http',
|
|
||||||
TCP = 'tcp',
|
|
||||||
UDP = 'udp',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const validationSchema = Yup.object({
|
|
||||||
name: Yup.string().required('The name is required.'),
|
|
||||||
image: Yup.string().label('Image to run'),
|
|
||||||
command: Yup.string(),
|
|
||||||
environment: Yup.array().of(
|
|
||||||
Yup.object().shape({
|
|
||||||
name: Yup.string().required(),
|
|
||||||
value: Yup.string().required(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
compute: Yup.object({
|
|
||||||
cpu: Yup.number().min(MIN_SERVICES_CPU).max(MAX_SERVICES_CPU).required(),
|
|
||||||
memory: Yup.number().min(MIN_SERVICES_MEM).max(MAX_SERVICES_MEM).required(),
|
|
||||||
}),
|
|
||||||
replicas: Yup.number().min(0).max(MAX_SERVICE_REPLICAS).required(),
|
|
||||||
ports: Yup.array().of(
|
|
||||||
Yup.object().shape({
|
|
||||||
port: Yup.number().required(),
|
|
||||||
type: Yup.mixed<PortTypes>().oneOf(Object.values(PortTypes)).required(),
|
|
||||||
publish: Yup.boolean().default(false),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
storage: Yup.array().of(
|
|
||||||
Yup.object()
|
|
||||||
.shape({
|
|
||||||
name: Yup.string().required(),
|
|
||||||
path: Yup.string().required(),
|
|
||||||
capacity: Yup.number().nonNullable().required(),
|
|
||||||
})
|
|
||||||
.required(),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type ServiceFormValues = Yup.InferType<typeof validationSchema>;
|
|
||||||
|
|
||||||
export interface ServiceFormProps extends DialogFormProps {
|
|
||||||
/**
|
|
||||||
* To use in conjunction with initialData to allow for updating the service
|
|
||||||
*/
|
|
||||||
serviceID?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* if there is initialData then it's an update operation
|
|
||||||
*/
|
|
||||||
initialData?: ServiceFormValues & { subdomain?: string }; // subdomain is only set on the backend
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to be called when the operation is cancelled.
|
|
||||||
*/
|
|
||||||
onCancel?: VoidFunction;
|
|
||||||
/**
|
|
||||||
* Function to be called when the submit is successful.
|
|
||||||
*/
|
|
||||||
onSubmit?: VoidFunction | ((args?: any) => Promise<any>);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ServiceForm({
|
export default function ServiceForm({
|
||||||
serviceID,
|
serviceID,
|
||||||
initialData,
|
initialData,
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { PortTypes } from '@/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
|
||||||
|
import type { DialogFormProps } from '@/types/common';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
|
import {
|
||||||
|
MAX_SERVICES_CPU,
|
||||||
|
MAX_SERVICES_MEM,
|
||||||
|
MAX_SERVICE_REPLICAS,
|
||||||
|
MIN_SERVICES_CPU,
|
||||||
|
MIN_SERVICES_MEM,
|
||||||
|
} from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||||
|
|
||||||
|
export const validationSchema = Yup.object({
|
||||||
|
name: Yup.string().required('The name is required.'),
|
||||||
|
image: Yup.string().label('Image to run'),
|
||||||
|
command: Yup.string(),
|
||||||
|
environment: Yup.array().of(
|
||||||
|
Yup.object().shape({
|
||||||
|
name: Yup.string().required(),
|
||||||
|
value: Yup.string().required(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
compute: Yup.object({
|
||||||
|
cpu: Yup.number().min(MIN_SERVICES_CPU).max(MAX_SERVICES_CPU).required(),
|
||||||
|
memory: Yup.number().min(MIN_SERVICES_MEM).max(MAX_SERVICES_MEM).required(),
|
||||||
|
}),
|
||||||
|
replicas: Yup.number().min(0).max(MAX_SERVICE_REPLICAS).required(),
|
||||||
|
ports: Yup.array().of(
|
||||||
|
Yup.object().shape({
|
||||||
|
port: Yup.number().required(),
|
||||||
|
type: Yup.mixed<PortTypes>().oneOf(Object.values(PortTypes)).required(),
|
||||||
|
publish: Yup.boolean().default(false),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
storage: Yup.array().of(
|
||||||
|
Yup.object()
|
||||||
|
.shape({
|
||||||
|
name: Yup.string().required(),
|
||||||
|
path: Yup.string().required(),
|
||||||
|
capacity: Yup.number().nonNullable().required(),
|
||||||
|
})
|
||||||
|
.required(),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ServiceFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
|
export interface ServiceFormProps extends DialogFormProps {
|
||||||
|
/**
|
||||||
|
* To use in conjunction with initialData to allow for updating the service
|
||||||
|
*/
|
||||||
|
serviceID?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if there is initialData then it's an update operation
|
||||||
|
*/
|
||||||
|
initialData?: ServiceFormValues & { subdomain?: string }; // subdomain is only set on the backend
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to be called when the operation is cancelled.
|
||||||
|
*/
|
||||||
|
onCancel?: VoidFunction;
|
||||||
|
/**
|
||||||
|
* Function to be called when the submit is successful.
|
||||||
|
*/
|
||||||
|
onSubmit?: VoidFunction | ((args?: any) => Promise<any>);
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
MEM_CPU_RATIO,
|
MEM_CPU_RATIO,
|
||||||
MIN_SERVICES_MEM,
|
MIN_SERVICES_MEM,
|
||||||
} from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
} from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||||
import type { ServiceFormValues } from '@/features/services/components/ServiceForm';
|
import type { ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
|
||||||
import { useFormContext, useWatch } from 'react-hook-form';
|
import { useFormContext, useWatch } from 'react-hook-form';
|
||||||
|
|
||||||
interface ComputeFormSectionProps {
|
interface ComputeFormSectionProps {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { TrashIcon } from '@/components/ui/v2/icons/TrashIcon';
|
|||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||||
import type { ServiceFormValues } from '@/features/services/components/ServiceForm';
|
import type { ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,8 @@ import { Text } from '@/components/ui/v2/Text';
|
|||||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { InfoCard } from '@/features/projects/overview/components/InfoCard';
|
import { InfoCard } from '@/features/projects/overview/components/InfoCard';
|
||||||
import {
|
import { PortTypes } from '@/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
|
||||||
PortTypes,
|
import { type ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
|
||||||
type ServiceFormValues,
|
|
||||||
} from '@/features/services/components/ServiceForm';
|
|
||||||
import { useFieldArray, useFormContext, useWatch } from 'react-hook-form';
|
import { useFieldArray, useFormContext, useWatch } from 'react-hook-form';
|
||||||
|
|
||||||
export default function PortsFormSection() {
|
export default function PortsFormSection() {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export enum PortTypes {
|
||||||
|
HTTP = 'http',
|
||||||
|
TCP = 'tcp',
|
||||||
|
UDP = 'udp',
|
||||||
|
}
|
||||||
@@ -1,2 +1 @@
|
|||||||
/* eslint-disable import/no-cycle */
|
|
||||||
export { default as PortsFormSection } from './PortsFormSection';
|
export { default as PortsFormSection } from './PortsFormSection';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Slider } from '@/components/ui/v2/Slider';
|
|||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||||
import { MAX_SERVICE_REPLICAS } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
import { MAX_SERVICE_REPLICAS } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||||
import type { ServiceFormValues } from '@/features/services/components/ServiceForm';
|
import type { ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
|
||||||
import { useFormContext, useWatch } from 'react-hook-form';
|
import { useFormContext, useWatch } from 'react-hook-form';
|
||||||
|
|
||||||
export default function ReplicasFormSection() {
|
export default function ReplicasFormSection() {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
|
|||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||||
import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||||
import type { ServiceFormValues } from '@/features/services/components/ServiceForm';
|
import type { ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
|
||||||
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
||||||
|
|
||||||
export interface ServiceConfirmationDialogProps {
|
export interface ServiceConfirmationDialogProps {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
MAX_STORAGE_CAPACITY,
|
MAX_STORAGE_CAPACITY,
|
||||||
MIN_STORAGE_CAPACITY,
|
MIN_STORAGE_CAPACITY,
|
||||||
} from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
} from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||||
import type { ServiceFormValues } from '@/features/services/components/ServiceForm';
|
import type { ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
|
||||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
export default function StorageFormSection() {
|
export default function StorageFormSection() {
|
||||||
@@ -53,7 +53,8 @@ export default function StorageFormSection() {
|
|||||||
<span>
|
<span>
|
||||||
By default, services do not have persistent storage. You can add
|
By default, services do not have persistent storage. You can add
|
||||||
SSD disks to the service here. It is important to note that
|
SSD disks to the service here. It is important to note that
|
||||||
capacity can not be decreased after creation, only expanded. Refer to{' '}
|
capacity can not be decreased after creation, only expanded.
|
||||||
|
Refer to{' '}
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
@@ -82,7 +83,7 @@ export default function StorageFormSection() {
|
|||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
<Box
|
<Box
|
||||||
key={field.id}
|
key={field.id}
|
||||||
className="flex w-full flex-col space-y-2 xs+:flex-row xs+:space-y-0 xs+:space-x-2"
|
className="flex w-full flex-col space-y-2 xs+:flex-row xs+:space-x-2 xs+:space-y-0"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
{...register(`storage.${index}.name`)}
|
{...register(`storage.${index}.name`)}
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
export * from './ServiceForm';
|
|
||||||
export { default as ServiceForm } from './ServiceForm';
|
export { default as ServiceForm } from './ServiceForm';
|
||||||
|
|||||||
@@ -11,10 +11,8 @@ import { UserIcon } from '@/components/ui/v2/icons/UserIcon';
|
|||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||||
import { DeleteServiceModal } from '@/features/projects/common/components/DeleteServiceModal';
|
import { DeleteServiceModal } from '@/features/projects/common/components/DeleteServiceModal';
|
||||||
import {
|
import { ServiceForm } from '@/features/services/components/ServiceForm';
|
||||||
ServiceForm,
|
import { type PortTypes } from '@/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
|
||||||
type PortTypes,
|
|
||||||
} from '@/features/services/components/ServiceForm';
|
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
import type { RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
|
import type { RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete
|
|||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { filterOptions } from '@/components/ui/v2/Autocomplete';
|
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import {
|
import {
|
||||||
GetStorageSettingsDocument,
|
GetStorageSettingsDocument,
|
||||||
@@ -136,12 +135,26 @@ export default function StorageServiceVersionSettings() {
|
|||||||
<ControlledAutocomplete
|
<ControlledAutocomplete
|
||||||
id="version"
|
id="version"
|
||||||
name="version"
|
name="version"
|
||||||
filterOptions={(options, state) => {
|
autoHighlight
|
||||||
if (state.inputValue === version) {
|
isOptionEqualToValue={() => false}
|
||||||
return options;
|
filterOptions={(options, { inputValue }) => {
|
||||||
}
|
const inputValueLower = inputValue.toLowerCase();
|
||||||
|
const matched = [];
|
||||||
|
const otherOptions = [];
|
||||||
|
|
||||||
return filterOptions(options, state);
|
options.forEach((option) => {
|
||||||
|
const optionLabelLower = option.label.toLowerCase();
|
||||||
|
|
||||||
|
if (optionLabelLower.startsWith(inputValueLower)) {
|
||||||
|
matched.push(option);
|
||||||
|
} else {
|
||||||
|
otherOptions.push(option);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = [...matched, ...otherOptions];
|
||||||
|
|
||||||
|
return result;
|
||||||
}}
|
}}
|
||||||
fullWidth
|
fullWidth
|
||||||
className="lg:col-span-2"
|
className="lg:col-span-2"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ query PrefetchNewApp {
|
|||||||
regions(order_by: { city: asc }) {
|
regions(order_by: { city: asc }) {
|
||||||
...PrefetchNewAppRegions
|
...PrefetchNewAppRegions
|
||||||
}
|
}
|
||||||
plans(order_by: { sort: asc }) {
|
plans(order_by: {sort: asc}, where: {deprecated: {_eq: false}}) {
|
||||||
...PrefetchNewAppPlans
|
...PrefetchNewAppPlans
|
||||||
}
|
}
|
||||||
workspaces {
|
workspaces {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ fragment Project on apps {
|
|||||||
createdAt
|
createdAt
|
||||||
desiredState
|
desiredState
|
||||||
nhostBaseFolder
|
nhostBaseFolder
|
||||||
providersUpdated
|
|
||||||
config(resolve: true) {
|
config(resolve: true) {
|
||||||
observability {
|
observability {
|
||||||
grafana {
|
grafana {
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
query getGraphiteSessions {
|
||||||
|
graphite {
|
||||||
|
sessions {
|
||||||
|
sessionID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
mutation confirmProvidersUpdated($id: uuid!) {
|
|
||||||
updateApp(pk_columns: { id: $id }, _set: { providersUpdated: true }) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { default as useHypertune } from './useHypertune';
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import hypertune from '@/hypertune/hypertune';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
export default function useHypertune() {
|
|
||||||
const [, setIsInitialized] = useState<boolean>(hypertune.isInitialized());
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
hypertune.waitForInitialization().then(() => {
|
|
||||||
setIsInitialized(true);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return hypertune;
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { initializeHypertune } from './project_2596';
|
|
||||||
|
|
||||||
const hypertune = initializeHypertune({});
|
|
||||||
|
|
||||||
export default hypertune;
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
import * as sdk from "hypertune";
|
|
||||||
|
|
||||||
const projectId = 2596;
|
|
||||||
|
|
||||||
const businessToken = `U2FsdGVkX19+V8BJnVR0xLEC+42OW5qZl/A0i6beAaRmJoIhFh5Yf6eIKBzLbV9h`;
|
|
||||||
|
|
||||||
const queryCode = `query InitQuery {
|
|
||||||
root {
|
|
||||||
enableServices
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const query = {"Query":{"objectTypeName":"Query","selection":{"root":{"fieldArguments":{"__isPartialObject__":true},"fieldQuery":{"Root":{"objectTypeName":"Root","selection":{"enableServices":{"fieldArguments":{},"fieldQuery":null}}}}}}}};
|
|
||||||
|
|
||||||
const fallbackInitData: sdk.FallbackInitData & { [key: string]: unknown } = {"commitId":3297,"reducedExpression":{"id":"caxyeQqTKX3UGOXClvbnW","logs":{"events":{},"exposures":{},"evaluations":{}},"type":"ObjectExpression","fields":{"root":{"id":"PoMWxsy7KbW9fCq5XXvx4","body":{"id":"IUICRjZ7iSnh9k0cWBmnd","logs":{"events":{},"exposures":{},"evaluations":{}},"type":"ObjectExpression","fields":{"enableServices":{"id":"7WZWy2AIy_q9Vbz4cn9KB","logs":{"evaluations":{"XNOtHkUBpglrY1nkYa_bf":1},"events":{},"exposures":{}},"type":"BooleanExpression","value":true,"valueType":{"type":"BooleanValueType"}}},"valueType":{"type":"ObjectValueType","objectTypeName":"Root"},"objectTypeName":"Root"},"logs":{"events":{},"exposures":{},"evaluations":{}},"type":"FunctionExpression","valueType":{"type":"FunctionValueType","returnValueType":{"type":"ObjectValueType","objectTypeName":"Root"},"parameterValueTypes":[{"type":"ObjectValueType","objectTypeName":"Query_root_args"}]},"parameters":[{"id":"Ygjhl2LqjiwcousTABFQz","name":"rootArgs"}]}},"metadata":{"permissions":{"user":{},"group":{"team":{"write":"allow"}}}},"valueType":{"type":"ObjectValueType","objectTypeName":"Query"},"objectTypeName":"Query"},"splits":{},"eventTypes":{},"commitConfig":{"splitConfig":{}},"initLogId":0,"commitHash":"4178461588049503","sdkConfig":{"hashPollInterval":1000,"flushLogsInterval":1000,"maxLogsPerFlush":1},"query":{"Query":{"objectTypeName":"Query","selection":{"root":{"fieldArguments":{"__isPartialObject__":true},"fieldQuery":{"Root":{"objectTypeName":"Root","selection":{"enableServices":{"fieldArguments":{},"fieldQuery":null}}}}}}}}};
|
|
||||||
|
|
||||||
export function initializeHypertune(
|
|
||||||
variableValues: Rec,
|
|
||||||
options: sdk.InitializeOptions = {}
|
|
||||||
): QueryNode {
|
|
||||||
const defaultOptions = { businessToken, query, fallbackInitData };
|
|
||||||
|
|
||||||
return sdk.initialize(
|
|
||||||
QueryNode,
|
|
||||||
projectId,
|
|
||||||
queryCode,
|
|
||||||
variableValues,
|
|
||||||
{ ...defaultOptions, ...options }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enum types
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Input object types
|
|
||||||
|
|
||||||
export type Rec = {
|
|
||||||
|
|
||||||
//
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Rec2 = {
|
|
||||||
context: Rec3;
|
|
||||||
//
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Rec3 = {
|
|
||||||
workSpace: Rec4;
|
|
||||||
//
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Rec4 = {
|
|
||||||
id: string;
|
|
||||||
//
|
|
||||||
};
|
|
||||||
|
|
||||||
// Enum node classes
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Fragment node classes
|
|
||||||
|
|
||||||
export class QueryNode extends sdk.Node {
|
|
||||||
typeName = "Query" as const;
|
|
||||||
|
|
||||||
root(args: Rec2): RootNode {
|
|
||||||
const props0 = this.getField("root", args);
|
|
||||||
const expression0 = props0.expression;
|
|
||||||
|
|
||||||
if (
|
|
||||||
expression0 &&
|
|
||||||
expression0.type === "ObjectExpression"
|
|
||||||
&& expression0.objectTypeName === "Root"
|
|
||||||
) {
|
|
||||||
return new RootNode(props0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const node = new RootNode(props0);
|
|
||||||
node._logUnexpectedTypeError();
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class RootNode extends sdk.Node {
|
|
||||||
typeName = "Root" as const;
|
|
||||||
|
|
||||||
enableServices(args: Rec): sdk.BooleanNode {
|
|
||||||
const props0 = this.getField("enableServices", args);
|
|
||||||
const expression0 = props0.expression;
|
|
||||||
|
|
||||||
if (
|
|
||||||
expression0 &&
|
|
||||||
expression0.type === "BooleanExpression"
|
|
||||||
|
|
||||||
) {
|
|
||||||
return new sdk.BooleanNode(props0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const node = new sdk.BooleanNode(props0);
|
|
||||||
node._logUnexpectedTypeError();
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,14 +9,14 @@ import { Link } from '@/components/ui/v2/Link';
|
|||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { AssistantForm } from '@/features/ai/AssistantForm';
|
import { AssistantForm } from '@/features/ai/AssistantForm';
|
||||||
import { AssistantsList } from '@/features/ai/AssistantsList';
|
import { AssistantsList } from '@/features/ai/AssistantsList';
|
||||||
|
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { useIsGraphiteEnabled } from '@/features/projects/common/hooks/useIsGraphiteEnabled';
|
||||||
import { getHasuraAdminSecret } from '@/utils/env';
|
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||||
import {
|
import {
|
||||||
useGetAssistantsQuery,
|
useGetAssistantsQuery,
|
||||||
type GetAssistantsQuery,
|
type GetAssistantsQuery,
|
||||||
} from '@/utils/__generated__/graphite.graphql';
|
} from '@/utils/__generated__/graphite.graphql';
|
||||||
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
|
|
||||||
import { useMemo, type ReactElement } from 'react';
|
import { useMemo, type ReactElement } from 'react';
|
||||||
|
|
||||||
export type Assistant = Omit<
|
export type Assistant = Omit<
|
||||||
@@ -26,34 +26,15 @@ export type Assistant = Omit<
|
|||||||
|
|
||||||
export default function AssistantsPage() {
|
export default function AssistantsPage() {
|
||||||
const { openDrawer } = useDialog();
|
const { openDrawer } = useDialog();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
|
|
||||||
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
|
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
|
||||||
const adminSecret = currentProject?.config?.hasura?.adminSecret;
|
const { adminClient } = useAdminApolloClient();
|
||||||
|
const { isGraphiteEnabled } = useIsGraphiteEnabled();
|
||||||
|
|
||||||
const serviceUrl = generateAppServiceUrl(
|
const { data, loading, refetch } = useGetAssistantsQuery({
|
||||||
currentProject?.subdomain,
|
client: adminClient,
|
||||||
currentProject?.region,
|
});
|
||||||
'graphql',
|
|
||||||
);
|
|
||||||
|
|
||||||
const client = useMemo(
|
|
||||||
() =>
|
|
||||||
new ApolloClient({
|
|
||||||
cache: new InMemoryCache(),
|
|
||||||
link: new HttpLink({
|
|
||||||
uri: serviceUrl,
|
|
||||||
headers: {
|
|
||||||
'x-hasura-admin-secret':
|
|
||||||
process.env.NEXT_PUBLIC_ENV === 'dev'
|
|
||||||
? getHasuraAdminSecret()
|
|
||||||
: adminSecret,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
[serviceUrl, adminSecret],
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data, loading, refetch } = useGetAssistantsQuery({ client });
|
|
||||||
|
|
||||||
const assistants = useMemo(() => data?.graphite?.assistants || [], [data]);
|
const assistants = useMemo(() => data?.graphite?.assistants || [], [data]);
|
||||||
|
|
||||||
@@ -64,7 +45,7 @@ export default function AssistantsPage() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (currentProject.plan.isFree) {
|
if (isPlatform && currentProject?.plan?.isFree) {
|
||||||
return (
|
return (
|
||||||
<Box className="p-4" sx={{ backgroundColor: 'background.default' }}>
|
<Box className="p-4" sx={{ backgroundColor: 'background.default' }}>
|
||||||
<UpgradeToProBanner
|
<UpgradeToProBanner
|
||||||
@@ -80,7 +61,12 @@ export default function AssistantsPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentProject.plan.isFree && !currentProject.config?.ai) {
|
if (
|
||||||
|
(isPlatform &&
|
||||||
|
!currentProject?.plan?.isFree &&
|
||||||
|
!currentProject.config?.ai) ||
|
||||||
|
!isGraphiteEnabled
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<Box className="p-4" sx={{ backgroundColor: 'background.default' }}>
|
<Box className="p-4" sx={{ backgroundColor: 'background.default' }}>
|
||||||
<Alert className="grid w-full grid-flow-col place-content-between items-center gap-2">
|
<Alert className="grid w-full grid-flow-col place-content-between items-center gap-2">
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user