Compare commits

..

485 Commits

Author SHA1 Message Date
Hassan Ben Jobrane
1c59c363ee Merge pull request #2328 from nhost/changeset-release/main
chore: update versions
2023-10-24 11:20:22 +01:00
github-actions[bot]
1d99f26fec chore: update versions 2023-10-24 09:59:28 +00:00
Hassan Ben Jobrane
49edb0e627 Merge pull request #2332 from ttiras/patch-1
Update Docs for useChangeEmail.ts Example
2023-10-24 10:56:55 +01:00
Hassan Ben Jobrane
f011e71ae1 chore: fix typo 2023-10-23 19:49:05 +01:00
Hassan Ben Jobrane
00c363f808 chore: add changeset 2023-10-23 18:10:04 +01:00
Hassan Ben Jobrane
0b2f749ae9 fix: docs: vuejs: update changeEmail docs reference 2023-10-23 18:05:24 +01:00
Hassan Ben Jobrane
cf62a1e6e3 Merge pull request #2331 from nhost/fix/custom-domains/reset-domain
fix(dashboard): allow resetting custom domains
2023-10-20 17:58:06 +01:00
Hassan Ben Jobrane
8df84d782f chore: add changeset 2023-10-20 16:01:47 +01:00
Hassan Ben Jobrane
f0deffafe1 fix(dashboard): allow resetting custom domains 2023-10-20 15:59:54 +01:00
Hassan Ben Jobrane
a291da661d Merge pull request #2321 from MainaMary/bug/update-use-change-password-interface
fix: update useChangePassword hook interface
2023-10-20 11:42:41 +01:00
Mary
66c3193bc9 chore: add changeset 2023-10-20 13:03:00 +03:00
Hassan Ben Jobrane
ac7be49cef Merge pull request #2327 from nhost/chore/run/tweaks
chore(dashboard): fixes and tweaks to services form and dialog
2023-10-19 11:53:32 +01:00
Hassan Ben Jobrane
fa79b77093 chore: add changeset 2023-10-19 11:22:18 +01:00
Hassan Ben Jobrane
5823947933 chore: add missing key to service details dialog 2023-10-19 11:21:11 +01:00
Hassan Ben Jobrane
333837fb57 chore: fix update button icon on service form 2023-10-19 11:10:01 +01:00
Hassan Ben Jobrane
7fae68f6cf Merge pull request #2324 from nhost/changeset-release/main
chore: update versions
2023-10-18 17:09:56 +01:00
github-actions[bot]
f2751f4bac chore: update versions 2023-10-18 16:00:42 +00:00
Hassan Ben Jobrane
089acbbe70 Merge pull request #2320 from nhost/feat/custom-domains
feat(dashboard): custom domains
2023-10-18 16:58:03 +01:00
Nuno Pato
6e08a82f49 Merge pull request #2325 from nhost/docs/custom-domains
chore: docs: add custom domains
2023-10-18 15:49:47 +00:00
Hassan Ben Jobrane
6899ef3b39 chore: run pnpm codegen 2023-10-18 16:20:17 +01:00
Hassan Ben Jobrane
cad3686364 fix: tweak text when dark mode is on 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
8f2c002715 fix: fix custom domains page on small screens 2023-10-18 16:18:45 +01:00
Nuno Pato
b70d61198f fix link to docs 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
d29af2ce6f fix: make sure settings container title supports both a ReactNode and a string 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
cdc992b888 chore: update custom domains header message
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
205a20de87 fix: use correct database host 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
b092b8fe08 chore: tweak database domain description and docs link 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
2d40cbf624 fix: tweak verification box 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
7b591e8c4c fix dns verification values 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
72b425a5bc fix: remove duplicated nhost.run suffix 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
971ff92ab4 chore: remove comment 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
b7f801874d chore: add changeset 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
ff69f30e47 chore: move docs link to the top section 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
cc1932492d fix: only show services that have ports 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
f45037e79f fix: always show CNAME verification panel 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
48658e2925 feat: added run services port domains 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
b90bb6b924 feat: add database domain form 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
de61f45bd5 fix: auth and hasura domain forms 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
fd11e5ca2c feat: add Hasura Domain 2023-10-18 16:18:44 +01:00
Hassan Ben Jobrane
7839c786ef chore: run pnpm codegen 2023-10-18 16:18:44 +01:00
Hassan Ben Jobrane
a2bcd6a4b6 feat: add auth domain form 2023-10-18 16:18:44 +01:00
Hassan Ben Jobrane
2cd5b26e0e chore: run pnpm codegen 2023-10-18 16:18:44 +01:00
Hassan Ben Jobrane
559611af70 feat: add upgrade banner to access custom domains 2023-10-18 16:16:37 +01:00
Hassan Ben Jobrane
ffb45f5a49 Merge pull request #2326 from nhost/feat/database/storage-capacity
feat(dashboard): add database storage capacity setting
2023-10-18 15:53:18 +01:00
Hassan Ben Jobrane
451e80ac12 chore: add a warning message that db storage can't be downgraded 2023-10-18 15:19:18 +01:00
Hassan Ben Jobrane
c9f8e523f2 chore: fix upgrade message text 2023-10-18 13:43:37 +01:00
Hassan Ben Jobrane
331ba03768 chore: add changeset 2023-10-18 13:29:42 +01:00
Hassan Ben Jobrane
611b26bc7d chore: fix mocks 2023-10-18 13:29:33 +01:00
Nuno Pato
a446c3efca use custom-domain.com 2023-10-18 12:24:11 +00:00
Hassan Ben Jobrane
24424ae4dc feat: add postgres storage capacity setting 2023-10-18 13:19:43 +01:00
Hassan Ben Jobrane
2a5b705c26 chore: run pnpm codegen & remove deprecated insertFeedback 2023-10-18 13:18:56 +01:00
Nuno Pato
7f3a32d386 use tabs 2023-10-18 11:51:14 +00:00
Nuno Pato
11fa442aa8 Merge branch 'main' into docs/custom-domains 2023-10-18 11:45:19 +00:00
Nuno Pato
5764f46d99 add changeset 2023-10-18 11:36:26 +00:00
Nuno Pato
78d501801b docs: custom domains 2023-10-18 11:34:38 +00:00
David Barroso
cc8cc8d45d chore(docs): database: added extension http (#2323) 2023-10-18 11:42:50 +02:00
Mary
61fc83996b fix: update useChangePassword hook interface 2023-10-17 13:29:49 +03:00
ttiras
9ddb37e9bb Update useChangeEmail.ts
the wrong example has been modified from;
 
 await changeEmail({
    email: 'new@example.com'
  })

to;

 await changeEmail('new@example.com')
2023-10-14 13:53:07 +03:00
Hassan Ben Jobrane
262828f9a1 Merge pull request #2318 from nhost/changeset-release/main
chore: update versions
2023-10-12 16:36:06 +01:00
github-actions[bot]
12f9726ad7 chore: update versions 2023-10-12 13:40:33 +00:00
Hassan Ben Jobrane
845937b552 Merge pull request #2317 from nhost/fix/apollo-integration
fix: integrations: apollo: correct accessToken nullability test
2023-10-12 14:37:49 +01:00
Hassan Ben Jobrane
f777a3380a chore: add changeset 2023-10-12 10:26:00 +01:00
Hassan Ben Jobrane
5081372cab fix: integrations: apollo: correct accessToken nullability test 2023-10-12 10:23:09 +01:00
Hassan Ben Jobrane
82212345c8 Merge pull request #2311 from nhost/changeset-release/main
chore: update versions
2023-10-11 13:54:25 +01:00
Hassan Ben Jobrane
32d3f167c5 Merge pull request #2313 from nhost/fix/quickstarts
fix: quickstarts: toml node version and tsconfig moduleResolution
2023-10-11 12:37:38 +01:00
github-actions[bot]
3d5f1ea922 chore: update versions 2023-10-11 10:24:24 +00:00
Hassan Ben Jobrane
97841ee5e8 Merge pull request #2312 from nhost/fix/dashboard/run-tab
fix: dashboard: disable run tab when developing locally
2023-10-11 11:21:38 +01:00
Hassan Ben Jobrane
4f3a615ebe fix: quickstarts: fix backend functions node version 2023-10-11 10:14:09 +01:00
Hassan Ben Jobrane
8e8197691c fix: quickstarts: change module resolution to node 2023-10-11 10:13:40 +01:00
Hassan Ben Jobrane
e10389ecf6 chore: add changeset 2023-10-11 10:08:11 +01:00
Hassan Ben Jobrane
cbdf6affec fix(dashboard): disable run services tab in local dev mode 2023-10-11 10:07:02 +01:00
Hassan Ben Jobrane
d19406e694 Merge pull request #2309 from nhost/fix/integration/apollo
fix: integrations: apollo: set accessToken to null after TOKEN_CHANGED event on sign-out
2023-10-10 16:24:39 +01:00
Hassan Ben Jobrane
cffc5dc65b Merge pull request #2310 from nhost/chore/ci/stop-dashboard-releases
chore: ci: stop @nhost/dashboard github releases
2023-10-10 16:24:01 +01:00
Hassan Ben Jobrane
2b5cb58553 chore: ci: stop @nhost/dashboard github releases 2023-10-10 16:03:37 +01:00
Hassan Ben Jobrane
7459a9413e chore: add changeset 2023-10-10 15:43:28 +01:00
Hassan Ben Jobrane
56871cc9f7 fix: integrations: apollo: set accessToken to null after TOKEN_CHANGED event on signout 2023-10-10 13:38:04 +01:00
Hassan Ben Jobrane
8f4d66e52d Merge pull request #2308 from nhost/changeset-release/main
chore: update versions
2023-10-10 13:31:53 +01:00
github-actions[bot]
315a820073 chore: update versions 2023-10-10 12:04:28 +00:00
Hassan Ben Jobrane
ca57ad2cbd Merge pull request #2301 from nhost/feat/quickstarts/sveltekit
feat(quickstarts): sveltekit <--> nhost
2023-10-10 13:00:14 +01:00
Hassan Ben Jobrane
40259344eb Merge pull request #2306 from nhost/chore/providers-updated-notice
chore(dashboard): show oauth providers update notice
2023-10-10 12:45:12 +01:00
Hassan Ben Jobrane
4749f60a08 chore: add changeset 2023-10-09 20:12:04 +01:00
Hassan Ben Jobrane
ac1888514d chore: update readme for sveltekit quickstart 2023-10-09 19:52:44 +01:00
Hassan Ben Jobrane
49b4af439b feat: signup/signin via webauthn 2023-10-09 19:23:50 +01:00
Hassan Ben Jobrane
61e03d6c70 chore: make sure deprication notice is under a project 2023-10-09 16:48:01 +01:00
Hassan Ben Jobrane
bec0fce497 chore: add deprication banner 2023-10-09 15:00:58 +01:00
Hassan Ben Jobrane
c01568a7dd chore: add changeset 2023-10-09 14:28:40 +01:00
Hassan Ben Jobrane
e934216a82 chore: bring back providers update alert 2023-10-09 14:26:41 +01:00
Hassan Ben Jobrane
701d6b8c84 feat: sveltekit: add delete pat 2023-10-07 19:47:05 +01:00
Hassan Ben Jobrane
e158e2440a wip: sveltekit: add echo and pat pages 2023-10-07 19:22:29 +01:00
Hassan Ben Jobrane
fbaa657001 wip: sveltekit: refresh access token + create/delete todos 2023-10-05 17:08:55 +01:00
Hassan Ben Jobrane
559db6d0ec wip: sveltekit: auth + todos(create) 2023-10-05 12:48:59 +01:00
Hassan Ben Jobrane
4c844930f1 wip: update: sveltekit 2023-10-04 16:58:59 +01:00
Hassan Ben Jobrane
3ef503ff81 Merge pull request #2298 from nhost/changeset-release/main
chore: update versions
2023-10-04 16:47:51 +02:00
github-actions[bot]
bfcfd236ea chore: update versions 2023-10-04 14:13:38 +00:00
Hassan Ben Jobrane
bfa7033506 Merge pull request #2296 from nhost/feat/query-announcements
feat: dashboard: query announcements
2023-10-04 16:09:48 +02:00
Hassan Ben Jobrane
78c29fcf0e feat: filter expired announcements 2023-10-04 14:40:22 +01:00
Hassan Ben Jobrane
f1b934ed22 chore: remove old announcement provider 2023-10-04 10:52:20 +01:00
Hassan Ben Jobrane
914369c53f feat: add announcements list component 2023-10-03 20:18:41 +01:00
Hassan Ben Jobrane
af379b967e chore: clean up commented code 2023-10-03 17:43:24 +01:00
Hassan Ben Jobrane
c3efb7ec84 chore: add changeset 2023-10-03 17:41:35 +01:00
Hassan Ben Jobrane
27cbd48c8c feat(dashboard): query latest announcement from platform 2023-10-03 17:40:50 +01:00
Hassan Ben Jobrane
236996a903 Merge pull request #2293 from nhost/changeset-release/main
chore: update versions
2023-10-02 13:01:48 +02:00
github-actions[bot]
5d0936bb93 chore: update versions 2023-10-02 10:38:35 +00:00
Hassan Ben Jobrane
733c212f2d Merge pull request #2291 from nhost/chore/announcement/node18
chore: node18 announcement
2023-10-02 12:35:53 +02:00
Hassan Ben Jobrane
8b47549189 Merge pull request #2286 from nhost/chore/ci/disable-github-releases
chore(ci): set createGithubReleases to false
2023-10-02 12:26:10 +02:00
Hassan Ben Jobrane
3c9c1025ce Merge pull request #2287 from nhost/fix/vue-sdk/nested-unref
fix(vue-sdk): correctly unref arrays
2023-10-02 12:25:14 +02:00
Hassan Ben Jobrane
3e46d3873c chore: add changeset 2023-10-02 10:50:46 +01:00
Hassan Ben Jobrane
4cf8820d72 chore: open announcement link in a new tab 2023-10-02 10:39:15 +01:00
Hassan Ben Jobrane
02a11184fb chore: change announcement link 2023-10-02 10:38:04 +01:00
Hassan Ben Jobrane
7214d47cc7 chore: add changeset 2023-09-30 17:54:42 +01:00
Hassan Ben Jobrane
238b77baad fix(vue): correctly unref arrays 2023-09-30 17:53:05 +01:00
Hassan Ben Jobrane
81b8e538b4 chore(ci): set createGithubReleases to false 2023-09-29 17:12:21 +01:00
Hassan Ben Jobrane
563a37e58d Merge pull request #2285 from nhost/changeset-release/main
chore: update versions
2023-09-29 18:05:40 +02:00
github-actions[bot]
bff23720ee chore: update versions 2023-09-29 15:43:43 +00:00
Hassan Ben Jobrane
02cbaeffd2 Merge pull request #2225 from nhost/feat/examples/nextjs-server-components
feat: quickstarts: draft for using server components
2023-09-29 17:40:52 +02:00
Hassan Ben Jobrane
9eb814c79a chore: update readme 2023-09-29 15:57:53 +01:00
Hassan Ben Jobrane
ebc5913bb3 chore: naming consistency 2023-09-29 15:57:53 +01:00
Hassan Ben Jobrane
4fe4a16964 chore: add changeset 2023-09-29 15:57:53 +01:00
Hassan Ben Jobrane
92c475b7a7 chore: add missing refreshToken 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
679b34b031 chore: cleanup 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
d3186aefbd refactor: extract session middleware into helper function 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
fdecac9d69 refactor: add high order component for protected pages 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
5077283028 chore: merge oauth handling in middleware 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
f5f662aad1 chore: refactor server actions 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
735b779af7 chore: clean up database setup 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
4418d6abcf chore: cleanup 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
049e315c30 fix: set correct path on cookie on oauth signin 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
764597538b fix: make sure that hasura-storage-js works on EdgeRuntime 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
c8aea785cc fix: tweak todo item layout 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
e0e44b2ff4 fix: set same path for session cookie 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
12280f7c87 feat: pat list pagination 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
732a4f40ca wip 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
d67fd599e4 feat: todos CRUD 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
a41231927a feat: add signin with pat 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
42ec665950 fix: return refreshToken in getAuthenticationResult 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
7225712a30 chore: update hasura auth 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
6593fdd9bb fix: make sure refreshToken is returned after signin/signup 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
40039fece5 Revert "refactor: make sure to return refresh token"
This reverts commit b31b358ca1898bb4173954b8b33059d92cc8c126.
2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
e5fcfb3cd5 refactor: make sure to return refresh token 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
218ec314fb feat(quickstarts): refactor and organize signup/signin 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
9367e91d45 feat: examples: add other sign in methods
Add sign in with google and webauthn
2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
06c640be2c chore: delete unnecessary files 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
ae45be9816 feat(quickstarts): draft for using server components 2023-09-29 15:57:52 +01:00
Hassan Ben Jobrane
ec4be590d8 Merge pull request #2284 from nhost/chore/ci/fix-release
chore: ci: fix release worflow
2023-09-29 16:56:12 +02:00
Hassan Ben Jobrane
5c51653aa0 chore: fix release worflow 2023-09-29 13:29:44 +01:00
Hassan Ben Jobrane
7348c15ad1 Merge pull request #2281 from nhost/changeset-release/main
chore: update versions
2023-09-29 13:48:59 +02:00
Hassan Ben Jobrane
44831e32a7 Merge pull request #2282 from nhost/chore/ci/fix-release-workflow
chore(ci): fix release workflow
2023-09-29 13:47:21 +02:00
Hassan Ben Jobrane
ee0f837762 chore(ci): fix release workflow 2023-09-29 12:32:05 +01:00
github-actions[bot]
e040979e91 chore: update versions 2023-09-29 10:59:27 +00:00
Hassan Ben Jobrane
68100d63b9 Merge pull request #2267 from nhost/changeset-release/main
chore: update versions
2023-09-29 12:56:45 +02:00
github-actions[bot]
9b800046d7 chore: update versions 2023-09-29 10:37:52 +00:00
Hassan Ben Jobrane
807d8574b6 Merge pull request #2280 from nhost/feat/dashboard/multiline-variables-input
feat(dashboard): make env value input multiline
2023-09-29 12:34:59 +02:00
Hassan Ben Jobrane
77028e4eef Merge pull request #2265 from nhost/chore/ci/disable-changeset-github-releases
chore(ci): disable automatic GitHub releases
2023-09-29 12:34:36 +02:00
Hassan Ben Jobrane
e0d32aab33 Merge pull request #2235 from nhost/chore/ci/release/recorder-steps
chore(ci): run publish to vercel before docker build
2023-09-29 12:34:16 +02:00
Hassan Ben Jobrane
75c4c8ae36 chore: add changeset 2023-09-29 11:14:53 +01:00
Hassan Ben Jobrane
1d90639e46 feat: make env value input multiline 2023-09-29 11:12:35 +01:00
David Barroso
765b398b21 chore(docs): added jit settings documentation (#2274)
Solves #2273
2023-09-29 10:44:32 +02:00
David Barroso
30aae1557c chore(docs): minor fix to performance documentation (#2272) 2023-09-29 10:44:02 +02:00
David Barroso
a3efc1d131 chore (docs): added storage/antivirus documentation (#2268)
Rearranged the storage section a bit and added:

https://docs-git-docs-storage-nhost.vercel.app/storage/av
2023-09-28 07:27:57 +02:00
David Barroso
612d754965 chore: docs: added more docs (#2264)
https://docs-git-docs-postgres-nhost.vercel.app/database/settings
https://docs-git-docs-postgres-nhost.vercel.app/database/extensions
https://docs-git-docs-postgres-nhost.vercel.app/graphql/settings
https://docs-git-docs-postgres-nhost.vercel.app/database/performance

---------

Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2023-09-28 07:27:15 +02:00
Hassan Ben Jobrane
b2e5f30379 Merge pull request #2269 from nhost/fix/docs/mermaid-theme
fix(docs): mermaid diagrams dark mode
2023-09-27 16:28:53 +01:00
Hassan Ben Jobrane
3b3e83a218 chore: add changeset 2023-09-27 15:42:48 +01:00
Hassan Ben Jobrane
0d5231f1a1 fix(docs): correct rendering of mermaid diagrams in dark mode 2023-09-27 15:41:59 +01:00
Hassan Ben Jobrane
1a8332a3ca Merge pull request #2266 from nhost/fix/settings/compute-resources
fix: make sure dedicated resources pricing follows total resources
2023-09-27 10:06:48 +01:00
Hassan Ben Jobrane
7418105de2 chore: remove commented code 2023-09-26 19:29:48 +01:00
Hassan Ben Jobrane
425d485f85 chore: add changeset 2023-09-26 17:09:08 +01:00
Hassan Ben Jobrane
d8d25b3ea0 fix: make sure dedicated resources pricing follows total resources on the top section 2023-09-26 17:06:51 +01:00
Hassan Ben Jobrane
320513f6f5 chore(ci): disable automatic GitHub releases 2023-09-26 16:34:18 +01:00
Hassan Ben Jobrane
b37053376d Merge pull request #2261 from nhost/changeset-release/main
chore: update versions
2023-09-22 12:00:44 +01:00
github-actions[bot]
c21ba4aebd chore: update versions 2023-09-22 10:46:59 +00:00
Hassan Ben Jobrane
58948c50d4 Merge pull request #2260 from nhost/fix/graphql/remove-unused-fields
fix: dashboard: remove unused fields
2023-09-22 11:44:23 +01:00
Hassan Ben Jobrane
ae324f67fa chore: add changeset 2023-09-22 11:33:51 +01:00
Hassan Ben Jobrane
acabf2b168 fix(dashboard): remove unused fields 2023-09-22 11:31:57 +01:00
Hassan Ben Jobrane
73cb65b9be Merge pull request #2255 from nhost/changeset-release/main
chore: update versions
2023-09-21 14:48:34 +01:00
github-actions[bot]
5e7c8395c2 chore: update versions 2023-09-21 11:33:11 +00:00
David Barroso
c2837209e6 chore: added codeql to CI (#2252) 2023-09-21 13:30:29 +02:00
Hassan Ben Jobrane
638710ea29 Merge pull request #2257 from nhost/fix/run/service-details
fix: dashboard: run: show correct private registry
2023-09-21 12:29:42 +01:00
Hassan Ben Jobrane
a79fddbafb fix(dashboard/run): remove image prop 2023-09-21 11:23:39 +01:00
Hassan Ben Jobrane
ab6a8f2add Merge pull request #2256 from nhost/feat/dashboard/query-software-versions
feat: dashboard: query software versions from platform
2023-09-21 11:12:52 +01:00
Hassan Ben Jobrane
69a5661bcf fix(dashboard/run): remove unused image prop 2023-09-21 10:37:18 +01:00
Hassan Ben Jobrane
0886118f9d Merge pull request #2254 from nhost/chore/run/remove-feature-flag
chore: remove run feature flag
2023-09-21 10:31:09 +01:00
Hassan Ben Jobrane
34fc08ca7c chore: add changeset 2023-09-21 10:18:05 +01:00
Hassan Ben Jobrane
153de22713 fix(dashboard): show correct private registry when showing service details 2023-09-21 10:16:52 +01:00
Hassan Ben Jobrane
bf4a1f6c2a chore: add changeset 2023-09-20 15:26:22 +01:00
Hassan Ben Jobrane
2a67d0f872 feat(dashboard): fetch Storage versions from platform 2023-09-20 15:23:17 +01:00
Hassan Ben Jobrane
b156c7b72e feat(dashboard): fetch Auth versions from platform 2023-09-20 15:22:55 +01:00
Hassan Ben Jobrane
b484b04ae2 feat(dashboard): fetch Hasura versions from platform 2023-09-20 15:18:40 +01:00
Hassan Ben Jobrane
2e55c7f46a feat(dashboard): fetch postgres versions from platform 2023-09-20 14:06:19 +01:00
Hassan Ben Jobrane
2d983e6ab1 feat(dashboard): codegen graphql query for getting software versions 2023-09-20 14:05:41 +01:00
Hassan Ben Jobrane
df5b4302c3 chore: add changeset 2023-09-20 12:12:42 +01:00
Hassan Ben Jobrane
828aed2df9 chore: remove run feature flag 2023-09-20 12:12:42 +01:00
Hassan Ben Jobrane
310df10892 Merge pull request #2251 from nhost/chore/replace-feedback
chore(dashboard): replace feedback form with contact us
2023-09-20 12:08:52 +01:00
Hassan Ben Jobrane
555fba4400 chore: re-arrange text content 2023-09-20 11:43:27 +01:00
Hassan Ben Jobrane
885d10620a chore: add changeset 2023-09-20 11:24:20 +01:00
Hassan Ben Jobrane
a8370f5aaa chore: fix nav test 2023-09-20 11:03:43 +01:00
Hassan Ben Jobrane
bd07905846 chore(dashboard): replace feedback form with contact us 2023-09-19 19:25:53 +01:00
Nuno Pato
47a2164549 Merge pull request #2250 from nhost/changeset-release/main
chore: update versions
2023-09-19 16:57:24 +00:00
github-actions[bot]
a96c79de00 chore: update versions 2023-09-19 16:42:43 +00:00
Nuno Pato
596d0666fc Merge pull request #2249 from nhost/chore/docs-run-public-beta
chore: docs: Run is now in public beta
2023-09-19 16:40:04 +00:00
Nuno Pato
9aaa407d29 Run is now in public beta 2023-09-19 16:01:57 +00:00
Hassan Ben Jobrane
1767b2f105 Merge pull request #2248 from nhost/changeset-release/main
chore: update versions
2023-09-18 20:17:34 +01:00
github-actions[bot]
c99c5c4191 chore: update versions 2023-09-18 19:03:08 +00:00
Hassan Ben Jobrane
d845da2503 Merge pull request #2246 from nhost/fix/one-click-install
fix: run: center the loading indicator
2023-09-18 20:00:37 +01:00
Hassan Ben Jobrane
9f1ba1686c Merge pull request #2247 from nhost/feat/run/delete-service-confirmation-dialog
feat: run: delete service confirmation dialog
2023-09-18 19:57:21 +01:00
Hassan Ben Jobrane
48b09a58ff fix: typo
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2023-09-18 18:54:37 +01:00
Hassan Ben Jobrane
2169908883 chore: add changeset 2023-09-18 18:48:06 +01:00
Hassan Ben Jobrane
ed16c8b5de chore: add changeset 2023-09-18 18:45:11 +01:00
Hassan Ben Jobrane
c618503376 feat(run): add a confirmation dialog when deleting a run service 2023-09-18 18:45:03 +01:00
Hassan Ben Jobrane
f306c3940c fix: center the loading indicator 2023-09-18 16:53:15 +01:00
Hassan Ben Jobrane
ef125216bb Merge pull request #2242 from nhost/changeset-release/main
chore: update versions
2023-09-15 17:33:37 +01:00
github-actions[bot]
fb43fefb5c chore: update versions 2023-09-15 16:22:32 +00:00
Hassan Ben Jobrane
73744c90f0 Merge pull request #2241 from nhost/feat/node18-announcement
feat: add node18 announcement banner
2023-09-15 17:19:27 +01:00
Hassan Ben Jobrane
9fbea9787e chore: add changeset 2023-09-15 16:47:21 +01:00
Hassan Ben Jobrane
e5f54bc197 feat: add node18 announcement banner 2023-09-15 16:43:59 +01:00
Hassan Ben Jobrane
10a6ae4853 Merge pull request #2233 from nhost/changeset-release/main
chore: update versions
2023-09-13 17:11:54 +01:00
github-actions[bot]
d6ca1c7cfd chore: update versions 2023-09-13 13:59:01 +00:00
Hassan Ben Jobrane
bb85a95eda Merge pull request #2236 from nhost/fix/run/subdomain-optional
fix: run: handle subdomain nullability
2023-09-13 14:56:22 +01:00
Hassan Ben Jobrane
e84acf4692 chore: add changeset 2023-09-13 13:06:05 +01:00
Hassan Ben Jobrane
2f20a70a28 fix(run): subdomain is not set when creating a new service 2023-09-13 13:00:18 +01:00
Hassan Ben Jobrane
e622ca0d83 chore(ci): run publish to vercel before docker build 2023-09-12 15:09:34 +01:00
David Barroso
819e1e97dc chore (docs): update fqdn format for nhost run (#2232) 2023-09-12 14:54:39 +02:00
Hassan Ben Jobrane
7c1cca0a43 Merge pull request #2231 from nhost/changeset-release/main
chore: update versions
2023-09-12 13:09:07 +01:00
github-actions[bot]
0f51f4e868 chore: update versions 2023-09-12 12:05:55 +00:00
Hassan Ben Jobrane
97a6fcead9 Merge pull request #2230 from nhost/feat/run/copy-urls-dialog
feat(run): add dialog to copy service urls
2023-09-12 13:03:17 +01:00
Hassan Ben Jobrane
b7c799d62c chore: add changeset 2023-09-12 12:04:41 +01:00
Hassan Ben Jobrane
18b14b27fd refactor: pass service data directly to the details dialog 2023-09-12 12:01:20 +01:00
Hassan Ben Jobrane
67a867c93a feat: add dialog to copy service urls 2023-09-11 19:52:51 +01:00
David Barroso
0a1fb12467 feat: observability: add egress/requests metrics to general dashboard (#2227) 2023-09-06 18:03:25 +02:00
Hassan Ben Jobrane
78467ee348 Merge pull request #2219 from nhost/changeset-release/main
chore: update versions
2023-09-04 11:58:34 +01:00
github-actions[bot]
c24eef0db9 chore: update versions 2023-09-04 10:24:35 +00:00
Hassan Ben Jobrane
2159b8171e Merge pull request #2218 from nhost/fix/format-functions-execution
fix: dashboard: usage stats
2023-09-04 11:21:25 +01:00
Hassan Ben Jobrane
8903e6abd9 chore: add changeset 2023-09-02 15:18:54 +01:00
Hassan Ben Jobrane
7290260990 fix: show correct egress volume limit 2023-09-02 15:15:43 +01:00
Hassan Ben Jobrane
06529a1ea4 fix: round up functions duration 2023-09-02 15:14:29 +01:00
Hassan Ben Jobrane
607d89e2aa Merge pull request #2215 from nhost/changeset-release/main
chore: update versions
2023-09-01 19:13:41 +01:00
github-actions[bot]
0cca72311c chore: update versions 2023-09-01 15:26:44 +00:00
Hassan Ben Jobrane
a6525b6467 Merge pull request #2214 from nhost/feat/update-usage-metrics
feat(dashboard): update usage metrics
2023-09-01 16:24:06 +01:00
Hassan Ben Jobrane
387be37b6e chore: remove redundant egress card 2023-09-01 15:30:01 +01:00
Hassan Ben Jobrane
c8fd8bbcc7 fix: update storage upper limit for pro plan 2023-09-01 13:01:27 +01:00
Hassan Ben Jobrane
bfb34bad00 fix: use correct value for functions duration 2023-09-01 12:28:19 +01:00
Hassan Ben Jobrane
666a75a233 chore: add changeset 2023-09-01 12:26:41 +01:00
Hassan Ben Jobrane
3b050217df feat(dashboard): tweak usage metrics 2023-09-01 12:25:15 +01:00
Hassan Ben Jobrane
0ed4481615 feat(dashboard): update usage metrics 2023-09-01 11:14:49 +01:00
Hassan Ben Jobrane
ac3f12c878 Merge pull request #2211 from nhost/changeset-release/main
chore: update versions
2023-08-31 12:29:34 +01:00
github-actions[bot]
65cabb089f chore: update versions 2023-08-31 11:01:17 +00:00
Hassan Ben Jobrane
2905beb0a1 Merge pull request #2212 from nhost/fix/hasura-storage-js-edge-runtime
fix(hasura-storage-js): swap fetch when running on edge runtime
2023-08-31 11:58:43 +01:00
Hassan Ben Jobrane
83fee54460 chore: add changeset 2023-08-31 11:11:44 +01:00
Hassan Ben Jobrane
82898b6dae fix(hasura-storage-js): swap fetch when running on edge runtime 2023-08-31 11:09:37 +01:00
Hassan Ben Jobrane
500f76a38d Merge pull request #2208 from nhost/fix/user-auth-locales
fix: remove hardcoded locales
2023-08-30 10:31:43 +01:00
Hassan Ben Jobrane
5e1e80aa8b chore: add changeset 2023-08-29 20:05:29 +01:00
Hassan Ben Jobrane
6d0a126907 fix: remove hardcoded locales 2023-08-29 13:32:12 +01:00
Hassan Ben Jobrane
1b7dcf2121 Merge pull request #2207 from nhost/changeset-release/main
chore: update versions
2023-08-28 16:40:51 +01:00
github-actions[bot]
2b9205b6cf chore: update versions 2023-08-28 15:16:01 +00:00
Hassan Ben Jobrane
bdc4d4a88c Merge pull request #2206 from nhost/fix/stripe-graphql-js
fix(stripe-graphql-js): fix stripe GraphQL extension export issue in serverless functions
2023-08-28 16:12:13 +01:00
Hassan Ben Jobrane
45759c4d4c chore: add changeset 2023-08-28 15:49:17 +01:00
Hassan Ben Jobrane
5f9886577a fix: import 2023-08-28 15:47:49 +01:00
Hassan Ben Jobrane
fa65496327 fix(stripe-extension): return yoga instance instead of node http server 2023-08-28 15:24:56 +01:00
Hassan Ben Jobrane
03777680c1 chore: add STRIPE_SECRET_KEY 2023-08-26 16:51:31 +01:00
Hassan Ben Jobrane
72c81207ff Merge pull request #2201 from nhost/chore/add-missing-changeset
chore: add missing changeset
2023-08-24 16:47:41 +01:00
Hassan Ben Jobrane
5ca2a394e8 chore: sync version in package.json 2023-08-24 16:30:18 +01:00
Hassan Ben Jobrane
e63b8da58a chore: add missing changeset 2023-08-24 16:27:38 +01:00
Hassan Ben Jobrane
bf8543cd34 Merge pull request #2195 from nhost/changeset-release/main
chore: update versions
2023-08-24 13:57:34 +01:00
github-actions[bot]
8a557bbd02 chore: update versions 2023-08-24 12:21:34 +00:00
Hassan Ben Jobrane
327e30b859 Merge pull request #2200 from nhost/chore/ignore-version-update-sveltekit-example
chore: sveltekit-example: changeset ignore dep version update
2023-08-24 13:18:25 +01:00
Hassan Ben Jobrane
bbfaf9732b chore: sveltekit-example: ignore changeset dep version update 2023-08-24 12:44:16 +01:00
Hassan Ben Jobrane
c064a53256 Merge pull request #2199 from nhost/chore/fix-dep-version
chore: fix dep version for sveltekit example
2023-08-24 12:03:57 +01:00
Hassan Ben Jobrane
ebda86f1f0 chore: sync lockfile 2023-08-24 11:53:41 +01:00
Hassan Ben Jobrane
8948be9d3d chore: fix dep version for sveltekit example 2023-08-24 11:50:47 +01:00
Hassan Ben Jobrane
54e9b141f1 Merge pull request #2191 from nhost/dbarroso/react-example
chore: react-apollo-example: add profile to allowedUrls
2023-08-24 10:56:45 +01:00
Hassan Ben Jobrane
dba71483df chore: add changeset 2023-08-24 10:41:58 +01:00
Hassan Ben Jobrane
77ef68232a Merge pull request #2197 from nhost/fix/webauthn-error-handling
fix(hasura-auth-js): make sure CodifiedError works on non v8 browsers
2023-08-24 10:26:46 +01:00
Hassan Ben Jobrane
8fbc7f9f95 Merge pull request #2198 from nhost/chore/remove-facebook-login
chore(react-apollo-example): remove facebook login
2023-08-24 10:26:31 +01:00
Hassan Ben Jobrane
ca9f0f6ae9 chore: show error toast when adding a security key fails 2023-08-23 23:48:45 +01:00
Hassan Ben Jobrane
e819903f1b chore: add changeset 2023-08-23 17:00:30 +01:00
Hassan Ben Jobrane
f780b17581 chore: remove facebook login from react apollo example 2023-08-23 16:59:44 +01:00
Hassan Ben Jobrane
032c0bd217 chore: add changeset 2023-08-23 16:51:14 +01:00
Hassan Ben Jobrane
5d278709cb fix(hasura-auth-js): make sure CodifiedError works on non v8 browsers 2023-08-23 16:25:57 +01:00
Hassan Ben Jobrane
3a012e089a Merge pull request #2182 from nhost/feat/add-sveltekit-example
feat: add sveltekit example
2023-08-23 12:14:38 +01:00
Hassan Ben Jobrane
7aed620e12 chore: fix tests 2023-08-23 11:39:29 +01:00
Hassan Ben Jobrane
d9fd1a54a5 Merge pull request #2192 from nhost/changeset-release/main
chore: update versions
2023-08-23 11:19:48 +01:00
github-actions[bot]
a19b85c8ac chore: update versions 2023-08-23 09:45:31 +00:00
Hassan Ben Jobrane
4e1aaca0ee Merge pull request #2194 from nhost/feat/toggle-av
feat: toggle av
2023-08-23 10:42:20 +01:00
Hassan Ben Jobrane
34ef37cdce Merge pull request #2190 from dddenis/fix/storage-upload-status-error
fix(hasura-storage-js): fix upload response status code check
2023-08-23 10:40:29 +01:00
Hassan Ben Jobrane
5d6b655cb1 fix: make sure AV turns off correctly 2023-08-23 01:40:01 +01:00
Hassan Ben Jobrane
074a0fa111 chore: add changeset 2023-08-22 18:32:34 +01:00
Hassan Ben Jobrane
403d839fca chore: cleanup 2023-08-22 18:30:26 +01:00
Hassan Ben Jobrane
4e3098240b feat(settings): add toggle av settings 2023-08-22 18:28:27 +01:00
Hassan Ben Jobrane
dd0a5cf3c1 chore: fix lock file 2023-08-22 16:55:28 +01:00
Hassan Ben Jobrane
5187fd3a4b chore: dashboard tests 2023-08-22 16:49:26 +01:00
Hassan Ben Jobrane
d8dfd6bf80 Revert "chore: add missing dep for vitest"
This reverts commit 6ea6ad61db.
2023-08-22 16:16:20 +01:00
Hassan Ben Jobrane
6ea6ad61db chore: add missing dep for vitest 2023-08-22 16:04:24 +01:00
Hassan Ben Jobrane
fd0b904ed4 chore: fix dashboard e2e tests 2023-08-22 15:41:36 +01:00
Hassan Ben Jobrane
8989e314a6 fix: ignore conflict with linting and sveltekit build 2023-08-22 14:40:18 +01:00
Hassan Ben Jobrane
5b5a1219c5 fix: make sure linting runs correctly 2023-08-22 14:31:45 +01:00
Hassan Ben Jobrane
07fda9bbb3 Merge pull request #2193 from nhost/fix/distinguish-not-uploaded-files
fix: grey out not uploaded files
2023-08-22 13:11:52 +01:00
Hassan Ben Jobrane
2fa828fef1 chore: cleanup .gitignore file 2023-08-22 13:10:59 +01:00
Hassan Ben Jobrane
d5ec69ac37 chore(examples-sveltekit): add a basic test 2023-08-22 13:07:06 +01:00
Hassan Ben Jobrane
4a7ede11e9 chore: add changeset 2023-08-22 11:33:42 +01:00
Hassan Ben Jobrane
482ae4c4f1 fix: grey not uploaded files 2023-08-22 11:25:23 +01:00
Denis Goncharenko
08fe4cd65f fix(hasura-storage-js): update upload response error details 2023-08-22 11:34:41 +02:00
Hassan Ben Jobrane
5781721bca Merge pull request #2188 from nhost/feat/one-click-run-service
feat: add support for template run services
2023-08-22 10:18:54 +01:00
Denis Goncharenko
39de0063bf chore: add changeset 2023-08-21 20:49:56 +02:00
Hassan Ben Jobrane
202b647234 chore: add changeset 2023-08-21 15:45:43 +01:00
Hassan Ben Jobrane
51c163a268 fix: copy complete link to config 2023-08-21 15:43:46 +01:00
Hassan Ben Jobrane
6e802c9938 fix: handle the case where the config is not set in the URL 2023-08-21 13:38:38 +01:00
Hassan Ben Jobrane
9a46104e37 feat: replace project selector with a searchable list 2023-08-21 13:26:27 +01:00
Hassan Ben Jobrane
655b317c39 fix: keep image field when copying config to clipboard 2023-08-21 13:25:57 +01:00
Hassan Ben Jobrane
d3ad7c9d4a fix: handle error when navigating back when service form is still open 2023-08-21 13:25:01 +01:00
David Barroso
09fc852c3a asd 2023-08-21 13:13:41 +02:00
Hassan Ben Jobrane
ece08d3efd feat: add ability to copy current service config from the editor 2023-08-21 10:47:54 +01:00
Hassan Ben Jobrane
3493442c2d fix: fix command import when value is null 2023-08-21 10:47:26 +01:00
Denis Goncharenko
632a79b9e4 fix(hasura-storage-js): fix upload response status code check 2023-08-20 14:15:24 +02:00
Hassan Ben Jobrane
4a4d85757a fix: use serviceId to determine whether to create or update service 2023-08-19 19:33:45 +01:00
Hassan Ben Jobrane
88a01004b7 feat: parse base64 encoded config from query param 2023-08-19 18:50:46 +01:00
David Barroso
73230eb35a chore: fix and deploy react example (#2183) 2023-08-19 07:57:42 +02:00
Hassan Ben Jobrane
27e1c90624 fix: change env to dynamic 2023-08-18 18:26:09 +01:00
Hassan Ben Jobrane
1cc53d550a chore: add changeset 2023-08-18 18:04:12 +01:00
Hassan Ben Jobrane
22d3f71e02 fix: make sure to include lib folder in sveltekit example 2023-08-18 17:58:29 +01:00
Hassan Ben Jobrane
010b816866 chore: fix README 2023-08-18 17:36:10 +01:00
Hassan Ben Jobrane
4a6e62e673 feat: add sveltekit example 2023-08-18 17:16:13 +01:00
Hassan Ben Jobrane
5cf9dd9bc2 Merge pull request #2173 from nhost/changeset-release/main
chore: update versions
2023-08-14 11:34:45 +01:00
github-actions[bot]
27e74c10d7 chore: update versions 2023-08-10 11:05:13 +00:00
Hassan Ben Jobrane
bd807a5ee1 Merge pull request #2171 from nhost/feat/run-pricing
feat: add pricing info and confirmation dialog
2023-08-10 12:01:04 +01:00
Hassan Ben Jobrane
4093e03a13 Merge pull request #2170 from nhost/feat/multiline-env-input
fix: nhost run enhancements
2023-08-10 12:00:53 +01:00
Hassan Ben Jobrane
29076d0304 Merge pull request #2169 from nhost/fix/null-values-services-form
fix(services): handle null values when editing a service
2023-08-10 12:00:43 +01:00
Hassan Ben Jobrane
ab83fa6b5e fix: make sure vCPUs are shown correctly 2023-08-09 16:20:45 +01:00
Hassan Ben Jobrane
b20761e976 chore: add changeset 2023-08-09 14:55:31 +01:00
Hassan Ben Jobrane
a445e5b786 feat: add pricing info and confirmation dialog 2023-08-09 14:53:05 +01:00
Hassan Ben Jobrane
90df6d81d8 chore: add changeset 2023-08-08 18:04:16 +01:00
Hassan Ben Jobrane
aa85084675 chore: add changeset 2023-08-08 18:02:35 +01:00
Hassan Ben Jobrane
07ad470c0c fix: query service logs correctly 2023-08-08 18:00:25 +01:00
Hassan Ben Jobrane
fa6b58a9c5 feat: enable multiline support for environment value input 2023-08-08 17:50:07 +01:00
Hassan Ben Jobrane
acf55376ba fix(services): handle null values when editing a service 2023-08-08 15:42:09 +01:00
Hassan Ben Jobrane
b0a9798b04 Merge pull request #2168 from nhost/changeset-release/main
chore: update versions
2023-08-08 11:17:25 +01:00
github-actions[bot]
3952e87f01 chore: update versions 2023-08-07 17:53:16 +00:00
Hassan Ben Jobrane
b95ccf873d Merge pull request #2167 from nhost/fix/announcement-banner-text
fix: make announcement text white in dark mode
2023-08-07 18:50:41 +01:00
Hassan Ben Jobrane
8d7f84b8da chore: add changeset 2023-08-07 18:33:17 +01:00
Hassan Ben Jobrane
bd1b69bd75 fix: make announcement text white in dark mode 2023-08-07 12:17:18 +01:00
Hassan Ben Jobrane
84d5436634 Merge pull request #2165 from nhost/changeset-release/main
chore: update versions
2023-08-07 12:09:08 +01:00
github-actions[bot]
2325766c1d chore: update versions 2023-08-04 16:06:40 +00:00
Hassan Ben Jobrane
2c355eaae4 Merge pull request #2162 from nhost/fix/dedicated-resources-modal
fix: show zero values when dedicated resources is disabled
2023-08-04 17:03:08 +01:00
Hassan Ben Jobrane
9e26ed767e Merge pull request #2161 from nhost/fix/announcement-banner
fix: make announcement close properly
2023-08-04 17:02:58 +01:00
Hassan Ben Jobrane
abdb6c56f4 chore: increase e2e CI timeout 2023-08-03 17:18:03 +01:00
Hassan Ben Jobrane
3b75bfce27 chore: add changeset 2023-08-03 17:03:01 +01:00
Hassan Ben Jobrane
f498190758 chore: add changeset 2023-08-03 17:00:10 +01:00
Hassan Ben Jobrane
b4158fa513 fix: show zero values when dedicated resources is disabled 2023-08-03 16:54:12 +01:00
Hassan Ben Jobrane
3d1a177632 fix: make announcement close properly 2023-08-03 15:17:06 +01:00
Hassan Ben Jobrane
0675a213b5 Merge pull request #2155 from nhost/changeset-release/main
chore: update versions
2023-08-01 14:28:53 +01:00
github-actions[bot]
a8ff383490 chore: update versions 2023-08-01 13:09:10 +00:00
David Barroso
960d815f68 chore(docs): added Nhost Run documentation (#2125)
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2023-08-01 15:05:56 +02:00
Hassan Ben Jobrane
edf2b4e93f Merge pull request #2150 from nhost/feat/tweak-copy-service-form
feat(services): consistent naming for compute
2023-08-01 14:04:22 +01:00
Hassan Ben Jobrane
fe240542a4 Merge pull request #2152 from nhost/fix/services-form
fix(services): fix when config is null
2023-08-01 14:04:09 +01:00
Hassan Ben Jobrane
c7752c0657 Merge pull request #2154 from nhost/feat/run-announcement
feat(run): add annoucement for nhost run
2023-08-01 14:03:55 +01:00
Hassan Ben Jobrane
d1e2b1c75a chore: tweak announcement copy 2023-08-01 14:03:25 +01:00
Hassan Ben Jobrane
bcdab66bf8 chore: add changeset 2023-08-01 13:26:43 +01:00
Hassan Ben Jobrane
7636f40030 chore: remove unused 2023-08-01 13:25:28 +01:00
Hassan Ben Jobrane
e643bd3620 chore: add changeset 2023-08-01 13:23:13 +01:00
Hassan Ben Jobrane
311c7756d7 chore: add changeset 2023-08-01 13:21:17 +01:00
David Barroso
f967a2e596 chore(docs): added note about storage not being able to be downsized (#2153) 2023-08-01 14:16:16 +02:00
Hassan Ben Jobrane
4c4b253a71 chore: fix lockfile 2023-08-01 13:07:16 +01:00
Hassan Ben Jobrane
0f5f8c0d90 chore: revert lockfile 2023-08-01 13:00:37 +01:00
Hassan Ben Jobrane
37a7fc05d5 feat(run): add annoucement for nhost run 2023-08-01 12:37:23 +01:00
Hassan Ben Jobrane
bf93d87b36 fix(services): fix when config is null 2023-07-31 18:46:39 +01:00
Hassan Ben Jobrane
efb3dc7294 feat(services): consistent naming for compute 2023-07-27 17:24:42 +01:00
Hassan Ben Jobrane
42bd7807b2 Merge pull request #2148 from nhost/chore/increase-ci-timeout
chore: increase timeout for Publish to Docker Hub
2023-07-25 16:08:34 +01:00
Hassan Ben Jobrane
eea59bd202 chore: increase timeout for Publish to Docker Hub 2023-07-25 15:36:15 +01:00
Hassan Ben Jobrane
7248eb733f Merge pull request #2146 from nhost/changeset-release/main
chore: update versions
2023-07-25 13:12:50 +01:00
github-actions[bot]
fceb6a4a89 chore: update versions 2023-07-25 11:52:49 +00:00
Hassan Ben Jobrane
b10eca09a8 Merge pull request #2126 from nhost/renovate/turbo-monorepo
chore(deps): update dependency turbo to v1.10.11
2023-07-25 12:49:57 +01:00
Hassan Ben Jobrane
4799b65e96 Merge pull request #2144 from nhost/feat/add-run-services-to-logs
feat: add run/services to the logs
2023-07-25 12:24:13 +01:00
Hassan Ben Jobrane
067eb9d6a9 Merge pull request #2143 from nhost/feat/change-grafana-url
feat: change grafana url to point to dashboards
2023-07-25 12:24:03 +01:00
Hassan Ben Jobrane
219d5ecdcf Merge pull request #2140 from nhost/fix/create-service-form
fix: tweaking the create service form
2023-07-25 12:23:47 +01:00
Hassan Ben Jobrane
9073182d51 chore: add changeset 2023-07-25 12:20:15 +01:00
Hassan Ben Jobrane
bdb5783e79 chore: sync turbo version in Dockerfile 2023-07-25 12:19:09 +01:00
Hassan Ben Jobrane
ece717d6e0 chore: add changeset 2023-07-25 12:01:53 +01:00
Hassan Ben Jobrane
b135ef695c chore: add changeset 2023-07-25 11:38:00 +01:00
Hassan Ben Jobrane
82b3353110 chore: add changeset 2023-07-25 11:34:20 +01:00
Hassan Ben Jobrane
3f165a85e3 fix: grafana urls in generateAppServiceUrl.test 2023-07-25 11:14:58 +01:00
renovate[bot]
aa4018909f chore(deps): update dependency turbo to v1.10.11 2023-07-24 23:03:50 +00:00
Hassan Ben Jobrane
98397e3ccd feat: add run/services to the logs 2023-07-24 18:00:22 +01:00
Hassan Ben Jobrane
911e7112c9 feat: change grafana url to point to dashboards 2023-07-24 17:32:46 +01:00
Hassan Ben Jobrane
e62402ecfc feat(services): add toolip content 2023-07-24 10:24:24 +01:00
Hassan Ben Jobrane
9190dd726d Merge pull request #2138 from nhost/changeset-release/main
chore: update versions
2023-07-23 21:31:33 +01:00
github-actions[bot]
ae093283d0 chore: update versions 2023-07-23 20:13:51 +00:00
Hassan Ben Jobrane
875327fbea Merge pull request #2136 from tawsbob/bug/users-page-limit
fix: back to previous limit
2023-07-23 21:11:11 +01:00
Hassan Ben Jobrane
3d5c34f4ce chore: add changeset 2023-07-23 20:53:14 +01:00
Dellean Santos
58c2a20532 fix: back to previous limit 2023-07-23 16:17:10 -03:00
Hassan Ben Jobrane
6c90cb5024 fix: make command optional and set min replicas 0 2023-07-22 19:58:34 +01:00
Hassan Ben Jobrane
7e37570587 Merge pull request #2133 from nhost/chore/ci-trigger-release-manually
chore(ci): add support for triggering release manually
2023-07-21 19:27:04 +01:00
Hassan Ben Jobrane
87d225a840 chore(ci): trigger release manually 2023-07-21 19:08:34 +01:00
Hassan Ben Jobrane
7b0de27c80 Merge pull request #2132 from nhost/changeset-release/main
chore: update versions
2023-07-21 17:43:45 +01:00
github-actions[bot]
564fc76195 chore: update versions 2023-07-21 16:24:17 +00:00
Hassan Ben Jobrane
2ed4f40c12 Merge pull request #2120 from nhost/feat/services
feat: services
2023-07-21 17:21:44 +01:00
Hassan Ben Jobrane
d67a023e21 feat(services): block services for free apps 2023-07-21 16:51:58 +01:00
Hassan Ben Jobrane
c99d117d1c chore: add changeset 2023-07-21 15:40:08 +01:00
Hassan Ben Jobrane
a497a6ba0a feat(services): mod port url if values are empty 2023-07-21 12:57:18 +01:00
Hassan Ben Jobrane
160cd08cc7 feat(services): fix e2e nav test 2023-07-21 10:35:51 +01:00
Hassan Ben Jobrane
120151c40c feat(services): run pnpm install 2023-07-20 19:14:47 +01:00
Hassan Ben Jobrane
9dc16f29b3 feat(services): add services pagination 2023-07-20 19:10:10 +01:00
Hassan Ben Jobrane
964fc5644a feat(services): put services behind a feature flag 2023-07-20 19:09:48 +01:00
Hassan Ben Jobrane
2f907fc68f feat(services): tweak service form on mobile 2023-07-20 12:41:35 +01:00
Hassan Ben Jobrane
fe6cadc2cd feat(services): tweak services list 2023-07-20 11:40:45 +01:00
Hassan Ben Jobrane
338c8e5a80 feat(services): fix command on initialData 2023-07-19 17:55:47 +01:00
Hassan Ben Jobrane
e6f3a1a39d feat(services): tweak compute section 2023-07-19 16:20:52 +01:00
Hassan Ben Jobrane
a168faeb69 feat(services): tweak service form 2023-07-19 14:00:25 +01:00
Hassan Ben Jobrane
b1628c59b5 feat(services): refactor command field 2023-07-19 13:36:37 +01:00
Hassan Ben Jobrane
32a2f5db9a feat(services): fix replicas form section 2023-07-19 11:57:10 +01:00
Hassan Ben Jobrane
818a48f74d feat(services): refactor edit service 2023-07-19 10:58:56 +01:00
Hassan Ben Jobrane
bed377d05f feat(services): add service details page 2023-07-18 16:19:48 +01:00
Hassan Ben Jobrane
709a616cfa feat(services): fix e2e nav item count 2023-07-17 19:59:24 +01:00
Hassan Ben Jobrane
860e2d877c feat(services): fix linter errors 2023-07-17 19:40:21 +01:00
Hassan Ben Jobrane
5c6b2f88b9 fix: export component properly 2023-07-17 18:43:41 +01:00
Hassan Ben Jobrane
f151a0e872 feat(services): tweaks + show URL for ports/image 2023-07-17 17:01:28 +01:00
Hassan Ben Jobrane
4a84bbb410 feat(service): tweaks + add service details page 2023-07-17 16:10:18 +01:00
Hassan Ben Jobrane
fa3a50e323 feat(service): add GiB to storage capacity field 2023-07-16 23:48:48 +01:00
Hassan Ben Jobrane
398152358c feat(service): tweak compute form section 2023-07-16 23:43:44 +01:00
Hassan Ben Jobrane
34ae9046f3 feat(services): add fetch and delete services 2023-07-16 23:32:24 +01:00
Hassan Ben Jobrane
a478689587 feat(services): fix compute section 2023-07-14 19:54:24 +01:00
Hassan Ben Jobrane
9dbc0607dc feat(services): hook up create service to the api 2023-07-14 19:43:08 +01:00
Hassan Ben Jobrane
7455efdd53 feat(services): tweak create service form 2023-07-14 15:20:14 +01:00
Hassan Ben Jobrane
d0aff6141f feat(services): add info tooltip to each section 2023-07-14 13:59:41 +01:00
Hassan Ben Jobrane
aed0c4f82a feat(services): add create service form 2023-07-14 12:20:34 +01:00
Hassan Ben Jobrane
74d4276c1a feat(services): add new page for services 2023-07-13 11:55:20 +01:00
Hassan Ben Jobrane
1e98130aa1 Merge pull request #2113 from nhost/changeset-release/main
chore: update versions
2023-07-12 20:34:35 +01:00
github-actions[bot]
52e9b510da chore: update versions 2023-07-12 19:22:34 +00:00
Hassan Ben Jobrane
ece197eb6b Merge pull request #2116 from nhost/renovate/prettier-plugin-tailwindcss-0.x
chore(deps): update dependency prettier-plugin-tailwindcss to ^0.4.0
2023-07-12 20:20:50 +01:00
Hassan Ben Jobrane
d14e112bff chore: add changeset 2023-07-12 17:29:12 +01:00
renovate[bot]
83884f04a5 chore(deps): update dependency prettier-plugin-tailwindcss to ^0.4.0 2023-07-12 16:10:24 +00:00
Hassan Ben Jobrane
977de21e86 Merge pull request #2117 from nhost/chore/add-hasura-auth-version
chore: add hasura-auth version 0.20.2
2023-07-12 17:07:27 +01:00
Hassan Ben Jobrane
462a60a8f8 chore: fix hasura-auth version 2023-07-12 16:45:01 +01:00
Hassan Ben Jobrane
9aa4371ef4 chore: add changeset 2023-07-12 16:45:01 +01:00
Hassan Ben Jobrane
f0feddd83f chore: add hasura-auth version 0.20.2 2023-07-12 16:45:01 +01:00
Hassan Ben Jobrane
0748cab125 Merge pull request #2087 from nhost/renovate/vite-plugin-dts-3.x
chore(deps): update dependency vite-plugin-dts to v3
2023-07-12 16:41:59 +01:00
Hassan Ben Jobrane
27885491ee chore: fix test project subdomain 2023-07-12 13:55:14 +01:00
Hassan Ben Jobrane
a36bdbf907 chore: uncomment setting preview URL 2023-07-12 13:53:42 +01:00
Hassan Ben Jobrane
d3e8bb94ae chore: add changeset 2023-07-11 16:39:26 +01:00
Hassan Ben Jobrane
645595ee43 Revert "chore: increase playwright timeout"
This reverts commit 72d1e94cb3.
2023-07-11 16:36:46 +01:00
Hassan Ben Jobrane
4d82bc5609 Revert "chore: playwright: increase number of workers"
This reverts commit b4c10f9f8a.
2023-07-11 16:17:30 +01:00
Hassan Ben Jobrane
fdf1e555d8 chore: ci: comment Fetch Dashboard Preview URL 2023-07-11 16:13:50 +01:00
Hassan Ben Jobrane
90c694cbba chore: ci: Comment step Set Dashboard Preview URL 2023-07-11 15:51:03 +01:00
Hassan Ben Jobrane
3262fa7b37 chore: teardown: run playwright in slowMo 2023-07-11 15:09:25 +01:00
Hassan Ben Jobrane
ab43fe567f chore: fix inserting sql in hasura page 2023-07-11 14:47:21 +01:00
Hassan Ben Jobrane
b4c10f9f8a chore: playwright: increase number of workers 2023-07-11 14:26:22 +01:00
Hassan Ben Jobrane
f4c6e7cfab chore: bring back raw_sql fill 2023-07-11 13:58:41 +01:00
Hassan Ben Jobrane
72d1e94cb3 chore: increase playwright timeout 2023-07-11 13:36:44 +01:00
Hassan Ben Jobrane
82d221a48d Revert "chore: increase CI e2e timeout"
This reverts commit 3fe46771b9.
2023-07-11 11:57:36 +01:00
Hassan Ben Jobrane
3fe46771b9 chore: increase CI e2e timeout 2023-07-11 11:06:09 +01:00
Hassan Ben Jobrane
a1c487aa21 chore: fix lock file 2023-07-11 01:55:39 +01:00
Hassan Ben Jobrane
cf455608e2 chore: fix e2e tests 2023-07-11 01:40:17 +01:00
Hassan Ben Jobrane
5dac12dd41 chore: use node v18 2023-07-10 18:15:22 +01:00
Hassan Ben Jobrane
2389b46e0d chore: update node to v18 2023-07-10 16:40:41 +01:00
renovate[bot]
6fe2d22d0e chore(deps): update dependency vite-plugin-dts to v3 2023-07-10 15:23:12 +00:00
Hassan Ben Jobrane
0b439149e4 Merge pull request #2106 from nhost/renovate/pluralize-0.x
chore(deps): update dependency @types/pluralize to ^0.0.30
2023-07-10 16:19:28 +01:00
Hassan Ben Jobrane
a9d7da8af7 chore: add changeset 2023-07-10 16:09:14 +01:00
renovate[bot]
3ecc21a45e chore(deps): update dependency @types/pluralize to ^0.0.30 2023-07-10 14:30:41 +00:00
Hassan Ben Jobrane
aa19e85cdc Merge pull request #2088 from nhost/renovate/turbo-monorepo
chore(deps): update dependency turbo to v1.10.7
2023-07-10 15:28:49 +01:00
Hassan Ben Jobrane
26c650227d Merge pull request #2111 from nhost/fix/tweak-config-warning
fix: tweak warning in dark mode
2023-07-10 15:16:25 +01:00
Hassan Ben Jobrane
face99ccde chore: add changeset 2023-07-10 15:07:25 +01:00
Hassan Ben Jobrane
49bcc525ad chore: bump turbo version in Dockerfile 2023-07-10 15:07:25 +01:00
renovate[bot]
533563c893 chore(deps): update dependency turbo to v1.10.7 2023-07-10 15:07:25 +01:00
Hassan Ben Jobrane
cfe527307e chore: add changeset 2023-07-10 15:05:22 +01:00
Hassan Ben Jobrane
1e36c6706d Revert "chore: use node 18 for GH actions"
This reverts commit 6e40b114fc.
2023-07-10 15:01:46 +01:00
Hassan Ben Jobrane
6e40b114fc chore: use node 18 for GH actions 2023-07-10 13:58:11 +01:00
Hassan Ben Jobrane
77acf1385d Revert "chore: increase ci timeout"
This reverts commit cec7edd2d5.
2023-07-10 12:23:22 +01:00
Hassan Ben Jobrane
cec7edd2d5 chore: increase ci timeout 2023-07-10 10:50:55 +01:00
Hassan Ben Jobrane
9dbbdb3121 fix: show only when a repo is connected 2023-07-07 19:24:09 +01:00
Hassan Ben Jobrane
79d2602648 fix: tweak warning in dark mode 2023-07-07 18:52:24 +01:00
Hassan Ben Jobrane
b0363a4f4c Merge pull request #2110 from nhost/changeset-release/main
chore: update versions
2023-07-07 17:18:43 +01:00
github-actions[bot]
17045b2018 chore: update versions 2023-07-07 16:07:03 +00:00
Hassan Ben Jobrane
c49cc11862 Merge pull request #2108 from nhost/feat/fix-hasura-storage-file-upload
fix(hasura-storage-js): fix file upload
2023-07-07 17:05:33 +01:00
Hassan Ben Jobrane
c83fe7d776 chore(e2e): change e2e tests timeout 2023-07-07 16:48:49 +01:00
Hassan Ben Jobrane
235b4c7405 chore: wrap secret values in quotes 2023-07-07 16:08:24 +01:00
Hassan Ben Jobrane
c2c0fbd33a chore(e2e): increase timeout 2023-07-07 15:19:02 +01:00
Hassan Ben Jobrane
300e3f49e0 chore: add changeset 2023-07-07 14:21:12 +01:00
Hassan Ben Jobrane
a95a77886b fix(hasura-storage-js): fix file upload 2023-07-07 10:44:42 +01:00
Stephan van Opstal
1f3f683202 Update serverless-functions.mdx (#2105)
Please correct me if I'm wrong but I believe the endpoints in the docs
are wrong.
2023-07-07 08:36:57 +02:00
github-actions[bot]
4c67fd23c4 chore: update versions (#2101)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@0.19.0

### Minor Changes

- 9c61c69a7: chore(dashboard):add postgres 14.6-20230705-1 to the
version selector

### Patch Changes

-   47bda15ff: feat(settings): add warning to pull config

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-07-06 20:15:13 +02:00
Hassan Ben Jobrane
93d8d71e34 Merge pull request #2102 from nhost/feat/pull-config-warning
feat(settings): add warning to pull config
2023-07-06 15:06:08 +01:00
Hassan Ben Jobrane
47bda15ff2 chore: add changeset 2023-07-06 14:39:27 +01:00
Hassan Ben Jobrane
4563488b5d feat(settings): show alert when there's a repo 2023-07-06 14:35:31 +01:00
Hassan Ben Jobrane
8fd35f3fea feat(settings): add warning to pull config 2023-07-06 14:26:14 +01:00
David Barroso
9c61c69a7b chore(dashboard):add postgres 14.6-20230705-1 to the version selector (#2100) 2023-07-06 15:24:06 +02:00
github-actions[bot]
030ad4621e chore: update versions (#2098)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@0.18.0

### Minor Changes

- ee0b9b8ed: chore(dashboard):add hasura v2.28.2 and v2.29.0 to the
version selector

## @nhost/docs@0.4.0

### Minor Changes

-   c6fa8da6d: fix(docs): remove outdated reference/cli

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-07-06 13:34:58 +02:00
David Barroso
ee0b9b8edc chore(dashboard):add hasura v2.28.2 and v2.29.0 to the version selector (#2097) 2023-07-06 13:21:53 +02:00
David Barroso
c6fa8da6df fix(docs): remove outdated reference/cli (#2093)
Fixes nhost/cli/issues/734
2023-07-06 12:21:14 +02:00
487 changed files with 17352 additions and 2163 deletions

View File

@@ -5,6 +5,5 @@
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
"updateInternalDependencies": "patch"
}

View File

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

View File

@@ -10,6 +10,7 @@ on:
- '**.md'
- '!.changeset/**'
- 'LICENSE'
workflow_dispatch:
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
@@ -41,6 +42,7 @@ jobs:
commit: 'chore: update versions'
title: 'chore: update versions'
publish: pnpm run release
createGithubReleases: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -61,12 +63,39 @@ jobs:
uses: ./.github/workflows/dashboard.yaml
secrets: inherit
publish-vercel:
name: Publish to Vercel
runs-on: ubuntu-latest
needs:
- test
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Node and dependencies
uses: ./.github/actions/install-dependencies
with:
TURBO_TOKEN: ${{ env.TURBO_TOKEN }}
TURBO_TEAM: ${{ env.TURBO_TEAM }}
- name: Setup Vercel CLI
run: pnpm add -g vercel
- name: Trigger a Vercel deployment
env:
VERCEL_ORG_ID: ${{ secrets.DASHBOARD_VERCEL_TEAM_ID }}
VERCEL_PROJECT_ID: ${{ secrets.DASHBOARD_VERCEL_PROJECT_ID }}
run: |
vercel pull --environment=production --token=${{ secrets.DASHBOARD_VERCEL_DEPLOY_TOKEN }}
vercel build --prod --token=${{ secrets.DASHBOARD_VERCEL_DEPLOY_TOKEN }}
vercel deploy --prebuilt --prod --token=${{ secrets.DASHBOARD_VERCEL_DEPLOY_TOKEN }}
publish-docker:
name: Publish to Docker Hub
runs-on: ubuntu-latest
needs:
- test
- version
- publish-vercel
steps:
- name: Checkout repository
uses: actions/checkout@v3
@@ -99,7 +128,7 @@ jobs:
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push to Docker Hub
uses: docker/build-push-action@v4
timeout-minutes: 60
timeout-minutes: 90
with:
context: .
file: ./dashboard/Dockerfile
@@ -112,42 +141,6 @@ jobs:
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
push: true
- name: Create GitHub Release
uses: taiki-e/create-gh-release-action@v1
with:
changelog: dashboard/CHANGELOG.md
token: ${{ secrets.GITHUB_TOKEN }}
prefix: ${{ env.DASHBOARD_PACKAGE }}@
ref: refs/tags/${{ env.DASHBOARD_PACKAGE }}@${{ needs.version.outputs.dashboardVersion }}
- name: Remove tag on failure
if: failure()
run: git push --delete origin ${{ env.DASHBOARD_PACKAGE }}@${{ needs.version.outputs.dashboardVersion }}
publish-vercel:
name: Publish to Vercel
runs-on: ubuntu-latest
needs:
- publish-docker
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Node and dependencies
uses: ./.github/actions/install-dependencies
with:
TURBO_TOKEN: ${{ env.TURBO_TOKEN }}
TURBO_TEAM: ${{ env.TURBO_TEAM }}
- name: Setup Vercel CLI
run: pnpm add -g vercel
- name: Trigger a Vercel deployment
env:
VERCEL_ORG_ID: ${{ secrets.DASHBOARD_VERCEL_TEAM_ID }}
VERCEL_PROJECT_ID: ${{ secrets.DASHBOARD_VERCEL_PROJECT_ID }}
run: |
vercel pull --environment=production --token=${{ secrets.DASHBOARD_VERCEL_DEPLOY_TOKEN }}
vercel build --prod --token=${{ secrets.DASHBOARD_VERCEL_DEPLOY_TOKEN }}
vercel deploy --prebuilt --prod --token=${{ secrets.DASHBOARD_VERCEL_DEPLOY_TOKEN }}
bump-cli:
name: Bump Dashboard version in the Nhost CLI

View File

@@ -146,7 +146,7 @@ jobs:
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
- name: Run e2e tests
timeout-minutes: 7
timeout-minutes: 15
run: pnpm --filter="${{ matrix.package.name }}" run e2e
- id: file-name
if: ${{ failure() }}

56
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,56 @@
name: "CodeQL"
on:
push: {}
pull_request: {}
schedule:
- cron: '20 23 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

3
.gitignore vendored
View File

@@ -19,10 +19,8 @@ logs/
coverage/
dist/
umd/
lib/
node_modules/
tmp/
.docz/
.pnpm-store
.turbo
.env
@@ -32,7 +30,6 @@ out/
# Custom
*.min.js
*.map
todo.md
# Config files that are not part of the repository root anymore. Should be removed in the future.
/.eslintignore

View File

@@ -2,5 +2,6 @@
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
"eslint.workingDirectories": ["./dashboard"]
"eslint.workingDirectories": ["./dashboard"],
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@@ -34,7 +34,7 @@ Nhost consists of open source software:
- Authentication: [Hasura Auth](https://github.com/nhost/hasura-auth/)
- Storage: [Hasura Storage](https://github.com/nhost/hasura-storage)
- Serverless Functions: Node.js (JavaScript and TypeScript)
- [Nhost CLI](https://docs.nhost.io/reference/cli) for local development
- [Nhost CLI](https://docs.nhost.io/cli) for local development
## Architecture of Nhost
@@ -97,7 +97,7 @@ Nhost is frontend agnostic, which means Nhost works with all frontend frameworks
# Resources
- Start developing locally with the [Nhost CLI](https://docs.nhost.io/reference/cli)
- Start developing locally with the [Nhost CLI](https://docs.nhost.io/cli)
## Nhost Clients

View File

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

View File

@@ -1,5 +1,234 @@
# @nhost/dashboard
## 0.20.27
### Patch Changes
- fa79b7709: chore(dashboard): tweaks and fixes to the service form and dialog
- 8df84d782: fix(dashboard): allow resetting custom domains
- @nhost/react-apollo@6.0.0
- @nhost/nextjs@1.13.39
## 0.20.26
### Patch Changes
- 331ba0376: feat(dashboard): add postgres storage capacity modifier in the settings
- b7f801874: feat(dashboard): add new settings page for custom domains
## 0.20.25
### Patch Changes
- @nhost/react-apollo@5.0.38
## 0.20.24
### Patch Changes
- e10389ecf: fix(dashboard): disable run tab when developing locally
- @nhost/react-apollo@5.0.37
## 0.20.23
### Patch Changes
- c01568a7d: chore(dashboard): show alert to update oauth providers
## 0.20.22
### Patch Changes
- c3efb7ec8: feat(dashboard): query latest announcement from platform
## 0.20.21
### Patch Changes
- 3e46d3873: chore: update link to node18 announcement
## 0.20.20
### Patch Changes
- @nhost/react-apollo@5.0.36
- @nhost/nextjs@1.13.38
## 0.20.19
### Patch Changes
- 75c4c8ae3: feat(dashboard): make env value input multiline
## 0.20.18
### Patch Changes
- 425d485f8: fix(dashboard): make sure dedicated resources pricing follows total resources
## 0.20.17
### Patch Changes
- ae324f67f: fix(dashboard): remove unused graphql fields
## 0.20.16
### Patch Changes
- df5b4302c: chore(dashboard): remove run feature flag
- bf4a1f6c2: feat(dashboard): fetch auth, postgres, hasura and storage versions from dashboard
- 34fc08ca7: fix(dashboard/run): show correct private registry in service details
- 885d10620: chore(dashboard): change feedback to contact us
## 0.20.15
### Patch Changes
- ed16c8b5d: feat(run): add a confirmation dialog when deleting a run service
- 216990888: fix(run): center loading indicator when selecting a project
## 0.20.14
### Patch Changes
- 9fbea9787: feat: add node18 announcement
## 0.20.13
### Patch Changes
- e84acf469: fix(run): handle subdomain undefined error when creating a new service
## 0.20.12
### Patch Changes
- b7c799d62: feat(run): add dialog to copy registry and URLs
## 0.20.11
### Patch Changes
- 8903e6abd: fix(dashboard): show correct egress limit in usage stats
## 0.20.10
### Patch Changes
- 666a75a23: feat(dashboard): add functions execution time and egress volume to usage stats
## 0.20.9
### Patch Changes
- 5e1e80aa8: fix(dashboard): show correct locales in user details
- @nhost/react-apollo@5.0.35
- @nhost/nextjs@1.13.37
## 0.20.8
### Patch Changes
- @nhost/react-apollo@5.0.34
- @nhost/nextjs@1.13.36
## 0.20.7
### Patch Changes
- 4a7ede11e: fix: distinguish files that were not uploaded
- 202b64723: feat(nhost-run): add support for one-click-install run services
- 074a0fa11: feat(dashboard): add settings toggle to enable/disable antivirus
- @nhost/react-apollo@5.0.33
- @nhost/nextjs@1.13.35
## 0.20.6
### Patch Changes
- b20761e97: feat(services): add pricing info and confirmation dialog
- 90df6d81d: fix(services): handle null values when editing a service
- aa8508467: fix: query service logs correctly
feat: enable multiline support for environment value input
## 0.20.5
### Patch Changes
- 8d7f84b8d: fix: make announcement adapt to theme
## 0.20.4
### Patch Changes
- 3b75bfce2: fix: make announcement close properly
- f49819075: fix: show correct values when dedicated resources are disabled
## 0.20.3
### Patch Changes
- e643bd362: fix(services): fix errors when config is null
- bcdab66bf: feat: add annoucement for nhost run
- f967a2e59: added note about storage not being able to be downsized
- 311c7756d: chore(services): consistent naming for compute
## 0.20.2
### Patch Changes
- 9073182d5: chore(dashboard): bump `turbo` to 1.10.11
- ece717d6e: feat(logs): show services in the logs page
- 82b335311: feat(metrics): change grafana link to point to the dashboards
- b135ef695: fix(services): set command as optional and set min replicas to 0
## 0.20.1
### Patch Changes
- 3d5c34f4c: fix(auth): fix users pagination limit
## 0.20.0
### Minor Changes
- c99d117d1: feat(services): add support for custom services
## 0.19.2
### Patch Changes
- face99ccd: chore(deps): bump turbo version
- cfe527307: style: tweak pull config warning in dark mode
- a9d7da8af: chore(deps): update dependency @types/pluralize to ^0.0.30
- 9aa4371ef: chore: add hasura-auth version 0.21.2
- d14e112bf: chore(deps): update dependency prettier-plugin-tailwindcss to ^0.4.0
- d3e8bb94a: chore(deps): update dependency vite-plugin-dts to v3
## 0.19.1
### Patch Changes
- @nhost/react-apollo@5.0.32
- @nhost/nextjs@1.13.34
## 0.19.0
### Minor Changes
- 9c61c69a7: chore(dashboard):add postgres 14.6-20230705-1 to the version selector
### Patch Changes
- 47bda15ff: feat(settings): add warning to pull config
## 0.18.0
### Minor Changes
- ee0b9b8ed: chore(dashboard):add hasura v2.28.2 and v2.29.0 to the version selector
## 0.17.20
### Patch Changes

View File

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

View File

@@ -30,7 +30,7 @@ test('should show a sidebar with menu items', async () => {
const navLocator = page.getByRole('navigation', { name: /main navigation/i });
await expect(navLocator).toBeVisible();
await expect(navLocator.getByRole('list').getByRole('listitem')).toHaveCount(
11,
12,
);
await expect(
navLocator.getByRole('link', { name: /overview/i }),

View File

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

View File

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

5
dashboard/hypertune.json Normal file
View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "0.17.20",
"version": "0.20.27",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -54,6 +54,7 @@
"graphql-request": "^6.0.0",
"graphql-tag": "^2.12.6",
"graphql-ws": "^5.11.2",
"hypertune": "^1.4.4",
"just-kebab-case": "^4.1.1",
"lodash.debounce": "^4.0.8",
"next": "^12.3.1",
@@ -65,12 +66,14 @@
"react-error-boundary": "^4.0.0",
"react-hook-form": "^7.42.1",
"react-hot-toast": "^2.4.0",
"react-intersection-observer": "^9.5.2",
"react-is": "18.2.0",
"react-loading-skeleton": "^2.2.0",
"react-merge-refs": "^1.1.0",
"react-syntax-highlighter": "^15.4.5",
"react-table": "^7.8.0",
"sharp": "^0.32.0",
"shell-quote": "^1.8.1",
"slugify": "^1.6.5",
"stripe": "^10.17.0",
"tailwind-merge": "^1.8.0",
@@ -101,13 +104,16 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/ace": "^0.0.48",
"@types/bcryptjs": "^2.4.2",
"@types/jest": "^29.5.3",
"@types/lodash.debounce": "^4.0.7",
"@types/node": "^16.11.7",
"@types/pluralize": "^0.0.29",
"@types/pluralize": "^0.0.30",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@types/react-table": "^7.7.12",
"@types/shell-quote": "^1.7.1",
"@types/testing-library__jest-dom": "^5.14.5",
"@types/validator": "^13.7.10",
"@typescript-eslint/eslint-plugin": "^5.43.0",
@@ -137,7 +143,7 @@
"postcss": "^8.4.19",
"prettier": "^2.7.1",
"prettier-plugin-organize-imports": "^3.2.0",
"prettier-plugin-tailwindcss": "^0.3.0",
"prettier-plugin-tailwindcss": "^0.4.0",
"react-date-fns-hooks": "^0.9.4",
"require-from-string": "^2.0.2",
"snake-case": "^3.0.4",

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -0,0 +1,70 @@
import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text';
import type { DetailedHTMLProps, HTMLProps } from 'react';
import { twMerge } from 'tailwind-merge';
export interface ContactUsProps
extends DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> {}
export default function FeedbackForm({ className, ...props }: ContactUsProps) {
return (
<div
className={twMerge(
'grid max-w-md grid-flow-row gap-2 py-4 px-5',
className,
)}
{...props}
>
<Text variant="h3" component="h2">
Contact us
</Text>
<Text>
To report issues with Nhost, please open a GitHub issue in the{' '}
<Link
href="https://github.com/nhost/nhost/issues/new"
target="_blank"
rel="noopener noreferrer"
underline="hover"
>
nhost/nhost
</Link>{' '}
repository.
</Text>
<Text>
For issues related to the CLI, please visit the{' '}
<Link
href="https://github.com/nhost/cli/issues/new"
target="_blank"
rel="noopener noreferrer"
underline="hover"
>
nhost/cli
</Link>{' '}
repository.
</Text>
<Text>
If you need assistance or have any questions, feel free to join us on{' '}
<Link
href="https://discord.com/invite/9V7Qb2U"
target="_blank"
rel="noopener noreferrer"
underline="hover"
>
Discord
</Link>
. Alternatively, if you prefer, you can also open a{' '}
<Link
href="https://github.com/nhost/nhost/discussions/new/choose"
target="_blank"
rel="noopener noreferrer"
underline="hover"
>
GitHub discussion
</Link>
.
</Text>
<Text>We&apos;re here to help, so don&apos;t hesitate to reach out!</Text>
</div>
);
}

View File

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

View File

@@ -0,0 +1,28 @@
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>
)
);
}

View File

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

View File

@@ -1,148 +0,0 @@
import { Avatar } from '@/components/ui/v2/Avatar';
import { Button } from '@/components/ui/v2/Button';
import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useInsertFeedbackOneMutation } from '@/utils/__generated__/graphql';
import { useUserData } from '@nhost/nextjs';
import Image from 'next/image';
import type { DetailedHTMLProps, HTMLProps } from 'react';
import { useState } from 'react';
import { twMerge } from 'tailwind-merge';
export interface FeedbackFormProps
extends DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> {}
// TODO: Use `react-hook-form` here instead of the custom form implementation
export default function FeedbackForm({
className,
...props
}: FeedbackFormProps) {
const { currentProject } = useCurrentWorkspaceAndProject();
const [insertFeedback, { loading }] = useInsertFeedbackOneMutation();
const user = useUserData();
const [feedback, setFeedback] = useState('');
const [feedbackSent, setFeedbackSent] = useState(false);
function handleClose() {
setTimeout(() => {
setFeedbackSent(false);
}, 500);
}
async function handleSubmit(e: React.SyntheticEvent<HTMLFormElement>) {
e.preventDefault();
const feedbackWithProjectInfo = [
currentProject && `Project ID: ${currentProject.id}`,
typeof window !== 'undefined' && `URL: ${window.location.href}`,
feedback,
]
.filter(Boolean)
.join('\n\n');
try {
await insertFeedback({
variables: {
feedback: {
feedback: feedbackWithProjectInfo,
},
},
});
setFeedbackSent(true);
setFeedback('');
} catch (error) {
// TODO: Display error to user and use a logging solution
}
}
if (feedbackSent) {
return (
<div
className={twMerge(
'grid max-w-md grid-flow-row justify-center gap-4 py-4 px-5 text-center',
className,
)}
{...props}
>
<Image
src="/assets/FeedbackReceived.svg"
alt="Light bulb with a checkmark"
width={72}
height={72}
/>
<div className="grid grid-flow-row gap-2">
<Text variant="h3" component="h2" className="text-center">
Feedback Received
</Text>
<Text>
Thanks for sending us your thoughts! Feel free to send more feedback
as you explore the beta, and stay tuned for updates.
</Text>
</div>
<Button
variant="outlined"
color="secondary"
className="mt-2 text-sm+ font-normal"
onClick={handleClose}
>
Go Back
</Button>
</div>
);
}
return (
<div
className={twMerge(
'grid max-w-md grid-flow-row gap-2 py-4 px-5',
className,
)}
{...props}
>
<Text variant="h3" component="h2">
Leave Feedback
</Text>
<Text>
Nhost is still in beta and not everything is in place yet, but we&apos;d
love to know what you think of it so far.
</Text>
<form onSubmit={handleSubmit} className="grid grid-flow-row gap-2">
<div className="grid grid-flow-col place-content-between gap-2">
<Text className="font-medium">
What do you think we should improve?
</Text>
<Avatar
className="h-6 w-6 rounded-full"
alt={user?.displayName}
src={user?.avatarUrl}
>
{user?.displayName}
</Avatar>
</div>
<Input
multiline
value={feedback}
onChange={(event) => setFeedback(event.target.value)}
placeholder="Your feedback"
rows={6}
required
fullWidth
hideEmptyHelperText
/>
<Button type="submit" disabled={!feedback} loading={loading}>
Send Feedback
</Button>
</form>
</div>
);
}

View File

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

View File

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

View File

@@ -0,0 +1,100 @@
import { useDialog } from '@/components/common/DialogProvider';
import { NhostIcon } from '@/components/presentational/NhostIcon';
import { Box } from '@/components/ui/v2/Box';
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 { ChangePlanModal } from '@/features/projects/common/components/ChangePlanModal';
import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCurrentUserOwner';
import Image from 'next/image';
interface UpgradeToProBannerProps {
title: string;
description: string;
}
export default function UpgradeToProBanner({
title,
description,
}: UpgradeToProBannerProps) {
const { openDialog, openAlertDialog } = useDialog();
const isOwner = useIsCurrentUserOwner();
return (
<Box
sx={{ backgroundColor: 'primary.light' }}
className="flex flex-col 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="space-y-2">
<div className="flex flex-col space-y-2 xs:flex-row xs:space-y-0 xs:space-x-2">
<Text>Available with</Text>
<div className="flex flex-row space-x-2">
<NhostIcon />
<Text sx={{ color: 'primary.main' }} className="font-semibold">
Nhost Pro
</Text>
</div>
</div>
<Text variant="h3">{title}</Text>
<Text>{description}</Text>
</div>
<div className="flex flex-col space-y-2 lg:flex-row lg:items-center lg:space-y-0 lg:space-x-2">
<Button
className="rounded-md"
onClick={() => {
if (isOwner) {
openDialog({
component: <ChangePlanModal />,
props: {
PaperProps: { className: 'p-0 max-w-xl w-full' },
},
});
} else {
openAlertDialog({
title: "You can't upgrade this project",
payload: (
<Text variant="subtitle1" component="span">
Ask an owner of this workspace to upgrade the project.
</Text>
),
props: {
secondaryButtonText: 'I understand',
hidePrimaryAction: true,
},
});
}
}}
>
Upgrade to Pro
</Button>
<Link
href="https://nhost.io/pricing"
target="_blank"
rel="noopener noreferrer"
underline="hover"
className="font-medium text-center"
sx={{
color: 'text.secondary',
}}
>
See all features
<ArrowSquareOutIcon className="w-4 h-4 ml-1" />
</Link>
</div>
</div>
<div className="max-w-xs mx-auto">
<Image
src="/illustration-unbox.png"
width={400}
height={260}
objectFit="contain"
/>
</div>
</Box>
);
}

View File

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

View File

@@ -178,6 +178,22 @@ export default function DataGridBody<T extends object>({
}
}
const getBackgroundCellColor = (
row: Row<T>,
column: DataBrowserGridColumn<T>,
) => {
// Grey out files not uploaded
if (!row.values.isUploaded) {
return 'grey.200';
}
if (column.isDisabled) {
return 'grey.100';
}
return 'background.paper';
};
return (
<div {...getTableBodyProps()} ref={bodyRef} {...props}>
{rows.length === 0 && !loading && (
@@ -260,9 +276,7 @@ export default function DataGridBody<T extends object>({
})}
cell={cell}
sx={{
backgroundColor: column.isDisabled
? 'grey.100'
: 'background.paper',
backgroundColor: getBackgroundCellColor(row, column),
color: isCellDisabled ? 'text.secondary' : 'text.primary',
}}
className={twMerge(

View File

@@ -1,4 +1,4 @@
import { FeedbackForm } from '@/components/common/FeedbackForm';
import { ContactUs } from '@/components/common/ContactUs';
import { NavLink } from '@/components/common/NavLink';
import { AccountMenu } from '@/components/layout/AccountMenu';
import { Breadcrumbs } from '@/components/layout/Breadcrumbs';
@@ -54,7 +54,7 @@ export default function Header({ className, ...props }: HeaderProps) {
sx={{ backgroundColor: 'background.paper' }}
{...props}
>
<div className="grid grid-flow-col items-center gap-3">
<div className="grid grid-flow-col items-center gap-3 ">
<NavLink href="/" className="w-12">
<Logo className="mx-auto cursor-pointer" />
</NavLink>
@@ -75,14 +75,14 @@ export default function Header({ className, ...props }: HeaderProps) {
hideChevron
className="rounded-md px-2.5 py-1.5 text-sm motion-safe:transition-colors"
>
Feedback
Contact us
</Dropdown.Trigger>
<Dropdown.Content
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
>
<FeedbackForm className="max-w-md" />
<ContactUs className="max-w-md" />
</Dropdown.Content>
</Dropdown.Root>
)}

View File

@@ -1,4 +1,4 @@
import { FeedbackForm } from '@/components/common/FeedbackForm';
import { ContactUs } from '@/components/common/ContactUs';
import { NavLink } from '@/components/common/NavLink';
import { ThemeSwitcher } from '@/components/common/ThemeSwitcher';
import { Nav } from '@/components/presentational/Nav';
@@ -171,7 +171,7 @@ export default function MobileNav({ className, ...props }: MobileNavProps) {
className="w-full"
role={undefined}
>
<ListItem.Text>Feedback</ListItem.Text>
<ListItem.Text>Contact us</ListItem.Text>
</ListItem.Button>
</ListItem.Root>
</Dropdown.Trigger>
@@ -180,7 +180,7 @@ export default function MobileNav({ className, ...props }: MobileNavProps) {
transformOrigin={{ vertical: 'top', horizontal: 'center' }}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
<FeedbackForm className="max-w-md" />
<ContactUs className="max-w-md" />
</Dropdown.Content>
</Dropdown.Root>
)}

View File

@@ -114,7 +114,7 @@ export default function SettingsContainer({
<Box
{...root}
className={twMerge(
'grid grid-flow-row gap-4 rounded-lg border-1 py-4',
'grid grid-flow-row gap-4 overflow-hidden rounded-lg border-1 py-4',
root?.className || rootClassName,
)}
>
@@ -128,7 +128,11 @@ export default function SettingsContainer({
icon}
<div className="grid grid-flow-row gap-1">
<Text className="text-lg font-semibold">{title}</Text>
{typeof title === 'string' ? (
<Text className="text-lg font-semibold">{title}</Text>
) : (
title
)}
{description && <Text color="secondary">{description}</Text>}
</div>

View File

@@ -1,9 +1,14 @@
import DepricationNotice from '@/components/common/DepricationNotice/DepricationNotice';
import type { ProjectLayoutProps } from '@/components/layout/ProjectLayout';
import { ProjectLayout } from '@/components/layout/ProjectLayout';
import type { SettingsSidebarProps } from '@/components/layout/SettingsSidebar';
import { SettingsSidebar } from '@/components/layout/SettingsSidebar';
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
import { Alert } from '@/components/ui/v2/Alert';
import { Box } from '@/components/ui/v2/Box';
import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useTheme } from '@mui/material';
import { twMerge } from 'tailwind-merge';
export interface SettingsLayoutProps extends ProjectLayoutProps {
@@ -22,6 +27,10 @@ export default function SettingsLayout({
sidebarProps: { className: sidebarClassName, ...sidebarProps } = {},
...props
}: SettingsLayoutProps) {
const theme = useTheme();
const { currentProject } = useCurrentWorkspaceAndProject();
const hasGitRepo = !!currentProject?.githubRepository;
return (
<ProjectLayout
mainContainerProps={{
@@ -37,9 +46,49 @@ export default function SettingsLayout({
<Box
sx={{ backgroundColor: 'background.default' }}
className="flex w-full flex-auto flex-col overflow-x-hidden"
className="flex w-full flex-auto flex-col overflow-scroll overflow-x-hidden"
>
<RetryableErrorBoundary>{children}</RetryableErrorBoundary>
<RetryableErrorBoundary>
<div className="flex flex-col space-y-2">
<DepricationNotice />
{hasGitRepo && (
<Alert
severity="warning"
className="grid grid-flow-row place-content-center gap-2"
>
<Text color="warning" className="text-sm ">
As you have a connected repository, make sure to synchronize
your changes with{' '}
<code
className={twMerge(
'rounded-md px-2 py-px',
theme.palette.mode === 'dark'
? 'bg-brown text-copper'
: 'bg-slate-200 text-slate-700',
)}
>
nhost config pull
</code>{' '}
or they may be reverted with the next push.
<br />
If there are multiple projects linked to the same repository
and you only want these changes to apply to a subset of them,
please check out{' '}
<a
target="_blank"
rel="noopener noreferrer"
className="underline"
href="https://docs.nhost.io/cli/overlays"
>
docs.nhost.io/cli/overlays
</a>{' '}
for guidance.
</Text>
</Alert>
)}
</div>
{children}
</RetryableErrorBoundary>
</Box>
</ProjectLayout>
);

View File

@@ -200,6 +200,14 @@ export default function SettingsSidebar({
>
Secrets
</SettingsNavLink>
<SettingsNavLink
href="/custom-domains"
exact={false}
onClick={handleSelect}
>
Custom Domains
</SettingsNavLink>
</List>
</nav>
</Box>

View File

@@ -0,0 +1,35 @@
import type { ForwardedRef, SVGProps } from 'react';
import { forwardRef } from 'react';
function NhostIcon(
props: SVGProps<SVGSVGElement>,
ref: ForwardedRef<SVGSVGElement>,
) {
return (
<svg
ref={ref}
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-label="Logo of Nhost"
{...props}
>
<g clipPath="url(#clip0_9802_20458)">
<rect width="24" height="24" fill="#0052CD" />
<path
d="M17.4656 7.39804L12.4705 4.51369C12.0223 4.25553 11.466 4.25553 11.0169 4.51369C10.5688 4.77276 10.2906 5.25455 10.2906 5.77179V6.14813L9.96517 5.95996C9.51702 5.70179 8.96069 5.70179 8.51163 5.95996C8.06348 6.21903 7.78531 6.70082 7.78531 7.21896V7.5953L7.45988 7.40713C7.01173 7.14897 6.4554 7.14897 6.00634 7.40713C5.55819 7.66621 5.28003 8.14799 5.28003 8.66614V17.7037C5.28003 17.9637 5.43093 18.2055 5.66546 18.3182C5.89908 18.4318 6.1827 18.4009 6.38632 18.24L8.86342 16.2865L12.6832 18.4918C12.7886 18.5527 12.9068 18.5827 13.025 18.5827C13.1431 18.5827 13.2613 18.5518 13.3668 18.4918C13.5777 18.37 13.7086 18.1437 13.7086 17.9001V12.4613C13.7086 11.5687 13.2286 10.7378 12.4559 10.2915L11.2033 9.56789V5.7727C11.2033 5.57998 11.3069 5.4 11.4742 5.30364C11.6414 5.20728 11.8487 5.20728 12.0159 5.30364L17.0111 8.18708C17.5028 8.4707 17.8083 9.00066 17.8083 9.56789V16.3402C17.8083 16.5329 17.7046 16.7129 17.5374 16.8092L16.2138 17.5737V11.0142C16.2138 10.1215 15.7339 9.29064 14.9612 8.84431L11.8859 7.06897V8.12072L14.5058 9.63334C14.9976 9.91696 15.303 10.446 15.303 11.0142V17.9673C15.303 18.21 15.4339 18.4373 15.6448 18.5591C15.7502 18.62 15.8684 18.65 15.9866 18.65C16.1048 18.65 16.2229 18.6191 16.3284 18.5591L17.9937 17.5974C18.4419 17.3383 18.72 16.8565 18.72 16.3383V9.56608C18.7182 8.67614 18.2382 7.84438 17.4656 7.39804ZM11.9987 11.0805C12.4905 11.3641 12.7959 11.8932 12.7959 12.4613V17.5064L9.63246 15.6802L10.6478 14.8803C10.9996 14.603 11.2014 14.1876 11.2014 13.7394V10.6215L11.9987 11.0805ZM10.2906 10.0942V13.7376C10.2906 13.9049 10.2152 14.0603 10.0842 14.163L6.19088 17.2328V8.66523C6.19088 8.47251 6.29451 8.29253 6.46177 8.19617C6.62903 8.09981 6.83629 8.09981 7.00355 8.19617L7.78531 8.64705V15.1057L8.69616 14.3876V7.21896C8.69616 7.02625 8.79979 6.84626 8.96705 6.7499C9.13431 6.65355 9.34157 6.65355 9.50883 6.7499L10.2906 7.20078V9.04157L9.37975 8.51524V9.56789L10.2906 10.0942Z"
fill="white"
/>
</g>
<defs>
<clipPath id="clip0_9802_20458">
<rect width="24" height="24" rx="4" fill="white" />
</clipPath>
</defs>
</svg>
);
}
export default forwardRef(NhostIcon);

View File

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

View File

@@ -0,0 +1,100 @@
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&apos;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&apos;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>
);
}

View File

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

View File

@@ -7,14 +7,15 @@ import MaterialLinearProgress, {
export interface LinearProgressProps extends MaterialLinearProgressProps {}
const LinearProgress = styled(MaterialLinearProgress)(({ theme }) => ({
const LinearProgress = styled(MaterialLinearProgress)(({ theme, value }) => ({
height: 12,
borderRadius: 1,
[`&.${linearProgressClasses.colorPrimary}`]: {
backgroundColor: theme.palette.grey[300],
},
[`& .${linearProgressClasses.bar}`]: {
backgroundColor: theme.palette.primary.main,
backgroundColor:
value >= 100 ? theme.palette.error.dark : theme.palette.primary.main,
},
}));

View File

@@ -0,0 +1,44 @@
import type { IconProps } from '@/components/ui/v2/icons';
function ArrowsClockwise(props: IconProps) {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
aria-label="Update"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M11.0103 6.23227H14.0103V3.23227"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
<path
d="M4.11084 4.11091C4.62156 3.60019 5.22788 3.19506 5.89517 2.91866C6.56246 2.64226 7.27766 2.5 7.99993 2.5C8.7222 2.5 9.4374 2.64226 10.1047 2.91866C10.772 3.19506 11.3783 3.60019 11.889 4.11091L14.0103 6.23223"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
<path
d="M4.98975 9.76773H1.98975V12.7677"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
<path
d="M11.8892 11.8891C11.3785 12.3998 10.7722 12.8049 10.1049 13.0813C9.43762 13.3577 8.72242 13.5 8.00015 13.5C7.27788 13.5 6.56269 13.3577 5.89539 13.0813C5.2281 12.8049 4.62179 12.3998 4.11107 11.8891L1.98975 9.76776"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
</svg>
);
}
ArrowsClockwise.displayName = 'NhostArrowsClockwise';
export default ArrowsClockwise;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,9 @@ import { filterOptions } from '@/components/ui/v2/Autocomplete';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import {
GetAuthenticationSettingsDocument,
Software_Type_Enum,
useGetAuthenticationSettingsQuery,
useGetSoftwareVersionsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { getToastStyleProps } from '@/utils/constants/settings';
@@ -28,14 +30,6 @@ export type AuthServiceVersionFormValues = Yup.InferType<
typeof validationSchema
>;
const AVAILABLE_AUTH_VERSIONS = [
'0.20.1',
'0.20.0',
'0.19.3',
'0.19.2',
'0.19.1',
];
export default function AuthServiceVersionSettings() {
const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject();
@@ -48,9 +42,16 @@ export default function AuthServiceVersionSettings() {
fetchPolicy: 'cache-only',
});
const { data: authVersionsData } = useGetSoftwareVersionsQuery({
variables: {
software: Software_Type_Enum.Auth,
},
});
const { version } = data?.config?.auth || {};
const versions = authVersionsData?.softwareVersions || [];
const availableVersions = Array.from(
new Set(AVAILABLE_AUTH_VERSIONS).add(version),
new Set(versions.map((el) => el.version)).add(version),
)
.sort()
.reverse()

View File

@@ -24,6 +24,13 @@ query GetAuthenticationSettings($appId: uuid!) {
expiresIn
}
}
resources {
networking {
ingresses {
fqdn
}
}
}
user {
email {
allowed
@@ -38,6 +45,10 @@ query GetAuthenticationSettings($appId: uuid!) {
default
rating
}
locale {
allowed
default
}
}
version
}

View File

@@ -24,6 +24,7 @@ import { copy } from '@/utils/copy';
import { getServerError } from '@/utils/getServerError';
import {
RemoteAppGetUsersDocument,
useGetProjectLocalesQuery,
useGetRolesPermissionsQuery,
useUpdateRemoteAppUserMutation,
} from '@/utils/__generated__/graphql';
@@ -146,6 +147,14 @@ export default function EditUserForm({
dataRoles?.config?.auth?.user?.roles?.allowed,
);
const { data } = useGetProjectLocalesQuery({
variables: {
appId: currentProject?.id,
},
});
const allowedLocales = data?.config?.auth?.user?.locale?.allowed || [];
/**
* This will change the `disabled` field in the user to its opposite.
* If the user is disabled, it will be enabled and vice versa.
@@ -374,12 +383,11 @@ export default function EditUserForm({
error={!!errors.locale}
helperText={errors?.locale?.message}
>
<Option key="en" value="en">
en
</Option>
<Option key="fr" value="fr">
fr
</Option>
{allowedLocales.map((locale) => (
<Option key={locale} value={locale}>
{locale}
</Option>
))}
</ControlledSelect>
</Box>
<Box

View File

@@ -2,8 +2,9 @@ import permissionVariablesQuery from '@/tests/msw/mocks/graphql/permissionVariab
import hasuraMetadataQuery from '@/tests/msw/mocks/rest/hasuraMetadataQuery';
import tableQuery from '@/tests/msw/mocks/rest/tableQuery';
import { render, screen } from '@/tests/testUtils';
import '@testing-library/jest-dom';
import { setupServer } from 'msw/node';
import { test, vi } from 'vitest';
import { afterAll, afterEach, beforeAll, test, vi } from 'vitest';
import ColumnAutocomplete from './ColumnAutocomplete';
const server = setupServer(

View File

@@ -7,7 +7,9 @@ import { filterOptions } from '@/components/ui/v2/Autocomplete';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import {
GetPostgresSettingsDocument,
Software_Type_Enum,
useGetPostgresSettingsQuery,
useGetSoftwareVersionsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { getToastStyleProps } from '@/utils/constants/settings';
@@ -30,14 +32,6 @@ export type DatabaseServiceVersionFormValues = Yup.InferType<
typeof validationSchema
>;
const AVAILABLE_POSTGRES_VERSIONS = [
'14.6-20230613-1',
'14.6-20230525',
'14.6-20230406-2',
'14.6-20230406-1',
'14.6-20230404',
];
export default function DatabaseServiceVersionSettings() {
const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject();
@@ -50,9 +44,16 @@ export default function DatabaseServiceVersionSettings() {
fetchPolicy: 'cache-only',
});
const { data: databaseVersionsData } = useGetSoftwareVersionsQuery({
variables: {
software: Software_Type_Enum.PostgreSql,
},
});
const { version } = data?.config?.postgres || {};
const databaseVersions = databaseVersionsData?.softwareVersions || [];
const availableVersions = Array.from(
new Set(AVAILABLE_POSTGRES_VERSIONS).add(version),
new Set(databaseVersions.map((el) => el.version)).add(version),
)
.sort()
.reverse()

View File

@@ -0,0 +1,161 @@
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Box } from '@/components/ui/v2/Box';
import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import {
useGetPostgresSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { getToastStyleProps } from '@/utils/constants/settings';
import { getServerError } from '@/utils/getServerError';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import * as Yup from 'yup';
const validationSchema = Yup.object({
capacity: Yup.number().required(),
});
export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function AuthDomain() {
const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject();
const {
data,
loading,
error,
refetch: refetchPostgresSettings,
} = useGetPostgresSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
});
const capacity =
data?.config?.postgres?.resources?.storage?.capacity ??
currentProject.plan.featureMaxDbSize;
const [updateConfig] = useUpdateConfigMutation();
const form = useForm<{ capacity: number }>({
reValidateMode: 'onSubmit',
defaultValues: { capacity },
resolver: yupResolver(validationSchema),
});
const { formState, register, reset } = form;
const isDirty = Object.keys(formState.dirtyFields).length > 0;
useEffect(() => {
if (data && !loading) {
reset({ capacity });
}
}, [loading, data, reset, capacity]);
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading Auth Domain Settings..."
className="justify-center"
/>
);
}
if (error) {
throw error;
}
async function handleSubmit(formValues: AuthDomainFormValues) {
try {
await toast.promise(
updateConfig({
variables: {
appId: currentProject.id,
config: {
postgres: {
resources: {
storage: {
capacity: formValues.capacity,
},
},
},
},
},
}),
{
loading: `Database storage capacity is being updated...`,
success: `Database storage capacity has been updated successfully.`,
error: getServerError(
`An error occurred while trying to update the database storage capacity.`,
),
},
getToastStyleProps(),
);
form.reset(formValues);
await refetchPostgresSettings();
} catch {
// Note: The toast will handle the error.
}
}
return (
<FormProvider {...form}>
<Form onSubmit={handleSubmit}>
<SettingsContainer
title="Storage capacity"
description="Specify the storage capacity for your PostgreSQL database."
slotProps={{
submitButton: {
disabled: !isDirty || maintenanceActive,
loading: formState.isSubmitting,
},
}}
className="flex flex-col"
>
{currentProject.plan.isFree && (
<UpgradeNotification message="Unlock by upgrading your project to the Pro plan." />
)}
<Box className="grid grid-flow-row lg:grid-cols-5">
<Input
{...register('capacity')}
id="capacity"
name="capacity"
type="number"
fullWidth
disabled={currentProject.plan.isFree}
className="lg:col-span-2"
error={Boolean(formState.errors.capacity?.message)}
helperText={formState.errors.capacity?.message}
slotProps={{
inputRoot: {
min: capacity,
},
}}
/>
</Box>
{!currentProject.plan.isFree && (
<Box
className="grid items-center grid-flow-col gap-1 p-3 rounded-lg shadow-sm place-content-between"
sx={{ backgroundColor: 'grey.200' }}
>
<Text>
Please note that once you increase the storage, it cannot be
reduced.
</Text>
</Box>
)}
</SettingsContainer>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -9,6 +9,11 @@ query GetPostgresSettings($appId: uuid!) {
__typename
postgres {
version
resources {
storage {
capacity
}
}
}
}
}

View File

@@ -7,7 +7,9 @@ import { filterOptions } from '@/components/ui/v2/Autocomplete';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import {
GetHasuraSettingsDocument,
Software_Type_Enum,
useGetHasuraSettingsQuery,
useGetSoftwareVersionsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { getToastStyleProps } from '@/utils/constants/settings';
@@ -30,14 +32,6 @@ export type HasuraServiceVersionFormValues = Yup.InferType<
typeof validationSchema
>;
const AVAILABLE_HASURA_VERSIONS = [
'v2.27.0-ce',
'v2.25.1-ce',
'v2.25.0-ce',
'v2.24.1-ce',
'v2.15.2',
];
export default function HasuraServiceVersionSettings() {
const { maintenanceActive } = useUI();
const { currentProject, refetch: refetchWorkspaceAndProject } =
@@ -51,9 +45,16 @@ export default function HasuraServiceVersionSettings() {
fetchPolicy: 'cache-only',
});
const { data: hasuraVersionsData } = useGetSoftwareVersionsQuery({
variables: {
software: Software_Type_Enum.Hasura,
},
});
const { version } = data?.config?.hasura || {};
const versions = hasuraVersionsData?.softwareVersions || [];
const availableVersions = Array.from(
new Set(AVAILABLE_HASURA_VERSIONS).add(version),
new Set(versions.map((el) => el.version)).add(version),
)
.sort()
.reverse()

View File

@@ -18,6 +18,13 @@ query GetHasuraSettings($appId: uuid!) {
events {
httpPoolSize
}
resources {
networking {
ingresses {
fqdn
}
}
}
}
}
}

View File

@@ -0,0 +1,47 @@
import { List } from '@/components/ui/v2/List';
import { ListItem } from '@/components/ui/v2/ListItem';
import { Text } from '@/components/ui/v2/Text';
import { useGetAnnouncementsQuery } from '@/utils/__generated__/graphql';
import formatDistance from 'date-fns/formatDistance';
export default function Announcements() {
const { data, loading, error } = useGetAnnouncementsQuery();
const announcements = data?.announcements || [];
if (loading || error) {
return null;
}
return (
<section>
<Text color="secondary" className="mb-2">
Latest announcements
</Text>
<List className="relative space-y-4 border-l border-gray-200 dark:border-gray-700">
{announcements.map((item) => (
<ListItem.Root key={item.id} className="ml-4">
<div className="flex flex-col">
<time className="mb-1 text-sm font-normal leading-none text-gray-400 dark:text-gray-500">
{formatDistance(new Date(item.createdAt), new Date(), {
addSuffix: true,
})}
</time>
<a href={item.href} target="_blank" rel="noopener noreferrer">
<ListItem.Button
dense
aria-label={`View ${item.content}`}
className="!p-1"
>
<p className="text-sm">{item.content}</p>
</ListItem.Button>
</a>
</div>
<div className="absolute top-[0.15rem] -ml-[1.4rem] h-3 w-3 rounded-full border border-white bg-gray-200 dark:border-gray-900 dark:bg-gray-700" />
</ListItem.Root>
))}
</List>
</section>
);
}

View File

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

View File

@@ -1,4 +1,4 @@
import { FeedbackForm } from '@/components/common/FeedbackForm';
import { ContactUs } from '@/components/common/ContactUs';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Button } from '@/components/ui/v2/Button';
import { Dropdown } from '@/components/ui/v2/Dropdown';
@@ -99,7 +99,7 @@ export default function AppLoader({
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
transformOrigin={{ vertical: 'top', horizontal: 'center' }}
>
<FeedbackForm />
<ContactUs />
</Dropdown.Content>
</Dropdown.Root>
)}

View File

@@ -1,4 +1,4 @@
import { FeedbackForm } from '@/components/common/FeedbackForm';
import { ContactUs } from '@/components/common/ContactUs';
import { Container } from '@/components/layout/Container';
import { Modal } from '@/components/ui/v1/Modal';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
@@ -250,7 +250,7 @@ export default function ApplicationErrored() {
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
transformOrigin={{ vertical: 'top', horizontal: 'center' }}
>
<FeedbackForm />
<ContactUs />
</Dropdown.Content>
</Dropdown.Root>

View File

@@ -1,4 +1,4 @@
import { FeedbackForm } from '@/components/common/FeedbackForm';
import { ContactUs } from '@/components/common/ContactUs';
import { Container } from '@/components/layout/Container';
import { Modal } from '@/components/ui/v1/Modal';
import { Button } from '@/components/ui/v2/Button';
@@ -65,7 +65,7 @@ export default function ApplicationUnknown() {
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
transformOrigin={{ vertical: 'top', horizontal: 'center' }}
>
<FeedbackForm />
<ContactUs />
</Dropdown.Content>
</Dropdown.Root>

View File

@@ -0,0 +1,116 @@
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Checkbox } from '@/components/ui/v2/Checkbox';
import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { getToastStyleProps } from '@/utils/constants/settings';
import {
useDeleteRunServiceConfigMutation,
useDeleteRunServiceMutation,
} from '@/utils/__generated__/graphql';
import type { ApolloError } from '@apollo/client';
import { type RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
import { useState } from 'react';
import toast from 'react-hot-toast';
import { twMerge } from 'tailwind-merge';
export interface DeleteServiceModalProps {
service: RunService;
onDelete?: () => Promise<any>;
close: () => void;
}
export default function DeleteServiceModal({
service,
onDelete,
close,
}: DeleteServiceModalProps) {
const [remove, setRemove] = useState(false);
const [loadingRemove, setLoadingRemove] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject();
const [deleteRunService] = useDeleteRunServiceMutation();
const [deleteRunServiceConfig] = useDeleteRunServiceConfigMutation();
const deleteServiceAndConfig = async () => {
await deleteRunService({ variables: { serviceID: service.id } });
await deleteRunServiceConfig({
variables: { appID: currentProject.id, serviceID: service.id },
});
await onDelete?.();
close();
};
async function handleClick() {
setLoadingRemove(true);
await toast.promise(
deleteServiceAndConfig(),
{
loading: 'Deleting the service...',
success: `The service has been deleted successfully.`,
error: (arg: ApolloError) => {
// we need to get the internal error message from the GraphQL error
const { internal } = arg.graphQLErrors[0]?.extensions || {};
const { message } = (internal as Record<string, any>)?.error || {};
// we use the default Apollo error message if we can't find the
// internal error message
return (
message ||
arg.message ||
'An error occurred while deleting the service. Please try again.'
);
},
},
getToastStyleProps(),
);
}
return (
<Box className={twMerge('w-full rounded-lg p-6 text-left')}>
<div className="grid grid-flow-row gap-1">
<Text variant="h3" component="h2">
Delete Service {service?.config?.name}
</Text>
<Text variant="subtitle2">
Are you sure you want to delete this service?
</Text>
<Text
variant="subtitle2"
className="font-bold"
sx={{ color: (theme) => `${theme.palette.error.main} !important` }}
>
This cannot be undone.
</Text>
<Box className="my-4">
<Checkbox
id="accept-1"
label={`I'm sure I want to delete ${service?.config?.name}`}
className="py-2"
checked={remove}
onChange={(_event, checked) => setRemove(checked)}
aria-label="Confirm Delete Project #1"
/>
</Box>
<div className="grid grid-flow-row gap-2">
<Button
color="error"
onClick={handleClick}
disabled={!remove}
loading={loadingRemove}
>
Delete Service
</Button>
<Button variant="outlined" color="secondary" onClick={close}>
Cancel
</Button>
</div>
</div>
</Box>
);
}

View File

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

View File

@@ -8,6 +8,7 @@ import { PlusCircleIcon } from '@/components/ui/v2/icons/PlusCircleIcon';
import { List } from '@/components/ui/v2/List';
import { ListItem } from '@/components/ui/v2/ListItem';
import { Text } from '@/components/ui/v2/Text';
import { Announcements } from '@/features/projects/common/components/Announcements';
import { EditWorkspaceNameForm } from '@/features/projects/workspaces/components/EditWorkspaceNameForm';
import type { Workspace } from '@/types/application';
import Image from 'next/image';
@@ -38,6 +39,8 @@ export default function WorkspaceSidebar({
)}
{...props}
>
<Announcements />
<section className="grid grid-flow-row gap-2">
<Text color="secondary">My Workspaces</Text>

View File

@@ -111,7 +111,6 @@ export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndP
awsName: null,
domain: null,
},
isProvisioned: true,
createdAt: new Date().toISOString(),
desiredState: ApplicationStatus.Live,
featureFlags: [],

View File

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

View File

@@ -0,0 +1,12 @@
import { useEffect, useState } from 'react';
export default function useHostName() {
const [hostName, setHostName] = useState('');
useEffect(() => {
const { port, hostname, protocol } = window.location;
setHostName(`${protocol}//${hostname}:${port}`);
}, []);
return hostName;
}

View File

@@ -8,6 +8,7 @@ import { GraphQLIcon } from '@/components/ui/v2/icons/GraphQLIcon';
import { HasuraIcon } from '@/components/ui/v2/icons/HasuraIcon';
import { HomeIcon } from '@/components/ui/v2/icons/HomeIcon';
import { RocketIcon } from '@/components/ui/v2/icons/RocketIcon';
import { ServicesIcon } from '@/components/ui/v2/icons/ServicesIcon';
import { StorageIcon } from '@/components/ui/v2/icons/StorageIcon';
import type { SvgIconProps } from '@/components/ui/v2/icons/SvgIcon';
import { UserIcon } from '@/components/ui/v2/icons/UserIcon';
@@ -136,6 +137,13 @@ export default function useProjectRoutes() {
label: 'Storage',
icon: <StorageIcon />,
},
{
relativePath: '/services',
exact: false,
label: 'Run',
icon: <ServicesIcon />,
disabled: !isPlatform,
},
...nhostRoutes,
];

View File

@@ -69,7 +69,7 @@ test('should generate a per service subdomain in remote mode', () => {
);
expect(generateAppServiceUrl('test', region, 'grafana')).toBe(
'https://test.grafana.eu-west-1.nhost.run',
'https://test.grafana.eu-west-1.nhost.run/dashboards',
);
});
@@ -102,7 +102,7 @@ test('should generate staging subdomains in staging environment', () => {
);
expect(generateAppServiceUrl('test', stagingRegion, 'grafana')).toBe(
'https://test.grafana.eu-west-1.staging.nhost.run',
'https://test.grafana.eu-west-1.staging.nhost.run/dashboards',
);
});
@@ -120,7 +120,7 @@ test('should generate no slug for Hasura and Grafana neither in local mode nor i
'https://test.hasura.eu-west-1.staging.nhost.run',
);
expect(generateAppServiceUrl('test', stagingRegion, 'grafana')).toBe(
'https://test.grafana.eu-west-1.staging.nhost.run',
'https://test.grafana.eu-west-1.staging.nhost.run/dashboards',
);
process.env.NEXT_PUBLIC_ENV = 'production';
@@ -129,7 +129,7 @@ test('should generate no slug for Hasura and Grafana neither in local mode nor i
'https://test.hasura.eu-west-1.nhost.run',
);
expect(generateAppServiceUrl('test', region, 'grafana')).toBe(
'https://test.grafana.eu-west-1.nhost.run',
'https://test.grafana.eu-west-1.nhost.run/dashboards',
);
});

View File

@@ -102,5 +102,11 @@ export default function generateAppServiceUrl(
.filter(Boolean)
.join('.');
return `https://${constructedDomain}${remoteBackendSlugs[service]}`;
let url = `https://${constructedDomain}${remoteBackendSlugs[service]}`;
if (service === 'grafana') {
url = `${url}/dashboards`;
}
return url;
}

View File

@@ -0,0 +1,153 @@
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain';
import {
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
type ConfigIngressUpdateInput,
} from '@/generated/graphql';
import { getToastStyleProps } from '@/utils/constants/settings';
import { getServerError } from '@/utils/getServerError';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import * as Yup from 'yup';
const validationSchema = Yup.object({
auth_fqdn: Yup.string(),
});
export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function AuthDomain() {
const { maintenanceActive } = useUI();
const [isVerified, setIsVerified] = useState(false);
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation();
const form = useForm<{ auth_fqdn: string }>({
reValidateMode: 'onSubmit',
defaultValues: { auth_fqdn: null },
resolver: yupResolver(validationSchema),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: {
appId: currentProject.id,
},
});
const { networking } = data?.config?.auth?.resources || {};
const initialValue = networking?.ingresses?.[0]?.fqdn?.[0];
useEffect(() => {
if (!loading && data) {
form.reset({ auth_fqdn: initialValue });
}
}, [data, loading, form, initialValue]);
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading Auth Domain Settings..."
className="justify-center"
/>
);
}
if (error) {
throw error;
}
const { formState, register, watch } = form;
const isDirty = Object.keys(formState.dirtyFields).length > 0;
const auth_fqdn = watch('auth_fqdn');
async function handleSubmit(formValues: AuthDomainFormValues) {
const ingresses: ConfigIngressUpdateInput[] =
formValues.auth_fqdn.length > 0 ? [{ fqdn: [formValues.auth_fqdn] }] : [];
const updateConfigPromise = updateConfig({
variables: {
appId: currentProject.id,
config: {
auth: {
resources: {
networking: {
ingresses,
},
},
},
},
},
});
try {
await toast.promise(
updateConfigPromise,
{
loading: `Auth domain is being updated...`,
success: `Auth domain has been updated successfully.`,
error: getServerError(
`An error occurred while trying to update the auth domain.`,
),
},
getToastStyleProps(),
);
form.reset(formValues);
await refetchWorkspaceAndProject();
} catch {
// Note: The toast will handle the error.
}
}
return (
<FormProvider {...form}>
<Form onSubmit={handleSubmit}>
<SettingsContainer
title="Auth Domain"
description="Enter below your custom domain for the authentication service."
slotProps={{
submitButton: {
disabled:
!isDirty || maintenanceActive || (!isVerified && !initialValue),
loading: formState.isSubmitting,
},
}}
className="grid grid-flow-row px-4 gap-y-4 gap-x-4 lg:grid-cols-5"
>
<Input
{...register('auth_fqdn')}
id="auth_fqdn"
name="auth_fqdn"
type="string"
fullWidth
className="col-span-5 lg:col-span-2"
placeholder="auth.mydomain.dev"
error={Boolean(formState.errors.auth_fqdn?.message)}
helperText={formState.errors.auth_fqdn?.message}
slotProps={{ inputRoot: { min: 1, max: 100 } }}
/>
<div className="col-span-5 row-start-2">
<VerifyDomain
recordType="CNAME"
hostname={auth_fqdn}
value={`lb.${currentProject.region.awsName}.${currentProject.region.domain}.`}
onHostNameVerified={() => setIsVerified(true)}
/>
</div>
</SettingsContainer>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,61 @@
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain';
import { useState } from 'react';
import * as Yup from 'yup';
const validationSchema = Yup.object({
database_fqdn: Yup.string().required(),
});
export type DatabaseDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function DatabaseDomain() {
const { currentProject } = useCurrentWorkspaceAndProject();
const [dbFQDN, setDbFQDN] = useState('');
const postgresHost = generateAppServiceUrl(
currentProject.subdomain,
currentProject.region,
'db',
).replace('https://', '');
return (
<SettingsContainer
title="Database Domain"
description="Enter below your custom domain for the PostgreSQL database to verify. Once verified there is no need to save this value as no configuration on our end is required."
slotProps={{
submitButton: {
hidden: true,
},
footer: {
className: 'hidden',
},
}}
className="grid grid-flow-row px-4 gap-y-4 gap-x-4 lg:grid-cols-5"
>
<Input
id="database_fqdn"
name="database_fqdn"
type="string"
fullWidth
className="col-span-5 lg:col-span-2"
placeholder="db.mydomain.dev"
onChange={(e) => {
setDbFQDN(e.target.value);
}}
slotProps={{ inputRoot: { min: 1, max: 100 } }}
/>
<div className="col-span-5 row-start-2">
<VerifyDomain
recordType="CNAME"
hostname={dbFQDN}
value={`${postgresHost}.`}
/>
</div>
</SettingsContainer>
);
}

View File

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

View File

@@ -0,0 +1,155 @@
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain';
import {
useGetHasuraSettingsQuery,
useUpdateConfigMutation,
type ConfigIngressUpdateInput,
} from '@/generated/graphql';
import { getToastStyleProps } from '@/utils/constants/settings';
import { getServerError } from '@/utils/getServerError';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import * as Yup from 'yup';
const validationSchema = Yup.object({
hasura_fqdn: Yup.string(),
});
export type HasuraDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraDomain() {
const { maintenanceActive } = useUI();
const [isVerified, setIsVerified] = useState(false);
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation();
const form = useForm<{ hasura_fqdn: string }>({
reValidateMode: 'onSubmit',
defaultValues: { hasura_fqdn: null },
resolver: yupResolver(validationSchema),
});
const { data, loading, error } = useGetHasuraSettingsQuery({
variables: {
appId: currentProject.id,
},
});
const { networking } = data?.config?.hasura?.resources || {};
const initialValue = networking?.ingresses?.[0]?.fqdn?.[0];
useEffect(() => {
if (!loading && data) {
form.reset({ hasura_fqdn: initialValue });
}
}, [data, loading, form, initialValue]);
if (loading) {
return (
<ActivityIndicator
delay={0}
label="Loading Hasura Domain..."
className="justify-center"
/>
);
}
if (error) {
throw error;
}
const { formState, register, watch } = form;
const isDirty = Object.keys(formState.dirtyFields).length > 0;
const hasura_fqdn = watch('hasura_fqdn');
async function handleSubmit(formValues: HasuraDomainFormValues) {
const ingresses: ConfigIngressUpdateInput[] =
formValues.hasura_fqdn.length > 0
? [{ fqdn: [formValues.hasura_fqdn] }]
: [];
const updateConfigPromise = updateConfig({
variables: {
appId: currentProject.id,
config: {
hasura: {
resources: {
networking: {
ingresses,
},
},
},
},
},
});
try {
await toast.promise(
updateConfigPromise,
{
loading: `Hasura domain is being updated...`,
success: `Hasura domain has been updated successfully.`,
error: getServerError(
`An error occurred while trying to update the Hasura domain.`,
),
},
getToastStyleProps(),
);
form.reset(formValues);
await refetchWorkspaceAndProject();
} catch {
// Note: The toast will handle the error.
}
}
return (
<FormProvider {...form}>
<Form onSubmit={handleSubmit}>
<SettingsContainer
title="Hasura Domain"
description="Enter below your custom domain for the Hasura/GraphQL service."
slotProps={{
submitButton: {
disabled:
!isDirty || maintenanceActive || (!isVerified && !initialValue),
loading: formState.isSubmitting,
},
}}
className="grid grid-flow-row px-4 gap-y-4 gap-x-4 lg:grid-cols-5"
>
<Input
{...register('hasura_fqdn')}
id="hasura_fqdn"
name="hasura_fqdn"
type="string"
fullWidth
className="col-span-5 lg:col-span-2"
placeholder="auth.mydomain.dev"
error={Boolean(formState.errors.hasura_fqdn?.message)}
helperText={formState.errors.hasura_fqdn?.message}
slotProps={{ inputRoot: { min: 1, max: 100 } }}
/>
<div className="col-span-5 row-start-2">
<VerifyDomain
recordType="CNAME"
hostname={hasura_fqdn}
value={`lb.${currentProject.region.awsName}.${currentProject.region.domain}.`}
onHostNameVerified={() => setIsVerified(true)}
/>
</div>
</SettingsContainer>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,88 @@
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
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 { RunServicePortDomain } from '@/features/projects/custom-domains/settings/components/RunServicePortDomain';
import { useGetRunServicesQuery } from '@/utils/__generated__/graphql';
import { useMemo } from 'react';
export default function RunServiceDomains() {
const { currentProject, currentWorkspace } = useCurrentWorkspaceAndProject();
const {
data,
loading,
// refetch: refetchServices, // TODO refetch after update
} = useGetRunServicesQuery({
variables: {
appID: currentProject.id,
resolve: false,
limit: 1000, // TODO consider pagination
offset: 0,
},
});
const services = useMemo(
() => data?.app?.runServices.map((service) => service) ?? [],
[data],
);
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading Run Services Domains..."
className="justify-center"
/>
);
}
return (
<div className="flex flex-col gap-6">
{services
.filter((service) => service.config?.ports?.length > 0)
.map((service) => (
<SettingsContainer
key={service.id}
title={
<div className="flex flex-row items-center">
<Text className="text-lg font-semibold">
{service.config?.name ?? 'unset'}
</Text>
<Link
href={`/${currentWorkspace.slug}/${currentProject.slug}/services`}
target="_blank"
rel="noopener noreferrer"
underline="hover"
className="font-medium"
>
<ArrowSquareOutIcon className="mb-1 ml-1 h-4 w-4" />
</Link>
</div>
}
description="Enter below your custom domain for the published ports."
docsTitle={service.config?.name ?? 'unset'}
slotProps={{
submitButton: {
hidden: true,
},
footer: {
className: 'hidden',
},
}}
className="grid gap-y-4 gap-x-4 px-4"
>
{service.config?.ports?.map((port) => (
<RunServicePortDomain
key={String(port.port)}
service={service}
port={port.port}
/>
))}
</SettingsContainer>
))}
</div>
);
}

View File

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

View File

@@ -0,0 +1,159 @@
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { Button } from '@/components/ui/v2/Button';
import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain';
import { useUpdateRunServiceConfigMutation } from '@/generated/graphql';
import { getToastStyleProps } from '@/utils/constants/settings';
import { getServerError } from '@/utils/getServerError';
import { yupResolver } from '@hookform/resolvers/yup';
import { type RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import * as Yup from 'yup';
interface RunServicePortProps {
service: RunService;
port: number;
}
const validationSchema = Yup.object({
runServicePortFQDN: Yup.string(),
});
export type RunServicePortFormValues = Yup.InferType<typeof validationSchema>;
export default function RunServicePortDomain({
service,
port,
}: RunServicePortProps) {
const { maintenanceActive } = useUI();
const [loading, setLoading] = useState(false);
const [isVerified, setIsVerified] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateRunServiceConfig] = useUpdateRunServiceConfigMutation();
const runServicePort = service.config.ports.find((p) => p.port === port);
const initialValue = runServicePort?.ingresses?.[0]?.fqdn?.[0];
const form = useForm<{ runServicePortFQDN: string }>({
reValidateMode: 'onSubmit',
defaultValues: {
runServicePortFQDN: initialValue,
},
resolver: yupResolver(validationSchema),
});
const { formState, register, watch } = form;
const isDirty = Object.keys(formState.dirtyFields).length > 0;
const runServicePortFQDN = watch('runServicePortFQDN');
async function handleSubmit(formValues: RunServicePortFormValues) {
try {
setLoading(true);
await toast.promise(
updateRunServiceConfig({
variables: {
appID: currentProject.id,
serviceID: service.id,
config: {
ports: service?.config?.ports?.map((p) => {
// exclude the `__typename` because the mutation will fail otherwise
const { __typename, ...rest } = p;
if (rest.port === port) {
return {
...rest,
ingresses:
formValues.runServicePortFQDN.length > 0
? [{ fqdn: [formValues.runServicePortFQDN] }]
: [],
};
}
return {
...rest,
// exclude the `__typename` because the mutation will fail otherwise
ingresses: rest.ingresses.map((item) => ({
fqdn: item.fqdn,
})),
};
}),
},
},
}),
{
loading: `Port ${port} is being updated...`,
success: `Port ${port} has been updated successfully.`,
error: getServerError(
`An error occurred while trying to update Port ${port}.`,
),
},
getToastStyleProps(),
);
form.reset(formValues);
// TODO refetch the service config
// await refetchWorkspaceAndProject();
} catch {
// Note: The toast will handle the error.
} finally {
setLoading(false);
}
}
return (
<FormProvider {...form}>
<Form onSubmit={handleSubmit}>
<div className="space-y-2">
<Text className="text-sm font-semibold">{`${runServicePort.type} <--> ${runServicePort.port}`}</Text>
<div className="flex flex-row space-x-4">
<Input
{...register('runServicePortFQDN')}
id="runServicePortFQDN"
name="runServicePortFQDN"
type="string"
fullWidth
className=""
placeholder={`${service.config?.name ?? 'unset'}-${
runServicePort.port
}.mydomain.dev`}
error={Boolean(formState.errors.runServicePortFQDN?.message)}
helperText={formState.errors.runServicePortFQDN?.message}
slotProps={{
inputRoot: { min: 1, max: 100 },
}}
/>
<Button
variant="outlined"
type="submit"
disabled={
loading ||
!isDirty ||
maintenanceActive ||
(!isVerified && !initialValue)
}
>
Save
</Button>
</div>
</div>
<div className="col-span-5 row-start-2 mt-4">
<VerifyDomain
recordType="CNAME"
hostname={runServicePortFQDN}
value={`lb.${currentProject.region.awsName}.${currentProject.region.domain}.`}
onHostNameVerified={() => setIsVerified(true)}
/>
</div>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,155 @@
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { IconButton } from '@/components/ui/v2/IconButton';
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
import { Text } from '@/components/ui/v2/Text';
import { getToastStyleProps } from '@/utils/constants/settings';
import { copy } from '@/utils/copy';
import { useDnsLookupCnameLazyQuery } from '@/utils/__generated__/graphql';
import { ApolloError } from '@apollo/client';
import { useState } from 'react';
import { toast } from 'react-hot-toast';
interface VerifyDomainProps {
recordType: string;
hostname: string;
value: string;
onHostNameVerified?: () => void;
}
export default function VerifyDomain({
recordType,
hostname,
value,
onHostNameVerified,
}: VerifyDomainProps) {
const [verificationFailed, setVerificationFailed] = useState(false);
const [verificationSucceeded, setVerificationSucceeded] = useState(false);
const [loading, setLoading] = useState(false);
const [fireLookupCNAME] = useDnsLookupCnameLazyQuery();
const handleVerifyDomain = async () => {
setLoading(true);
try {
await toast.promise(
fireLookupCNAME({
variables: {
hostname,
},
}).then(({ data: { dnsLookupCNAME } }) => {
if (dnsLookupCNAME !== value) {
throw new Error(`Could not verify ${hostname}`);
}
}),
{
loading: `Verifying ${hostname} ...`,
success: () => {
setVerificationFailed(false);
setVerificationSucceeded(true);
setLoading(false);
onHostNameVerified?.();
return `${hostname} has been verified.`;
},
error: (arg: Error | ApolloError) => {
setVerificationFailed(true);
setVerificationSucceeded(false);
setLoading(false);
if (arg instanceof ApolloError) {
// we need to get the internal error message from the GraphQL error
const { internal } = arg.graphQLErrors[0]?.extensions || {};
const { message } =
(internal as Record<string, any>)?.error || {};
// we use the default Apollo error message if we can't find the
// internal error message
return (
message ||
arg.message ||
`An error occurred while trying to verify ${hostname}. Please try again.`
);
}
return arg.message;
},
},
getToastStyleProps(),
);
} catch (error) {
// Note: The toast will handle the error.
}
};
return (
<Box
sx={[
{ backgroundColor: 'primary.light' },
verificationFailed && {
backgroundColor: 'error.light',
color: 'error.main',
},
verificationSucceeded && {
backgroundColor: 'success.light',
color: 'success.dark',
},
]}
className="flex flex-col p-4 space-y-4 rounded-md"
>
<div className="flex flex-row items-center justify-between">
{!verificationFailed && !verificationSucceeded && (
<Text>
Add the record below in your DNS provider to verify {hostname}
</Text>
)}
{verificationSucceeded && (
<Text>
<span className="font-semibold">{hostname}</span> was verified
successfully. Hit save to apply.
</Text>
)}
{verificationFailed && (
<Text>
An error occurred while trying to verify{' '}
<span className="font-semibold">{hostname}</span>. Make sure you
correctly added the <span className="font-semibold">CNAME</span> and
try again.
</Text>
)}
</div>
<div className="relative flex flex-col text-slate-500">
<div className="flex space-x-2">
<Text>Record type: </Text>
<Text className="font-bold">{recordType}</Text>
</div>
<div className="flex space-x-2">
<Text>Host:</Text>
<Text className="font-bold">{hostname}</Text>
</div>
<div className="flex flex-row space-x-2">
<Text>Value:</Text>
<Text className="font-bold">{value}</Text>
<IconButton
aria-label="Copy Personal Access Token"
variant="borderless"
color="secondary"
onClick={() => copy(value, 'CNAME Value')}
>
<CopyIcon className="w-4 h-4" />
</IconButton>
</div>
<Button
disabled={loading || !hostname}
onClick={handleVerifyDomain}
className="mt-4 sm:absolute sm:bottom-0 sm:right-0 sm:mt-0"
>
Verify
</Button>
</div>
</Box>
);
}

View File

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

View File

@@ -128,6 +128,8 @@ export default function BaseEnvironmentVariableForm({
error={!!errors.value}
helperText={errors?.value?.message}
fullWidth
multiline
rows={5}
autoComplete="off"
autoFocus={mode === 'edit'}
/>

View File

@@ -10,6 +10,7 @@ import type { LogsCustomInterval } from '@/features/projects/logs/utils/constant
import { LOGS_AVAILABLE_INTERVALS } from '@/features/projects/logs/utils/constants/intervals';
import type { AvailableLogsService } from '@/features/projects/logs/utils/constants/services';
import { LOGS_AVAILABLE_SERVICES } from '@/features/projects/logs/utils/constants/services';
import { useGetRunServicesQuery } from '@/utils/__generated__/graphql';
import { subMinutes } from 'date-fns';
import { useEffect, useState } from 'react';
import { twMerge } from 'tailwind-merge';
@@ -132,6 +133,37 @@ export default function LogsHeader({
const { currentProject } = useCurrentWorkspaceAndProject();
const applicationCreationDate = new Date(currentProject.createdAt);
const [runServices, setRunServices] = useState<
{
label: string;
value: string;
}[]
>([]);
const { data, loading } = useGetRunServicesQuery({
variables: {
appID: currentProject.id,
resolve: false,
limit: 1000,
offset: 0,
},
});
useEffect(() => {
if (!loading) {
const services = data.app?.runServices ?? [];
setRunServices(
services
.filter((s) => !!s.config?.name)
.map((s) => ({
label: s.config.name,
value: `run-${s.config.name}`,
})),
);
}
}, [loading, data]);
/**
* Will subtract the `customInterval` time in minutes from the current date.
*/
@@ -181,15 +213,17 @@ export default function LogsHeader({
root: { className: 'min-h-[initial] h-9 leading-[initial]' },
}}
>
{LOGS_AVAILABLE_SERVICES.map(({ value, label }) => (
<Option
key={value}
value={value}
className="text-sm+ font-medium"
>
{label}
</Option>
))}
{[...LOGS_AVAILABLE_SERVICES, ...runServices].map(
({ value, label }) => (
<Option
key={value}
value={value}
className="text-sm+ font-medium"
>
{label}
</Option>
),
)}
</Select>
</Box>
</Box>

View File

@@ -41,11 +41,6 @@ export default function OverviewMetrics() {
numberOfDecimals: 0,
}),
},
{
label: 'Egress Volume',
tooltip: 'Amount of data your services have sent to users',
value: prettifySize(data?.egressVolume?.value || 0),
},
{
label: 'Logs',
tooltip: 'Amount of logs stored',

View File

@@ -96,7 +96,7 @@ export function OverviewUsageMetrics() {
remoteAppMetricsData?.filesAggregate?.aggregate?.sum?.size || 0;
const totalStorage = currentProject?.plan?.isFree
? 1 * 1000 ** 3 // 1 GB
: 10 * 1000 ** 3; // 10 GB
: 50 * 1000 ** 3; // 10 GB
// metrics for users
const usedUsers = remoteAppMetricsData?.usersAggregate?.aggregate?.count || 0;
@@ -105,6 +105,16 @@ export function OverviewUsageMetrics() {
// metrics for functions
const usedFunctions = functionsInfoData?.app.metadataFunctions.length || 0;
const totalFunctions = currentProject?.plan?.isFree ? 10 : 50;
const usedFunctionsDuration = projectMetrics?.functionsDuration.value || 0;
const totalFunctionsDuration = currentProject?.plan?.isFree
? 3600 // 1 hour
: 3600 * 10; // 10 hours
// metrics for egress
const usedEgressVolume = projectMetrics?.egressVolume.value || 0;
const totalEgressVolume = currentProject?.plan?.isFree
? 5 * 1000 ** 3 // 5 GB
: 50 * 1000 ** 3; // 50 GB
if (metricsLoading) {
return (
@@ -112,7 +122,9 @@ export function OverviewUsageMetrics() {
<UsageProgress label="Database" percentage={0} />
<UsageProgress label="Storage" percentage={0} />
<UsageProgress label="Users" percentage={0} />
<UsageProgress label="Functions" percentage={0} />
<UsageProgress label="Number of Functions" percentage={0} />
<UsageProgress label="Functions Execution Time" percentage={0} />
<UsageProgress label="Egress Volume" percentage={0} />
</div>
);
}
@@ -139,6 +151,18 @@ export function OverviewUsageMetrics() {
used={usedFunctions}
percentage={100}
/>
<UsageProgress
label="Functions"
used={usedFunctionsDuration}
percentage={100}
/>
<UsageProgress
label="Egress"
used={usedEgressVolume}
percentage={100}
/>
</div>
);
}
@@ -167,11 +191,25 @@ export function OverviewUsageMetrics() {
/>
<UsageProgress
label="Functions"
label="Number of Functions"
used={usedFunctions}
total={totalFunctions}
percentage={(usedFunctions / totalFunctions) * 100}
/>
<UsageProgress
label="Functions Execution Time"
used={Math.trunc(usedFunctionsDuration)}
total={`${totalFunctionsDuration} seconds`}
percentage={(usedFunctionsDuration / totalFunctionsDuration) * 100}
/>
<UsageProgress
label="Egress Volume"
used={prettifySize(usedEgressVolume)}
total={prettifySize(totalEgressVolume)}
percentage={(usedEgressVolume / totalEgressVolume) * 100}
/>
</div>
);
}

View File

@@ -1,4 +1,5 @@
import type { PermissionVariable } from '@/types/application';
import { expect, test } from 'vitest';
import getAllPermissionVariables from './getAllPermissionVariables';
test('should convert permission variable object to array', () => {

View File

@@ -68,11 +68,15 @@ export default function ResourcesConfirmationDialog({
const totalBillableVCPU = formValues.enabled ? billableResources.vcpu : 0;
const totalBillableMemory = formValues.enabled ? billableResources.memory : 0;
const updatedPrice =
Math.max(
priceForTotalAvailableVCPU,
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * RESOURCE_VCPU_PRICE,
) + proPlan.price;
const { enabled } = formValues;
const updatedPrice = enabled
? Math.max(
priceForTotalAvailableVCPU,
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) *
RESOURCE_VCPU_PRICE,
) + proPlan.price
: proPlan.price;
if (!loading && !proPlan) {
return (
@@ -86,18 +90,30 @@ export default function ResourcesConfirmationDialog({
throw error;
}
const databaseVCPU = enabled ? formValues.database.vcpu : 0;
const databaseMemory = enabled ? formValues.database.memory : 0;
const hasuraVCPU = enabled ? formValues.hasura.vcpu : 0;
const hasuraMemory = enabled ? formValues.hasura.memory : 0;
const authVCPU = enabled ? formValues.auth.vcpu : 0;
const authMemory = enabled ? formValues.auth.memory : 0;
const storageVCPU = enabled ? formValues.storage.vcpu : 0;
const storageMemory = enabled ? formValues.storage.memory : 0;
const databaseResources = `${prettifyVCPU(
formValues.database.vcpu,
)} vCPU + ${prettifyMemory(formValues.database.memory)}`;
const hasuraResources = `${prettifyVCPU(
formValues.hasura.vcpu,
)} vCPU + ${prettifyMemory(formValues.hasura.memory)}`;
const authResources = `${prettifyVCPU(
formValues.auth.vcpu,
)} vCPU + ${prettifyMemory(formValues.auth.memory)}`;
databaseVCPU,
)} vCPU + ${prettifyMemory(databaseMemory)}`;
const hasuraResources = `${prettifyVCPU(hasuraVCPU)} vCPU + ${prettifyMemory(
hasuraMemory,
)}`;
const authResources = `${prettifyVCPU(authVCPU)} vCPU + ${prettifyMemory(
authMemory,
)}`;
const storageResources = `${prettifyVCPU(
formValues.storage.vcpu,
)} vCPU + ${prettifyMemory(formValues.storage.memory)}`;
storageVCPU,
)} vCPU + ${prettifyMemory(storageMemory)}`;
return (
<div className="grid grid-flow-row gap-6 px-6 pb-6">

View File

@@ -4,7 +4,6 @@ import { ArrowRightIcon } from '@/components/ui/v2/icons/ArrowRightIcon';
import { Slider, sliderClasses } from '@/components/ui/v2/Slider';
import { Text } from '@/components/ui/v2/Text';
import { useProPlan } from '@/features/projects/common/hooks/useProPlan';
import { calculateBillableResources } from '@/features/projects/resources/settings/utils/calculateBillableResources';
import { getAllocatedResources } from '@/features/projects/resources/settings/utils/getAllocatedResources';
import { prettifyMemory } from '@/features/projects/resources/settings/utils/prettifyMemory';
import { prettifyVCPU } from '@/features/projects/resources/settings/utils/prettifyVCPU';
@@ -63,34 +62,7 @@ export default function TotalResourcesFormFragment({
(formValues.totalAvailableVCPU / RESOURCE_VCPU_MULTIPLIER) *
RESOURCE_VCPU_PRICE;
const billableResources = calculateBillableResources(
{
replicas: formValues.database?.replicas,
vcpu: formValues.database?.vcpu,
memory: formValues.database?.memory,
},
{
replicas: formValues.hasura?.replicas,
vcpu: formValues.hasura?.vcpu,
memory: formValues.hasura?.memory,
},
{
replicas: formValues.auth?.replicas,
vcpu: formValues.auth?.vcpu,
memory: formValues.auth?.memory,
},
{
replicas: formValues.storage?.replicas,
vcpu: formValues.storage?.vcpu,
memory: formValues.storage?.memory,
},
);
const updatedPrice =
Math.max(
priceForTotalAvailableVCPU,
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * RESOURCE_VCPU_PRICE,
) + proPlan.price;
const updatedPrice = priceForTotalAvailableVCPU + proPlan.price;
const { vcpu: allocatedVCPU, memory: allocatedMemory } =
getAllocatedResources(formValues);

View File

@@ -1,4 +1,4 @@
import { test } from 'vitest';
import { expect, test } from 'vitest';
import getAllocatedResources from './getAllocatedResources';
test('should return the total number of allocated resources', () => {

View File

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

View File

@@ -0,0 +1,508 @@
import { useDialog } from '@/components/common/DialogProvider';
import { Form } from '@/components/form/Form';
import { Alert } from '@/components/ui/v2/Alert';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { ArrowsClockwise } from '@/components/ui/v2/icons/ArrowsClockwise';
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useHostName } from '@/features/projects/common/hooks/useHostName';
import { InfoCard } from '@/features/projects/overview/components/InfoCard';
import {
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 { EnvironmentFormSection } from '@/features/services/components/ServiceForm/components/EnvironmentFormSection';
import { PortsFormSection } from '@/features/services/components/ServiceForm/components/PortsFormSection';
import { ReplicasFormSection } from '@/features/services/components/ServiceForm/components/ReplicasFormSection';
import { StorageFormSection } from '@/features/services/components/ServiceForm/components/StorageFormSection';
import type { DialogFormProps } from '@/types/common';
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
import { getToastStyleProps } from '@/utils/constants/settings';
import { copy } from '@/utils/copy';
import {
useInsertRunServiceConfigMutation,
useInsertRunServiceMutation,
useReplaceRunServiceConfigMutation,
type ConfigRunServiceConfigInsertInput,
} from '@/utils/__generated__/graphql';
import type { ApolloError } from '@apollo/client';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import { parse } from 'shell-quote';
import * as Yup from 'yup';
import { ServiceConfirmationDialog } from './components/ServiceConfirmationDialog';
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({
serviceID,
initialData,
onSubmit,
onCancel,
location,
}: ServiceFormProps) {
const hostName = useHostName();
const { onDirtyStateChange, openDialog, closeDialog } = useDialog();
const [insertRunService] = useInsertRunServiceMutation();
const { currentProject } = useCurrentWorkspaceAndProject();
const [insertRunServiceConfig] = useInsertRunServiceConfigMutation();
const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation();
const [detailsServiceId, setDetailsServiceId] = useState('');
const [detailsServiceSubdomain, setDetailsServiceSubdomain] = useState(
initialData?.subdomain,
);
const [createServiceFormError, setCreateServiceFormError] =
useState<Error | null>(null);
const form = useForm<ServiceFormValues>({
defaultValues: initialData ?? {
compute: {
cpu: 62,
memory: 128,
},
replicas: 1,
},
reValidateMode: 'onSubmit',
resolver: yupResolver(validationSchema),
});
const {
watch,
register,
formState: { errors, isSubmitting, dirtyFields },
} = form;
const formValues = watch();
const serviceImage = watch('image');
const isDirty = Object.keys(dirtyFields).length > 0;
useEffect(() => {
onDirtyStateChange(isDirty, location);
}, [isDirty, location, onDirtyStateChange]);
const getFormattedConfig = (values: ServiceFormValues) => {
const config: ConfigRunServiceConfigInsertInput = {
name: values.name,
image: {
image: values.image,
},
command: parse(values.command).map((item) => item.toString()),
resources: {
compute: {
cpu: values.compute.cpu,
memory: values.compute.memory,
},
storage: values.storage.map((item) => ({
name: item.name,
path: item.path,
capacity: item.capacity,
})),
replicas: values.replicas,
},
environment: values.environment.map((item) => ({
name: item.name,
value: item.value,
})),
ports: values.ports.map((item) => ({
port: item.port,
type: item.type,
publish: item.publish,
})),
};
return config;
};
const createOrUpdateService = async (values: ServiceFormValues) => {
const config = getFormattedConfig(values);
if (serviceID) {
// Update service config
await replaceRunServiceConfig({
variables: {
appID: currentProject.id,
serviceID,
config,
},
});
setDetailsServiceId(serviceID);
} else {
// Insert service config
const {
data: {
insertRunService: { id: newServiceID, subdomain },
},
} = await insertRunService({
variables: {
object: {
appID: currentProject.id,
},
},
});
await insertRunServiceConfig({
variables: {
appID: currentProject.id,
serviceID: newServiceID,
config: {
...config,
image: {
// If the image field left empty then we auto-populate following this format
// registry.<region>.<nhost_domain>/<service_id>
image:
values.image.length > 0
? values.image
: `registry.${currentProject.region.awsName}.${currentProject.region.domain}/${newServiceID}`,
},
},
},
});
setDetailsServiceId(newServiceID);
setDetailsServiceSubdomain(subdomain);
}
};
const handleSubmit = async (values: ServiceFormValues) => {
try {
await toast.promise(
createOrUpdateService(values),
{
loading: 'Configuring the service...',
success: `The service has been configured successfully.`,
error: (arg: ApolloError) => {
// we need to get the internal error message from the GraphQL error
const { internal } = arg.graphQLErrors[0]?.extensions || {};
const { message } = (internal as Record<string, any>)?.error || {};
// we use the default Apollo error message if we can't find the
// internal error message
return (
message ||
arg.message ||
'An error occurred while configuring the service. Please try again.'
);
},
},
getToastStyleProps(),
);
onSubmit?.();
} catch {
// Note: The toast will handle the error.
}
};
const handleConfirm = (values: ServiceFormValues) => {
openDialog({
title: 'Confirm Resources',
component: (
<ServiceConfirmationDialog
formValues={values}
onCancel={closeDialog}
onSubmit={async () => {
await handleSubmit(formValues);
}}
/>
),
});
};
useEffect(() => {
(async () => {
if (detailsServiceId) {
openDialog({
title: 'Service Details',
component: (
<ServiceDetailsDialog
serviceID={detailsServiceId}
subdomain={detailsServiceSubdomain}
ports={formValues.ports}
/>
),
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
})();
}, [detailsServiceId, detailsServiceSubdomain, formValues, openDialog]);
const pricingExplanation = () => {
const vCPUs = `${formValues.compute.cpu / RESOURCE_VCPU_MULTIPLIER} vCPUs`;
const mem = `${formValues.compute.memory} MiB Mem`;
let details = `${vCPUs} + ${mem}`;
if (formValues.replicas > 1) {
details = `(${details}) x ${formValues.replicas} replicas`;
}
return `Approximate cost for ${details}`;
};
const copyConfig = () => {
const config = getFormattedConfig(formValues);
const base64Config = btoa(JSON.stringify(config));
const link = `${hostName}/run-one-click-install?config=${base64Config}`;
copy(link, 'Service Config');
};
return (
<FormProvider {...form}>
<Form
onSubmit={handleConfirm}
className="grid grid-flow-row gap-4 px-6 pb-6"
>
<Input
{...register('name')}
id="name"
label={
<Box className="flex flex-row items-center space-x-2">
<Text>Name</Text>
<Tooltip title="Name of the service, must be unique per project.">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
</Box>
}
placeholder="Service name"
hideEmptyHelperText
error={!!errors.name}
helperText={errors?.name?.message}
fullWidth
autoComplete="off"
autoFocus
/>
<Input
{...register('image')}
id="image"
label={
<Box className="flex flex-row items-center space-x-2">
<Text>Image</Text>
<Tooltip
title={
<span>
Image to use, it can be hosted on any public registry or it
can use the{' '}
<a
target="_blank"
rel="noopener noreferrer"
href="https://docs.nhost.io/run/registry"
className="underline"
>
Nhost registry
</a>
. Image needs to support arm.
</span>
}
>
<InfoIcon
aria-label="Info"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
</Box>
}
placeholder="To automatically fill the private registry, leave it blank."
hideEmptyHelperText
error={!!errors.image}
helperText={errors?.image?.message}
fullWidth
autoComplete="off"
/>
{/* This shows only when trying to edit a service */}
{serviceID && serviceImage && (
<InfoCard
title="Private registry"
value={`registry.${currentProject.region.awsName}.${currentProject.region.domain}/${serviceID}`}
/>
)}
<Input
{...register('command')}
id="command"
label={
<Box className="flex flex-row items-center space-x-2">
<Text>Command</Text>
<Tooltip title="Command to run when to start the service. This is optional as the image may already have a baked-in command.">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
</Box>
}
placeholder="$ npm start"
hideEmptyHelperText
error={!!errors.command}
helperText={errors?.command?.message}
fullWidth
autoComplete="off"
/>
<Alert
severity="info"
className="flex items-center justify-between space-x-2"
>
<span>{pricingExplanation()}</span>
<b>
$
{parseFloat(
(
formValues.compute.cpu *
formValues.replicas *
COST_PER_VCPU
).toFixed(2),
)}
</b>
</Alert>
<ComputeFormSection />
<ReplicasFormSection />
<EnvironmentFormSection />
<PortsFormSection />
<StorageFormSection />
{createServiceFormError && (
<Alert
severity="error"
className="grid items-center justify-between grid-flow-col px-4 py-3"
>
<span className="text-left">
<strong>Error:</strong> {createServiceFormError.message}
</span>
<Button
variant="borderless"
color="error"
size="small"
onClick={() => {
setCreateServiceFormError(null);
}}
>
Clear
</Button>
</Alert>
)}
<div className="grid grid-flow-row gap-2">
<div className="grid grid-cols-2 gap-2">
<Button
type="submit"
disabled={isSubmitting}
startIcon={serviceID ? <ArrowsClockwise /> : <PlusIcon />}
>
{serviceID ? 'Update' : 'Create'}
</Button>
<Button
color="secondary"
variant="outlined"
disabled={isSubmitting}
onClick={copyConfig}
startIcon={<CopyIcon />}
>
Copy one-click install link
</Button>
</div>
<Button variant="outlined" color="secondary" onClick={onCancel}>
Cancel
</Button>
</div>
</Form>
</FormProvider>
);
}

View File

@@ -0,0 +1,103 @@
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { ArrowLeftIcon } from '@/components/ui/v2/icons/ArrowLeftIcon';
import { ArrowRightIcon } from '@/components/ui/v2/icons/ArrowRightIcon';
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { Slider } from '@/components/ui/v2/Slider';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import {
MAX_SERVICES_MEM,
MEM_CPU_RATIO,
MIN_SERVICES_MEM,
} from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import type { ServiceFormValues } from '@/features/services/components/ServiceForm';
import { useFormContext, useWatch } from 'react-hook-form';
export default function ComputeFormSection() {
const { setValue } = useFormContext<ServiceFormValues>();
const formValues = useWatch<ServiceFormValues>();
const handleSliderUpdate = (value: string) => {
const updatedMem = parseFloat(value);
if (Number.isNaN(updatedMem) || updatedMem < MIN_SERVICES_MEM) {
return;
}
setValue('compute.memory', Math.floor(updatedMem), { shouldDirty: true });
setValue('compute.cpu', Math.floor(updatedMem / MEM_CPU_RATIO), {
shouldDirty: true,
});
};
const incrementCompute = () => {
const newMemoryValue = formValues.compute.memory + 128;
setValue('compute.memory', newMemoryValue);
setValue('compute.cpu', Math.floor(newMemoryValue / MEM_CPU_RATIO));
};
const decrementCompute = () => {
const newMemoryValue = formValues.compute.memory - 128;
setValue('compute.memory', newMemoryValue);
setValue('compute.cpu', Math.floor(newMemoryValue / MEM_CPU_RATIO));
};
return (
<Box className="space-y-4 rounded border-1 p-4">
<Box className="flex flex-row items-center space-x-2">
<Text variant="h4" className="font-semibold">
vCPUs: {formValues.compute.cpu / 1000} / Memory:{' '}
{formValues.compute.memory}
</Text>
<Tooltip
title={
<span>
Compute resources dedicated for the service. Refer to{' '}
<a
target="_blank"
rel="noopener noreferrer"
href="https://docs.nhost.io/run/resources"
className="underline"
>
resources
</a>{' '}
for more information.
</span>
}
>
<InfoIcon aria-label="Info" className="h-4 w-4" color="primary" />
</Tooltip>
</Box>
<Box className="flex flex-row items-center justify-between space-x-4">
<Button
disabled={formValues.compute.memory <= MIN_SERVICES_MEM}
variant="outlined"
onClick={decrementCompute}
>
<ArrowLeftIcon className="h-4 w-4" />
</Button>
<Slider
value={Number(formValues.compute.memory)}
onChange={(_event, value) => handleSliderUpdate(value.toString())}
max={MAX_SERVICES_MEM}
min={MIN_SERVICES_MEM}
step={256}
aria-label="Compute resources"
marks
/>
<Button
disabled={formValues.compute.memory >= MAX_SERVICES_MEM}
variant="outlined"
onClick={incrementCompute}
>
<ArrowRightIcon className="h-4 w-4" />
</Button>
</Box>
</Box>
);
}

View File

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

View File

@@ -0,0 +1,97 @@
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { TrashIcon } from '@/components/ui/v2/icons/TrashIcon';
import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import type { ServiceFormValues } from '@/features/services/components/ServiceForm';
import { useState } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
export default function EnvironmentFormSection() {
const {
register,
formState: { errors },
} = useFormContext<ServiceFormValues>();
const [focusedInput, setFocusedInput] = useState<string>(null);
const { fields, append, remove } = useFieldArray({
name: 'environment',
});
return (
<Box className="space-y-4 rounded border-1 p-4">
<Box className="flex flex-row items-center justify-between ">
<Box className="flex flex-row items-center space-x-2">
<Text variant="h4" className="font-semibold">
Environment
</Text>
<Tooltip
title={
<span>
Environment variables to add to the service. Other than the ones
specified here only <code>NHOST_SUBDOMAIN</code> and{' '}
<code>NHOST_REGION</code> are added automatically to the
service.
</span>
}
>
<InfoIcon aria-label="Info" className="h-4 w-4" color="primary" />
</Tooltip>
</Box>
<Button
variant="borderless"
onClick={() => append({ name: '', value: '' })}
>
<PlusIcon className="h-5 w-5" />
</Button>
</Box>
<Box className="flex flex-col space-y-4">
{fields.map((field, index) => (
<Box key={field.id} className="flex w-full items-center space-x-2">
<div className="flex w-full flex-col space-y-2">
<Input
{...register(`environment.${index}.name`)}
id={`${field.id}-name`}
placeholder={`Key ${index}`}
className="w-full"
hideEmptyHelperText
error={!!errors?.environment?.at(index)}
helperText={errors?.environment?.at(index)?.message}
fullWidth
autoComplete="off"
/>
<Input
{...register(`environment.${index}.value`)}
id={`${field.id}-value`}
placeholder={`Value ${index}`}
className="w-full"
hideEmptyHelperText
error={!!errors?.environment?.at(index)}
helperText={errors?.environment?.at(index)?.message}
fullWidth
autoComplete="off"
multiline
maxRows={focusedInput === `${field.id}-value` ? 1000 : 1}
onFocusCapture={() => setFocusedInput(`${field.id}-value`)}
onBlurCapture={() => setFocusedInput(null)}
/>
</div>
<Button
variant="borderless"
className=""
color="error"
onClick={() => remove(index)}
>
<TrashIcon className="h-6 w-4" />
</Button>
</Box>
))}
</Box>
</Box>
);
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,61 @@
import { Box } from '@/components/ui/v2/Box';
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { Slider } from '@/components/ui/v2/Slider';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import { MAX_SERVICE_REPLICAS } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import type { ServiceFormValues } from '@/features/services/components/ServiceForm';
import { useFormContext, useWatch } from 'react-hook-form';
export default function ReplicasFormSection() {
const { setValue } = useFormContext<ServiceFormValues>();
const { replicas } = useWatch<ServiceFormValues>();
const handleReplicasChange = (value: string) => {
const updatedReplicas = parseInt(value, 10);
setValue('replicas', updatedReplicas, { shouldDirty: true });
// TODO Trigger revalidate storage
};
return (
<Box className="space-y-4 rounded border-1 p-4">
<Box className="flex flex-row items-center space-x-2">
<Text variant="h4" className="font-semibold">
Replicas ({replicas})
</Text>
<Tooltip
title={
<span>
Number of replicas for the service. Multiple replicas can process
requests/work in parallel. You can set replicas to 0 to pause the
service. Refer to{' '}
<a
target="_blank"
rel="noopener noreferrer"
href="https://docs.nhost.io/run/resources"
className="underline"
>
resources
</a>{' '}
for more information.
</span>
}
>
<InfoIcon aria-label="Info" className="h-4 w-4" color="primary" />
</Tooltip>
</Box>
<Slider
value={replicas}
onChange={(_event, value) => handleReplicasChange(value.toString())}
min={0}
max={MAX_SERVICE_REPLICAS}
step={1}
aria-label="Replicas"
marks
/>
</Box>
);
}

View File

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

View File

@@ -0,0 +1,87 @@
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Divider } from '@/components/ui/v2/Divider';
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import type { ServiceFormValues } from '@/features/services/components/ServiceForm';
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
export interface ServiceConfirmationDialogProps {
/**
* The updated resources that the user has selected.
*/
formValues: ServiceFormValues;
/**
* Function to be called when the user clicks the cancel button.
*/
onCancel: () => void;
/**
* Function to be called when the user clicks the confirm button.
*/
onSubmit: () => Promise<void>;
}
export default function ServiceConfirmationDialog({
formValues,
onCancel,
onSubmit,
}: ServiceConfirmationDialogProps) {
const approximatePriceForService = parseFloat(
(formValues.compute.cpu * formValues.replicas * COST_PER_VCPU).toFixed(2),
);
return (
<div className="grid grid-flow-row gap-6 px-6 pb-6">
<Box className="grid grid-flow-row gap-4">
<Box className="grid grid-flow-row gap-1.5">
<Box className="grid grid-flow-col items-center justify-between gap-2">
<Box className="grid grid-flow-row gap-0.5">
<Text color="secondary">vCPUs</Text>
</Box>
<Text>{formValues.compute.cpu / RESOURCE_VCPU_MULTIPLIER}</Text>
</Box>
<Box className="grid grid-flow-col items-center justify-between gap-2">
<Box className="grid grid-flow-row gap-0.5">
<Text color="secondary">Memory</Text>
</Box>
<Text>{formValues.compute.memory} MiB</Text>
</Box>
<Box className="grid grid-flow-col items-center justify-between gap-2">
<Box className="grid grid-flow-row gap-0.5">
<Text color="secondary">Replicas</Text>
</Box>
<Text>{formValues.replicas}</Text>
</Box>
</Box>
<Divider />
<Box className="grid grid-flow-col justify-between gap-2">
<Box className="grid grid-flow-col items-center gap-1.5">
<Text className="font-medium">Approximate Cost</Text>
<Tooltip title="$0.0012/minute for every 1 vCPU and 2 GiB of RAM">
<InfoIcon aria-label="Info" className="h-4 w-4" color="primary" />
</Tooltip>
</Box>
<Text>${approximatePriceForService}/mo</Text>
</Box>
</Box>
<Box className="grid grid-flow-row gap-2">
<Button color="primary" onClick={onSubmit} autoFocus>
Confirm
</Button>
<Button variant="borderless" color="secondary" onClick={onCancel}>
Cancel
</Button>
</Box>
</div>
);
}

View File

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

View File

@@ -0,0 +1,76 @@
import { useDialog } from '@/components/common/DialogProvider';
import { Button } from '@/components/ui/v2/Button';
import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { InfoCard } from '@/features/projects/overview/components/InfoCard';
import type { ConfigRunServicePort } from '@/utils/__generated__/graphql';
export interface ServiceDetailsDialogProps {
/**
* The id of the service
*/
serviceID: string;
/**
* The subdomain of the service
*/
subdomain: string;
/**
* The service ports
* We use partial here because `port` is set as required in ConfigRunServicePort
*/
ports: Partial<ConfigRunServicePort>[];
}
export default function ServiceDetailsDialog({
serviceID,
subdomain,
ports,
}: ServiceDetailsDialogProps) {
const { currentProject } = useCurrentWorkspaceAndProject();
const { closeDialog } = useDialog();
const getPortURL = (_port: string | number) => {
const port = Number(_port) > 0 ? Number(_port) : '[port]';
return `https://${subdomain}-${port}.svc.${currentProject?.region.awsName}.${currentProject?.region.domain}`;
};
return (
<div className="flex flex-col gap-4 px-6 pb-6">
<div className="flex flex-col gap-2">
<Text color="secondary">Private registry</Text>
<InfoCard
title=""
value={`registry.${currentProject.region.awsName}.${currentProject.region.domain}/${serviceID}`}
/>
</div>
{ports?.length > 0 && (
<div className="flex flex-col gap-2">
<Text color="secondary">Ports</Text>
{ports
.filter((port) => port.publish)
.map((port) => (
<InfoCard
key={String(port.port)}
title={`${port.type} <--> ${port.port}`}
value={getPortURL(port.port)}
/>
))}
</div>
)}
<Button
className="w-full"
color="primary"
onClick={() => closeDialog()}
autoFocus
>
OK
</Button>
</div>
);
}

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