Merge branch 'main' of https://github.com/jaysomani/console into fix/dashboard-user-team-prefs-deletion

This commit is contained in:
Your Name
2026-01-17 19:39:17 +05:30
64 changed files with 885 additions and 861 deletions
+5 -5
View File
@@ -22,12 +22,13 @@
},
"dependencies": {
"@ai-sdk/svelte": "^1.1.24",
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f",
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@865e2fc",
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bbad65f",
"@appwrite.io/pink-legacy": "^1.0.3",
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@865e2fc",
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@bbad65f",
"@faker-js/faker": "^9.9.0",
"@plausible-analytics/tracker": "^0.4.4",
"@popperjs/core": "^2.11.8",
"@sentry/sveltekit": "^8.38.0",
"@stripe/stripe-js": "^3.5.0",
@@ -42,7 +43,6 @@
"ignore": "^6.0.2",
"nanoid": "^5.1.5",
"nanotar": "^0.1.1",
"plausible-tracker": "^0.3.9",
"pretty-bytes": "^6.1.1",
"prismjs": "^1.30.0",
"remarkable": "^2.0.1",
@@ -57,7 +57,7 @@
"@melt-ui/svelte": "^0.86.5",
"@playwright/test": "^1.55.1",
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.42.1",
"@sveltejs/kit": "^2.49.5",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
+68 -60
View File
@@ -12,29 +12,32 @@ importers:
specifier: ^1.1.24
version: 1.1.24(svelte@5.25.3)(zod@3.24.3)
'@appwrite.io/console':
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f
version: https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755
version: https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755
'@appwrite.io/pink-icons':
specifier: 0.25.0
version: 0.25.0
'@appwrite.io/pink-icons-svelte':
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@865e2fc
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@865e2fc(svelte@5.25.3)
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bbad65f
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bbad65f(svelte@5.25.3)
'@appwrite.io/pink-legacy':
specifier: ^1.0.3
version: 1.0.3
'@appwrite.io/pink-svelte':
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@865e2fc
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@865e2fc(svelte@5.25.3)
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@bbad65f
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@bbad65f(svelte@5.25.3)
'@faker-js/faker':
specifier: ^9.9.0
version: 9.9.0
'@plausible-analytics/tracker':
specifier: ^0.4.4
version: 0.4.4
'@popperjs/core':
specifier: ^2.11.8
version: 2.11.8
'@sentry/sveltekit':
specifier: ^8.38.0
version: 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@sveltejs/kit@2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
version: 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@sveltejs/kit@2.49.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(typescript@5.8.2)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
'@stripe/stripe-js':
specifier: ^3.5.0
version: 3.5.0
@@ -71,9 +74,6 @@ importers:
nanotar:
specifier: ^0.1.1
version: 0.1.1
plausible-tracker:
specifier: ^0.3.9
version: 0.3.9
pretty-bytes:
specifier: ^6.1.1
version: 6.1.1
@@ -110,10 +110,10 @@ importers:
version: 1.56.1
'@sveltejs/adapter-static':
specifier: ^3.0.8
version: 3.0.8(@sveltejs/kit@2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))
version: 3.0.8(@sveltejs/kit@2.49.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(typescript@5.8.2)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))
'@sveltejs/kit':
specifier: ^2.42.1
version: 2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
specifier: ^2.49.5
version: 2.49.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(typescript@5.8.2)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
'@sveltejs/vite-plugin-svelte':
specifier: ^5.0.3
version: 5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
@@ -272,17 +272,17 @@ packages:
'@analytics/type-utils@0.6.2':
resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==}
'@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f}
version: 1.10.0
'@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755}
version: 2.1.0
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1':
resolution: {integrity: sha512-iLFlV55hj8mGuAbmxJGenxN5RaZMmVT4GJb9dv/MP1xBAtYibFq7JvBcxm18qV2KU8c31Rntf+Ub4GL7HwqTYg==}
peerDependencies:
svelte: ^4.0.0
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@865e2fc':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@865e2fc}
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bbad65f':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bbad65f}
version: 2.0.0-RC.1
peerDependencies:
svelte: ^4.0.0
@@ -296,8 +296,8 @@ packages:
'@appwrite.io/pink-legacy@1.0.3':
resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==}
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@865e2fc':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@865e2fc}
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@bbad65f':
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@bbad65f}
version: 2.0.0-RC.2
peerDependencies:
svelte: ^4.0.0
@@ -364,8 +364,8 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/parser@7.28.5':
resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
'@babel/parser@7.28.6':
resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -385,8 +385,8 @@ packages:
resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==}
engines: {node: '>=6.9.0'}
'@babel/types@7.28.5':
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
'@babel/types@7.28.6':
resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==}
engines: {node: '>=6.9.0'}
'@csstools/color-helpers@5.0.2':
@@ -1008,6 +1008,9 @@ packages:
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
engines: {node: '>= 10.0.0'}
'@plausible-analytics/tracker@0.4.4':
resolution: {integrity: sha512-fz0NOYUEYXtg1TBaPEEvtcBq3FfmLFuTe1VZw4M8sTWX129br5dguu3M15+plOQnc181ShYe67RfwhKgK89VnA==}
'@playwright/test@1.56.1':
resolution: {integrity: sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==}
engines: {node: '>=18'}
@@ -1279,18 +1282,21 @@ packages:
peerDependencies:
'@sveltejs/kit': ^2.0.0
'@sveltejs/kit@2.42.1':
resolution: {integrity: sha512-SoWdb/OxEKHMlXZ78eNZa4pR1YdOGnUfcFj5NylJv+ZaTrnHK5xapw+7RJP2MiS1D4T+sEH25/tmMtmEl8p6VA==}
'@sveltejs/kit@2.49.5':
resolution: {integrity: sha512-dCYqelr2RVnWUuxc+Dk/dB/SjV/8JBndp1UovCyCZdIQezd8TRwFLNZctYkzgHxRJtaNvseCSRsuuHPeUgIN/A==}
engines: {node: '>=18.13'}
hasBin: true
peerDependencies:
'@opentelemetry/api': ^1.0.0
'@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0
svelte: ^4.0.0 || ^5.0.0-next.0
typescript: ^5.3.3
vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0
peerDependenciesMeta:
'@opentelemetry/api':
optional: true
typescript:
optional: true
'@sveltejs/vite-plugin-svelte-inspector@4.0.1':
resolution: {integrity: sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==}
@@ -1882,6 +1888,9 @@ packages:
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
d3-array@3.2.4:
resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
engines: {node: '>=12'}
@@ -2072,8 +2081,8 @@ packages:
engines: {node: '>=0.10'}
hasBin: true
devalue@5.3.2:
resolution: {integrity: sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==}
devalue@5.6.2:
resolution: {integrity: sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==}
devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
@@ -2900,10 +2909,6 @@ packages:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
plausible-tracker@0.3.9:
resolution: {integrity: sha512-hMhneYm3GCPyQon88SZrVJx+LlqhM1kZFQbuAgXPoh/Az2YvO1B6bitT9qlhpiTdJlsT5lsr3gPmzoVjb5CDXA==}
engines: {node: '>=10'}
playwright-core@1.56.1:
resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==}
engines: {node: '>=18'}
@@ -3131,14 +3136,14 @@ packages:
engines: {node: '>=10'}
hasBin: true
seroval-plugins@1.3.3:
resolution: {integrity: sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==}
seroval-plugins@1.4.2:
resolution: {integrity: sha512-X7p4MEDTi+60o2sXZ4bnDBhgsUYDSkQEvzYZuJyFqWg9jcoPsHts5nrg5O956py2wyt28lUrBxk0M0/wU8URpA==}
engines: {node: '>=10'}
peerDependencies:
seroval: ^1.0
seroval@1.3.2:
resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==}
seroval@1.4.2:
resolution: {integrity: sha512-N3HEHRCZYn3cQbsC4B5ldj9j+tHdf4JZoYPlcI4rRYu0Xy4qN8MQf1Z08EibzB0WpgRG5BGK08FTrmM66eSzKQ==}
engines: {node: '>=10'}
set-cookie-parser@2.7.1:
@@ -3823,13 +3828,13 @@ snapshots:
'@analytics/type-utils@0.6.2': {}
'@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f': {}
'@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755': {}
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)':
dependencies:
svelte: 5.25.3
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@865e2fc(svelte@5.25.3)':
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bbad65f(svelte@5.25.3)':
dependencies:
svelte: 5.25.3
@@ -3842,7 +3847,7 @@ snapshots:
'@appwrite.io/pink-icons': 1.0.0
the-new-css-reset: 1.11.3
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@865e2fc(svelte@5.25.3)':
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@bbad65f(svelte@5.25.3)':
dependencies:
'@appwrite.io/pink-icons-svelte': 2.0.0-RC.1(svelte@5.25.3)
'@floating-ui/dom': 1.6.13
@@ -3945,9 +3950,9 @@ snapshots:
dependencies:
'@babel/types': 7.27.0
'@babel/parser@7.28.5':
'@babel/parser@7.28.6':
dependencies:
'@babel/types': 7.28.5
'@babel/types': 7.28.6
'@babel/runtime@7.27.0':
dependencies:
@@ -3976,7 +3981,7 @@ snapshots:
'@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
'@babel/types@7.28.5':
'@babel/types@7.28.6':
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
@@ -4575,6 +4580,8 @@ snapshots:
'@parcel/watcher-win32-x64': 2.5.1
optional: true
'@plausible-analytics/tracker@0.4.4': {}
'@playwright/test@1.56.1':
dependencies:
playwright: 1.56.1
@@ -4796,14 +4803,14 @@ snapshots:
magic-string: 0.30.7
svelte: 5.25.3
'@sentry/sveltekit@8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@sveltejs/kit@2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))':
'@sentry/sveltekit@8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)(@sveltejs/kit@2.49.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(typescript@5.8.2)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))':
dependencies:
'@sentry/core': 8.55.0
'@sentry/node': 8.55.0
'@sentry/opentelemetry': 8.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.30.0)
'@sentry/svelte': 8.55.0(svelte@5.25.3)
'@sentry/vite-plugin': 2.22.6
'@sveltejs/kit': 2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
'@sveltejs/kit': 2.49.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(typescript@5.8.2)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
magic-string: 0.30.7
magicast: 0.2.8
sorcery: 1.0.0
@@ -4875,11 +4882,11 @@ snapshots:
dependencies:
acorn: 8.15.0
'@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))':
'@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.49.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(typescript@5.8.2)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))':
dependencies:
'@sveltejs/kit': 2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
'@sveltejs/kit': 2.49.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(typescript@5.8.2)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))
'@sveltejs/kit@2.42.1(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))':
'@sveltejs/kit@2.49.5(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(typescript@5.8.2)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))':
dependencies:
'@standard-schema/spec': 1.0.0
'@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0)
@@ -4887,7 +4894,7 @@ snapshots:
'@types/cookie': 0.6.0
acorn: 8.15.0
cookie: 0.6.0
devalue: 5.3.2
devalue: 5.6.2
esm-env: 1.2.2
kleur: 4.1.5
magic-string: 0.30.18
@@ -4899,6 +4906,7 @@ snapshots:
vite: 7.0.6(@types/node@22.13.14)(sass@1.86.0)
optionalDependencies:
'@opentelemetry/api': 1.9.0
typescript: 5.8.2
'@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0)))(svelte@5.25.3)(vite@7.0.6(@types/node@22.13.14)(sass@1.86.0))':
dependencies:
@@ -5292,7 +5300,7 @@ snapshots:
'@vue/compiler-core@3.5.13':
dependencies:
'@babel/parser': 7.28.5
'@babel/parser': 7.28.6
'@vue/shared': 3.5.13
entities: 4.5.0
estree-walker: 2.0.2
@@ -5305,7 +5313,7 @@ snapshots:
'@vue/compiler-sfc@3.5.13':
dependencies:
'@babel/parser': 7.28.5
'@babel/parser': 7.28.6
'@vue/compiler-core': 3.5.13
'@vue/compiler-dom': 3.5.13
'@vue/compiler-ssr': 3.5.13
@@ -5334,7 +5342,7 @@ snapshots:
'@vue/reactivity': 3.5.13
'@vue/runtime-core': 3.5.13
'@vue/shared': 3.5.13
csstype: 3.1.3
csstype: 3.2.3
'@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.8.2))':
dependencies:
@@ -5619,6 +5627,8 @@ snapshots:
csstype@3.1.3: {}
csstype@3.2.3: {}
d3-array@3.2.4:
dependencies:
internmap: 2.0.3
@@ -5836,7 +5846,7 @@ snapshots:
detect-libc@1.0.3:
optional: true
devalue@5.3.2: {}
devalue@5.6.2: {}
devlop@1.1.0:
dependencies:
@@ -6675,8 +6685,6 @@ snapshots:
picomatch@4.0.3: {}
plausible-tracker@0.3.9: {}
playwright-core@1.56.1: {}
playwright@1.56.1:
@@ -6903,11 +6911,11 @@ snapshots:
semver@7.7.1: {}
seroval-plugins@1.3.3(seroval@1.3.2):
seroval-plugins@1.4.2(seroval@1.4.2):
dependencies:
seroval: 1.3.2
seroval: 1.4.2
seroval@1.3.2: {}
seroval@1.4.2: {}
set-cookie-parser@2.7.1: {}
@@ -6984,9 +6992,9 @@ snapshots:
solid-js@1.9.5:
dependencies:
csstype: 3.1.3
seroval: 1.3.2
seroval-plugins: 1.3.3(seroval@1.3.2)
csstype: 3.2.3
seroval: 1.4.2
seroval-plugins: 1.4.2(seroval@1.4.2)
solid-swr-store@0.10.7(solid-js@1.9.5)(swr-store@0.10.6):
dependencies:
+16 -15
View File
@@ -1,5 +1,5 @@
import Analytics, { type AnalyticsPlugin } from 'analytics';
import Plausible from 'plausible-tracker';
import { init, track } from '@plausible-analytics/tracker';
import { get } from 'svelte/store';
import { page } from '$app/state';
import { user } from '$lib/stores/user';
@@ -11,30 +11,30 @@ import { getReferrerAndUtmSource, getTrackedQueryParams } from '$lib/helpers/utm
function plausible(domain: string): AnalyticsPlugin {
if (!browser) return { name: 'analytics-plugin-plausible' };
const instance = Plausible({
domain
init({
domain,
autoCapturePageviews: false
});
return {
name: 'analytics-plugin-plausible',
page: ({ payload }) => {
instance.trackPageview({
track('pageview', {
url: payload.properties.path,
referrer: payload.properties.referrer,
deviceWidth: payload.properties.width
props: {
referrer: payload.properties.referrer,
deviceWidth: String(payload.properties.width)
}
});
},
track: ({ payload }) => {
instance.trackEvent(
payload.event,
{
props: payload.properties
},
{
url: payload.properties.path,
deviceWidth: payload.properties.width
track(payload.event, {
url: payload.properties.path,
props: {
...payload.properties,
deviceWidth: String(payload.properties.width)
}
);
});
},
loaded: () => true
};
@@ -247,6 +247,7 @@ export enum Submit {
ProjectDelete = 'submit_project_delete',
ProjectUpdateName = 'submit_project_update_name',
ProjectUpdateTeam = 'submit_project_update_team',
ProjectUpdateLabels = 'submit_project_update_labels',
ProjectService = 'submit_project_service',
ProjectUpdateSMTP = 'submit_project_update_smtp',
MemberCreate = 'submit_member_create',
+4 -4
View File
@@ -5,7 +5,6 @@
import { isCloud, isSelfHosted } from '$lib/system';
import { organization } from '$lib/stores/organization';
import { BillingPlan, Dependencies } from '$lib/constants';
import type { BackupArchive, BackupRestoration } from '$lib/sdk/backups';
import { goto, invalidate } from '$app/navigation';
import { page } from '$app/state';
import { addNotification } from '$lib/stores/notifications';
@@ -13,10 +12,11 @@
import { getProjectId } from '$lib/helpers/project';
import { toLocaleDate } from '$lib/helpers/date';
import { Typography } from '@appwrite.io/pink-svelte';
import { type Models } from '@appwrite.io/console';
const backupRestoreItems: {
archives: Map<string, BackupArchive>;
restorations: Map<string, BackupRestoration>;
archives: Map<string, Models.BackupArchive>;
restorations: Map<string, Models.BackupRestoration>;
} = {
archives: new Map(),
restorations: new Map()
@@ -117,7 +117,7 @@
if (which === 'restorations') lastDatabaseRestorationId = null;
}
function backupName(item: BackupArchive | BackupRestoration, key: string) {
function backupName(item: Models.BackupArchive | Models.BackupRestoration, key: string) {
const column = key === 'archives' ? '$createdAt' : 'startedAt';
return toLocaleDate(item[column]);
@@ -3,8 +3,15 @@
import { Badge, Layout, Typography, Table, InteractiveText } from '@appwrite.io/pink-svelte';
export let domain: string;
export let verified = undefined;
let {
domain,
verified,
ruleStatus
}: {
domain: string;
verified?: boolean;
ruleStatus?: 'created' | 'verifying' | 'unverified' | 'verified';
} = $props();
const nameserverList = $regionalConsoleVariables?._APP_DOMAINS_NAMESERVERS
? $regionalConsoleVariables?._APP_DOMAINS_NAMESERVERS?.split(',')
@@ -16,14 +23,24 @@
<Typography.Text variant="l-500" color="--fgcolor-neutral-primary">
{domain}
</Typography.Text>
{#if verified === true}
<Badge variant="secondary" type="success" size="xs" content="Verified" />
{:else if verified === false}
<Badge variant="secondary" type="warning" size="xs" content="Verification failed" />
{#if verified !== undefined}
{#if ruleStatus === 'created'}
<Badge variant="secondary" type="error" size="xs" content="Verification failed" />
{:else if ruleStatus === 'verifying'}
<Badge variant="secondary" size="xs" content="Generating certificate" />
{:else if ruleStatus === 'unverified'}
<Badge
variant="secondary"
type="error"
size="xs"
content="Certificate generation failed" />
{:else if verified === true}
<Badge variant="secondary" type="success" size="xs" content="Verified" />
{/if}
{/if}
</Layout.Stack>
<Typography.Text variant="m-400">
Add the following nameservers on your DNS provider. Note that changes may take up to 48
Add the following nameservers on your DNS provider. Note that DNS changes may take up to 48
hours to propagate fully.
</Typography.Text>
</Layout.Stack>
@@ -14,10 +14,10 @@
let {
domain,
verified = undefined,
verified,
variant,
service = 'general',
ruleStatus = undefined,
ruleStatus,
onNavigateToNameservers = () => {},
onNavigateToA = () => {},
onNavigateToAAAA = () => {}
@@ -115,7 +115,9 @@
</svelte:fragment>
<Table.Row.Base {root}>
<Table.Cell column="type" {root}>{variant.toUpperCase()}</Table.Cell>
<Table.Cell column="name" {root}>{subdomain || '@'}</Table.Cell>
<Table.Cell column="name" {root}>
<InteractiveText variant="copy" isVisible text={subdomain || '@'} />
</Table.Cell>
<Table.Cell column="value" {root}>
<InteractiveText variant="copy" isVisible text={setTarget()} />
</Table.Cell>
+13 -3
View File
@@ -15,21 +15,31 @@ function isBuildTimedOut(createdAt: string, status: string, timeoutSeconds: numb
const created = new Date(createdAt);
const elapsedSeconds = Math.floor((Date.now() - created.getTime()) / 1000);
return elapsedSeconds > timeoutSeconds;
return elapsedSeconds >= timeoutSeconds;
}
/**
* Gets the effective status for a build, considering timeout
*/
export function getEffectiveBuildStatus(
originalStatus: string,
createdAt: string,
deployment: Models.Deployment,
consoleVariables: Models.ConsoleVariables | undefined
): string {
const originalStatus = deployment.status;
const createdAt = deployment.$createdAt;
const timeoutSeconds = getBuildTimeoutSeconds(consoleVariables);
if (isBuildTimedOut(createdAt, originalStatus, timeoutSeconds)) {
return 'failed';
}
const isReady = originalStatus === 'ready';
const hasScreenshot = deployment.screenshotLight && deployment.screenshotDark;
if (isReady && !hasScreenshot) {
return 'finalizing';
}
return originalStatus;
}
+37
View File
@@ -0,0 +1,37 @@
import type { Models } from '@appwrite.io/console';
/**
* Checks if an execution has exceeded the maximum execution timeout duration
*/
function isExecutionTimedOut(createdAt: string, status: string, timeoutSeconds: number): boolean {
if (!['waiting', 'processing'].includes(status)) {
return false;
}
if (!timeoutSeconds || timeoutSeconds <= 0) {
return false;
}
const created = new Date(createdAt);
const elapsedSeconds = Math.floor((Date.now() - created.getTime()) / 1000);
return elapsedSeconds >= timeoutSeconds;
}
/**
* Gets the effective status for an execution, considering timeout
*/
export function getEffectiveExecutionStatus(
execution: Models.Execution,
func: Models.Function
): string {
const originalStatus = execution.status;
const createdAt = execution.$createdAt;
const timeoutSeconds = func.timeout;
if (isExecutionTimedOut(createdAt, originalStatus, timeoutSeconds)) {
return 'failed';
}
return originalStatus;
}
+6
View File
@@ -0,0 +1,6 @@
.responsive-table {
overflow: hidden;
width: 100%;
scrollbar-width: thin;
position: relative;
}
-332
View File
@@ -1,332 +0,0 @@
import { AppwriteException, Client, type Payload } from '@appwrite.io/console';
export type BackupPolicyList = {
total: number;
policies: BackupPolicy[];
};
export type BackupArchiveList = {
total: number;
archives: BackupArchive[];
};
export type BackupRestorationList = {
total: number;
restorations: BackupRestoration[];
};
export type BackupPolicy = {
$id: string;
name: string;
$createdAt: string;
$updatedAt: string;
services: string[];
resources: string[];
resourceId?: string;
resourceType?: string;
retention: number;
schedule: string;
enabled: boolean;
};
export type BackupArchive = {
$id: string;
$createdAt: string;
$updatedAt: string;
policyId: string;
size: number;
status: string;
startedAt: string;
migrationId: string;
services: string[];
resources: string[];
resourceId?: string;
resourceType?: string;
};
export type BackupRestoration = {
$id: string;
$createdAt: string;
$updatedAt: string;
archiveId: string;
policyId: string;
status: string;
startedAt: string;
migrationId: string;
services: string[];
resources: string[];
options: string;
};
export class Backups {
client: Client;
constructor(client: Client) {
this.client = client;
}
async createArchive(services: string[], resourceId?: string): Promise<BackupArchive> {
if (typeof services === 'undefined') {
throw new AppwriteException('Missing required parameter: "services"');
}
const apiPath = '/backups/archives';
const payload: Payload = {};
if (typeof services !== 'undefined') {
payload['services'] = services;
}
if (typeof resourceId !== 'undefined') {
payload['resourceId'] = resourceId;
}
const uri = new URL(this.client.config.endpoint + apiPath);
const apiHeaders: { [header: string]: string } = {
'content-type': 'application/json'
};
return await this.client.call('post', uri, apiHeaders, payload);
}
async deleteArchive(archiveId: string): Promise<object> {
if (typeof archiveId === 'undefined') {
throw new AppwriteException('Missing required parameter: "archiveId"');
}
const apiPath = '/backups/archives/{archiveId}'.replace('{archiveId}', archiveId);
const payload: Payload = {};
const uri = new URL(this.client.config.endpoint + apiPath);
const apiHeaders: { [header: string]: string } = {
'content-type': 'application/json'
};
return await this.client.call('delete', uri, apiHeaders, payload);
}
async listArchives(queries?: string[]): Promise<BackupArchiveList> {
const apiPath = '/backups/archives';
const payload: Payload = {};
if (typeof queries !== 'undefined') {
payload['queries'] = queries;
}
const uri = new URL(this.client.config.endpoint + apiPath);
const apiHeaders: { [header: string]: string } = {
'content-type': 'application/json'
};
return await this.client.call('get', uri, apiHeaders, payload);
}
async getArchive(archiveId: string): Promise<BackupArchive> {
if (typeof archiveId === 'undefined') {
throw new AppwriteException('Missing required parameter: "archiveId"');
}
const apiPath = '/backups/archives/{archiveId}'.replace('{archiveId}', archiveId);
const payload: Payload = {};
const uri = new URL(this.client.config.endpoint + apiPath);
const apiHeaders: { [header: string]: string } = {
'content-type': 'application/json'
};
return await this.client.call('get', uri, apiHeaders, payload);
}
async listPolicies(queries?: string[]): Promise<BackupPolicyList> {
const apiPath = '/backups/policies';
const payload: Payload = {};
if (typeof queries !== 'undefined') {
payload['queries'] = queries;
}
const uri = new URL(this.client.config.endpoint + apiPath);
const apiHeaders: { [header: string]: string } = {
'content-type': 'application/json'
};
return await this.client.call('get', uri, apiHeaders, payload);
}
async createPolicy(
policyId: string,
services: string[],
retention: number,
schedule: string,
name?: string,
resourceId?: string,
enabled?: boolean
): Promise<BackupPolicy> {
if (typeof policyId === 'undefined') {
throw new AppwriteException('Missing required parameter: "policyId"');
}
if (typeof services === 'undefined') {
throw new AppwriteException('Missing required parameter: "services"');
}
if (typeof retention === 'undefined') {
throw new AppwriteException('Missing required parameter: "retention"');
}
if (typeof schedule === 'undefined') {
throw new AppwriteException('Missing required parameter: "schedule"');
}
const apiPath = '/backups/policies';
const payload: Payload = {};
if (typeof policyId !== 'undefined') {
payload['policyId'] = policyId;
}
if (typeof name !== 'undefined') {
payload['name'] = name;
}
if (typeof services !== 'undefined') {
payload['services'] = services;
}
if (typeof resourceId !== 'undefined') {
payload['resourceId'] = resourceId;
}
if (typeof enabled !== 'undefined') {
payload['enabled'] = enabled;
}
if (typeof retention !== 'undefined') {
payload['retention'] = retention;
}
if (typeof schedule !== 'undefined') {
payload['schedule'] = schedule;
}
const uri = new URL(this.client.config.endpoint + apiPath);
const apiHeaders: { [header: string]: string } = {
'content-type': 'application/json'
};
return await this.client.call('post', uri, apiHeaders, payload);
}
async getPolicy(policyId: string): Promise<BackupPolicy> {
if (typeof policyId === 'undefined') {
throw new AppwriteException('Missing required parameter: "policyId"');
}
const apiPath = '/backups/policies/{policyId}'.replace('{policyId}', policyId);
const payload: Payload = {};
const uri = new URL(this.client.config.endpoint + apiPath);
const apiHeaders: { [header: string]: string } = {
'content-type': 'application/json'
};
return await this.client.call('get', uri, apiHeaders, payload);
}
async updatePolicy(
policyId: string,
name?: string,
retention?: number,
schedule?: string,
enabled?: boolean
): Promise<BackupPolicy> {
if (typeof policyId === 'undefined') {
throw new AppwriteException('Missing required parameter: "policyId"');
}
const apiPath = '/backups/policies/{policyId}'.replace('{policyId}', policyId);
const payload: Payload = {};
if (typeof name !== 'undefined') {
payload['name'] = name;
}
if (typeof retention !== 'undefined') {
payload['retention'] = retention;
}
if (typeof schedule !== 'undefined') {
payload['schedule'] = schedule;
}
if (typeof enabled !== 'undefined') {
payload['enabled'] = enabled;
}
const uri = new URL(this.client.config.endpoint + apiPath);
const apiHeaders: { [header: string]: string } = {
'content-type': 'application/json'
};
return await this.client.call('patch', uri, apiHeaders, payload);
}
async deletePolicy(policyId: string): Promise<object> {
if (typeof policyId === 'undefined') {
throw new AppwriteException('Missing required parameter: "policyId"');
}
const apiPath = '/backups/policies/{policyId}'.replace('{policyId}', policyId);
const payload: Payload = {};
const uri = new URL(this.client.config.endpoint + apiPath);
const apiHeaders: { [header: string]: string } = {
'content-type': 'application/json'
};
return await this.client.call('delete', uri, apiHeaders, payload);
}
async createRestoration(
archiveId: string,
services: string[],
newResourceId?: string,
newResourceName?: string
): Promise<BackupRestoration> {
if (typeof archiveId === 'undefined') {
throw new AppwriteException('Missing required parameter: "archiveId"');
}
if (typeof services === 'undefined') {
throw new AppwriteException('Missing required parameter: "services"');
}
const apiPath = '/backups/restoration';
const payload: Payload = {};
if (typeof archiveId !== 'undefined') {
payload['archiveId'] = archiveId;
}
if (typeof services !== 'undefined') {
payload['services'] = services;
}
if (typeof newResourceId !== 'undefined') {
payload['newResourceId'] = newResourceId;
}
if (typeof newResourceName !== 'undefined') {
payload['newResourceName'] = newResourceName;
}
const uri = new URL(this.client.config.endpoint + apiPath);
const apiHeaders: { [header: string]: string } = {
'content-type': 'application/json'
};
return await this.client.call('post', uri, apiHeaders, payload);
}
async listRestorations(queries?: string[]): Promise<BackupRestorationList> {
const apiPath = '/backups/restorations';
const payload: Payload = {};
if (typeof queries !== 'undefined') {
payload['queries'] = queries;
}
const uri = new URL(this.client.config.endpoint + apiPath);
const apiHeaders: { [header: string]: string } = {
'content-type': 'application/json'
};
return await this.client.call('get', uri, apiHeaders, payload);
}
async getRestoration(restorationId: string): Promise<BackupArchive> {
if (typeof restorationId === 'undefined') {
throw new AppwriteException('Missing required parameter: "restorationId"');
}
const apiPath = '/backups/restorations/{restorationId}'.replace(
'{restorationId}',
restorationId
);
const payload: Payload = {};
const uri = new URL(this.client.config.endpoint + apiPath);
const apiHeaders: { [header: string]: string } = {
'content-type': 'application/json'
};
return await this.client.call('get', uri, apiHeaders, payload);
}
}
+3 -3
View File
@@ -27,9 +27,9 @@ export async function checkForDatabaseBackupPolicies(
if (isCloud && backupsEnabled) {
try {
const policies = await sdk
.forProject(region, projectId)
.backups.listPolicies([Query.limit(1), Query.equal('resourceId', database.$id)]);
const policies = await sdk.forProject(region, projectId).backups.listPolicies({
queries: [Query.limit(1), Query.equal('resourceId', database.$id)]
});
total = policies.total;
} catch (e) {
+1 -1
View File
@@ -3,6 +3,7 @@ import {
Account,
Assistant,
Avatars,
Backups,
Client,
Console,
Functions,
@@ -26,7 +27,6 @@ import {
Organizations
} from '@appwrite.io/console';
import { Billing } from '../sdk/billing';
import { Backups } from '../sdk/backups';
import { Sources } from '$lib/sdk/sources';
import {
REGION_FRA,
+1 -1
View File
@@ -18,7 +18,7 @@ export const load: LayoutLoad = async ({ depends, parent }) => {
sdk.forConsole.account.getPrefs(),
isCloud ? sdk.forConsole.billing.getPlansInfo() : null,
fetch(`${endpoint}/health/version`, {
headers: { 'X-Appwrite-Project': project }
headers: { 'X-Appwrite-Project': project as string }
}).then((response) => response.json() as { version?: string }),
sdk.forConsole.console.variables()
]);
@@ -170,7 +170,7 @@
<Container>
<Layout.Stack direction="row" justifyContent="space-between" class="common-section">
<SearchQuery bind:this={searchQuery} placeholder="Search by name or ID" />
<SearchQuery bind:this={searchQuery} placeholder="Search by name, label, or ID" />
{#if $canWriteProjects}
{#if projectCreationDisabled && reachedProjectLimit}
@@ -24,8 +24,12 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
const archivedPage =
Number.isFinite(archivedPageRaw) && archivedPageRaw > 0 ? archivedPageRaw : 1;
const archivedOffset = pageToOffset(archivedPage, limit);
const common = [Query.equal('teamId', params.organization)];
const active = isCloud
const searchQueries = search
? [Query.or([Query.search('search', search), Query.contains('labels', search)])]
: [];
const commonQueries = [Query.equal('teamId', params.organization)];
const activeQueries = isCloud
? [Query.or([Query.equal('status', 'active'), Query.isNull('status')])]
: [];
@@ -35,10 +39,10 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
Query.offset(offset),
Query.limit(limit),
Query.orderDesc(''),
...common,
...active
],
search: search || undefined
...commonQueries,
...searchQueries,
...activeQueries
]
}),
isCloud
? sdk.forConsole.projects.list({
@@ -46,20 +50,18 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
Query.offset(archivedOffset),
Query.limit(limit),
Query.orderDesc(''),
...common,
...commonQueries,
...searchQueries,
Query.equal('status', 'archived')
],
search: search || undefined
]
})
: Promise.resolve({ projects: [], total: 0 }),
sdk.forConsole.projects.list({
queries: [...common, ...active],
search: search || undefined
queries: [...commonQueries, ...activeQueries, ...searchQueries]
}),
isCloud
? sdk.forConsole.projects.list({
queries: [...common, Query.equal('status', 'archived')],
search: search || undefined
queries: [...commonQueries, ...searchQueries, Query.equal('status', 'archived')]
})
: Promise.resolve({ projects: [], total: 0 })
]);
@@ -93,9 +93,9 @@
{#if !isDomainVerified(domain)}
<Badge
variant="secondary"
type="warning"
type="error"
content="Not verified"
size="s" />
size="xs" />
{/if}
</Layout.Stack>
{:else if column.id === 'registrar'}
@@ -1,6 +1,7 @@
<script lang="ts">
import { base } from '$app/paths';
import { page } from '$app/state';
import { goto } from '$app/navigation';
import { Button, Form } from '$lib/elements/forms';
import { InputDomain } from '$lib/elements/forms/index.js';
import { Wizard } from '$lib/layout';
@@ -23,7 +24,16 @@
teamId: page.params.organization,
domain: domainName.toLocaleLowerCase()
});
invalidate(Dependencies.DOMAINS);
await invalidate(Dependencies.DOMAINS);
const verified = domain.nameservers.toLowerCase() === 'appwrite';
if (verified) {
await goto(backPage);
addNotification({
type: 'success',
message: 'Domain verified successfully'
});
}
} catch (error) {
addNotification({
type: 'error',
@@ -13,9 +13,16 @@
} from '@appwrite.io/pink-svelte';
import DomainMetrics from './domainMetrics.svelte';
import { base } from '$app/paths';
import { isSmallViewport } from '$lib/stores/viewport';
import { app } from '$lib/stores/app';
import { Button } from '$lib/elements/forms';
import { IconDownload, IconPlus, IconUpload } from '@appwrite.io/pink-icons-svelte';
import {
IconDownload,
IconPlus,
IconUpload,
IconAdjustments
} from '@appwrite.io/pink-icons-svelte';
import DisplaySettingsModal from '$lib/layout/displaySettingsModal.svelte';
import { ViewSelector } from '$lib/components';
import { View } from '$lib/helpers/load';
import { columns, presets } from './store';
@@ -33,6 +40,7 @@
let showCreate = false;
let showPresetModal = false;
let showImportModal = false;
let showDisplaySettingsModal = false;
let selectedPreset = '';
async function downloadRecords() {
@@ -70,23 +78,55 @@
<Layout.Stack gap="l">
{#if data.recordList.total}
<Layout.Stack direction="row" justifyContent="space-between">
<Layout.Stack direction="row" gap="s" inline>
<Button secondary on:click={() => (showImportModal = true)}>
<Icon icon={IconUpload} size="s" slot="start" />
Import zone file
<Layout.Stack direction="row" gap="s" alignItems="center" wrap="wrap">
<div style:order={$isSmallViewport ? 2 : undefined}>
<Button
secondary
icon={$isSmallViewport}
ariaLabel={$isSmallViewport ? 'Import zone file' : undefined}
on:click={() => (showImportModal = true)}>
{#if $isSmallViewport}
<Icon size="m" icon={IconUpload} />
{:else}
<Icon icon={IconUpload} size="s" slot="start" />
Import zone file
{/if}
</Button>
</div>
<div style:order={$isSmallViewport ? 3 : undefined}>
<Tooltip>
<PinkButton.Button variant="secondary" icon on:click={downloadRecords}>
<Icon icon={IconDownload} size="s" />
<Icon icon={IconDownload} size={$isSmallViewport ? 'm' : 's'} />
</PinkButton.Button>
<svelte:fragment slot="tooltip">Export as .txt</svelte:fragment>
</Tooltip>
</Layout.Stack>
<Layout.Stack direction="row" gap="s" inline>
<ViewSelector ui="new" view={View.Table} {columns} hideView />
</div>
{#if !$isSmallViewport}
<div style="flex: 1"></div>
{/if}
<div style:order={$isSmallViewport ? 4 : undefined}>
{#if $isSmallViewport}
<Button
secondary
icon
ariaLabel="Display settings"
on:click={() => (showDisplaySettingsModal = true)}>
<Icon icon={IconAdjustments} />
</Button>
{:else}
<ViewSelector ui="new" view={View.Table} {columns} hideView />
{/if}
</div>
<div
style:order={$isSmallViewport ? 1 : undefined}
style:flex={$isSmallViewport ? 1 : undefined}>
<Popover let:toggle padding="none">
<Button secondary on:click={toggle}>Add preset</Button>
<Button secondary fullWidth={$isSmallViewport} on:click={toggle}
>Add preset</Button>
<svelte:fragment slot="tooltip" let:toggle>
<ActionMenu.Root>
{#each presets as preset}
@@ -100,11 +140,16 @@
</ActionMenu.Root>
</svelte:fragment>
</Popover>
<Button size="s" on:click={() => (showCreate = true)}>
</div>
<div
style:order={$isSmallViewport ? 5 : undefined}
style:width={$isSmallViewport ? '100%' : undefined}>
<Button fullWidth={$isSmallViewport} on:click={() => (showCreate = true)}>
<Icon size="s" icon={IconPlus} slot="start" />
Create record
</Button>
</Layout.Stack>
</div>
</Layout.Stack>
<Table {data} />
{:else}
@@ -153,3 +198,11 @@
{#if showRetry}
<RetryDomainModal bind:show={showRetry} selectedDomain={data.domain} />
{/if}
{#if showDisplaySettingsModal}
<DisplaySettingsModal
bind:show={showDisplaySettingsModal}
{columns}
hideView
view={View.Table} />
{/if}
@@ -50,7 +50,10 @@
</script>
<Layout.Stack>
<Table.Root columns={[...$columns, { id: 'actions', width: 40 }]} let:root>
<Table.Root
class="responsive-table"
columns={[...$columns, { id: 'actions', width: 40 }]}
let:root>
<svelte:fragment slot="header" let:root>
{#each $columns as { id, title } (id)}
<Table.Header.Cell column={id} {root}>
@@ -39,7 +39,7 @@
if (verified) {
addNotification({
type: 'success',
message: 'Domain verified'
message: 'Domain verified successfully'
});
await goto(routeBase);
@@ -76,7 +76,7 @@
{#if verified === false}
<Badge
variant="secondary"
type="warning"
type="error"
size="xs"
content="Verification failed" />
{:else if verified === true}
@@ -84,8 +84,8 @@
{/if}
</Layout.Stack>
<Typography.Text variant="m-400">
Add the following nameservers on your DNS provider. Note that changes may take up to
48 hours to propagate fully.
Add the following nameservers on your DNS provider. Note that DNS changes may take
up to 48 hours to propagate fully.
</Typography.Text>
</Layout.Stack>
@@ -32,17 +32,20 @@
const domain = await sdk.forConsole.domains.updateNameservers({
domainId: selectedDomain.$id
});
if (domain.nameservers.toLowerCase() === 'appwrite') {
show = false;
addNotification({
type: 'success',
message: `${selectedDomain.domain} has been verified`
});
} else {
error =
'Domain verification failed. Please check your domain settings or try again later';
}
await Promise.all([invalidate(Dependencies.DOMAIN), invalidate(Dependencies.DOMAINS)]);
const verified = domain?.nameservers.toLowerCase() === 'appwrite';
if (!verified) {
throw new Error(
'Domain verification failed. Please check your domain settings or try again later'
);
}
show = false;
addNotification({
type: 'success',
message: 'Domain verified successfully'
});
trackEvent(Submit.DomainUpdateVerification);
} catch (e) {
error = e.message;
@@ -64,8 +67,8 @@
>{selectedDomain.domain}</Typography.Text>
</Layout.Stack>
<Typography.Text variant="m-400">
Add the following nameservers on your DNS provider. Note that changes may take up to 48
hours to propagate fully.
Add the following nameservers on your DNS provider. Note that DNS changes may take up to
48 hours to propagate fully.
</Typography.Text>
</Layout.Stack>
@@ -77,7 +77,11 @@
{#if data.view === 'grid'}
<Grid {data} bind:showCreate />
{:else}
<Table {data} />
<Table
tables={data.tables}
policies={data.policies}
databases={data.databases}
lastBackups={data.lastBackups} />
{/if}
<PaginationWithLimit
@@ -4,7 +4,6 @@ import { sdk } from '$lib/stores/sdk';
import { type Models, Query } from '@appwrite.io/console';
import { timeFromNow } from '$lib/helpers/date';
import type { PageLoad, RouteParams } from './$types';
import type { BackupPolicy } from '$lib/sdk/backups';
import { isSelfHosted } from '$lib/system';
import { isCloud } from '$lib/system';
import type { Plan } from '$lib/sdk/billing';
@@ -71,7 +70,8 @@ async function fetchDatabasesAndBackups(
})
);
let lastBackups: Record<string, string>, policies: Record<string, BackupPolicy[]>;
let lastBackups: Record<string, string> = {};
let policies: Record<string, Models.BackupPolicy[]> = {};
if (isCloud && backupsEnabled) {
[policies, lastBackups] = await Promise.all([
@@ -86,19 +86,21 @@ async function fetchDatabasesAndBackups(
async function fetchPolicies(databases: Models.DatabaseList, params: RouteParams) {
if (isSelfHosted) return {};
const databasePolicies: Record<string, BackupPolicy[]> = {};
const databasePolicies: Record<string, Models.BackupPolicy[]> = {};
await Promise.all(
databases.databases.map(async (database) => {
try {
const { policies } = await sdk
.forProject(params.region, params.project)
.backups.listPolicies([
// TODO: are all needed!?
// Query.limit(3),
Query.equal('resourceType', 'database'),
Query.equal('resourceId', database.$id)
]);
.backups.listPolicies({
queries: [
// TODO: are all needed!?
// Query.limit(3),
Query.equal('resourceType', 'database'),
Query.equal('resourceId', database.$id)
]
});
if (policies.length > 0) {
databasePolicies[database.$id] = policies;
@@ -122,12 +124,14 @@ async function fetchLastBackups(databases: Models.DatabaseList, params: RoutePar
try {
const { archives } = await sdk
.forProject(params.region, params.project)
.backups.listArchives([
Query.limit(1),
Query.orderDesc('$createdAt'),
Query.equal('resourceType', 'database'),
Query.equal('resourceId', database.$id)
]);
.backups.listArchives({
queries: [
Query.limit(1),
Query.orderDesc('$createdAt'),
Query.equal('resourceType', 'database'),
Query.equal('resourceId', database.$id)
]
});
if (archives.length > 0) {
lastBackups[database.$id] = timeFromNow(archives[0].$createdAt);
@@ -61,16 +61,14 @@
const totalPoliciesPromise = totalPolicies.map((policy) => {
cronExpression(policy);
return sdk
.forProject(page.params.region, page.params.project)
.backups.createPolicy(
ID.unique(),
['databases'],
policy.retained,
policy.schedule,
policy.label,
resourceId
);
return sdk.forProject(page.params.region, page.params.project).backups.createPolicy({
policyId: ID.unique(),
services: ['databases'],
retention: policy.retained,
schedule: policy.schedule,
name: policy.label,
resourceId
});
});
await Promise.all(totalPoliciesPromise);
@@ -66,9 +66,10 @@
const createManualBackup = async () => {
try {
await sdk
.forProject(page.params.region, page.params.project)
.backups.createArchive(['databases'], data.database.$id);
await sdk.forProject(page.params.region, page.params.project).backups.createArchive({
services: ['databases'],
resourceId: data.database.$id
});
await invalidate(Dependencies.BACKUPS);
addNotification({
type: 'success',
@@ -119,16 +120,14 @@
const totalPoliciesPromise = totalPolicies.map((policy) => {
cronExpression(policy);
return sdk
.forProject(page.params.region, page.params.project)
.backups.createPolicy(
ID.unique(),
['databases'],
policy.retained,
policy.schedule,
policy.label,
data.database.$id
);
return sdk.forProject(page.params.region, page.params.project).backups.createPolicy({
policyId: ID.unique(),
services: ['databases'],
retention: policy.retained,
schedule: policy.schedule,
name: policy.label,
resourceId: data.database.$id
});
});
try {
@@ -2,8 +2,8 @@ import { getLimit, getPage, getView, pageToOffset, View } from '$lib/helpers/loa
import { Dependencies, PAGE_LIMIT } from '$lib/constants';
import { sdk } from '$lib/stores/sdk';
import { Query } from '@appwrite.io/console';
import type { BackupArchive, BackupArchiveList, BackupPolicyList } from '$lib/sdk/backups';
import { isCloud } from '$lib/system';
import { type Models } from '@appwrite.io/console';
export const load = async ({ params, url, route, depends, parent }) => {
depends(Dependencies.BACKUPS);
@@ -13,8 +13,8 @@ export const load = async ({ params, url, route, depends, parent }) => {
const view = getView(url, route, View.Grid);
const offset = pageToOffset(page, limit);
let backups: BackupArchiveList = { total: 0, archives: [] };
let policies: BackupPolicyList = { total: 0, policies: [] };
let backups: Models.BackupArchiveList = { total: 0, archives: [] };
let policies: Models.BackupPolicyList = { total: 0, policies: [] };
// already loaded by parent.
const { currentPlan } = await parent();
@@ -23,23 +23,23 @@ export const load = async ({ params, url, route, depends, parent }) => {
if (isCloud && backupsEnabled) {
try {
[backups, policies] = await Promise.all([
sdk
.forProject(params.region, params.project)
.backups.listArchives([
sdk.forProject(params.region, params.project).backups.listArchives({
queries: [
Query.limit(limit),
Query.offset(offset),
Query.orderDesc('$createdAt'),
Query.equal('resourceType', 'database'),
Query.equal('resourceId', params.database)
]),
]
}),
sdk
.forProject(params.region, params.project)
.backups.listPolicies([
sdk.forProject(params.region, params.project).backups.listPolicies({
queries: [
Query.orderDesc('$createdAt'),
Query.equal('resourceType', 'database'),
Query.equal('resourceId', params.database)
])
]
})
]);
} catch (e) {
// ignore
@@ -59,17 +59,17 @@ export const load = async ({ params, url, route, depends, parent }) => {
};
};
const groupArchivesByPolicy = (archives: BackupArchive[]) => {
const groupArchivesByPolicy = (archives: Models.BackupArchive[]) => {
return archives.reduce((acc, archive) => {
if (!acc.has(archive.policyId)) {
acc.set(archive.policyId, []);
}
acc.get(archive.policyId)!.push(archive);
return acc;
}, new Map<string, BackupArchive[]>());
}, new Map<string, Models.BackupArchive[]>());
};
const getLatestBackupForPolicies = (policyIdMap: Map<string, BackupArchive[]>) => {
const getLatestBackupForPolicies = (policyIdMap: Map<string, Models.BackupArchive[]>) => {
const latestBackups = new Map<string, string | null>();
for (const [policyId, archives] of policyIdMap) {
const latestBackup = archives.sort(
@@ -11,7 +11,6 @@
import { toLocaleDateTime } from '$lib/helpers/date';
import EmptyDark from '$lib/images/backups/backups-dark.png';
import EmptyLight from '$lib/images/backups/backups-light.png';
import type { BackupPolicy, BackupPolicyList } from '$lib/sdk/backups';
import { backupFrequencies } from '$lib/helpers/backups';
import { Click, trackEvent } from '$lib/actions/analytics';
import {
@@ -27,21 +26,22 @@
import { Confirm } from '$lib/components/index.js';
import Ellipse from './components/Ellipse.svelte';
import { page } from '$app/state';
import { type Models } from '@appwrite.io/console';
let showDelete = false;
let selectedPolicy: BackupPolicy = null;
let selectedPolicy: Models.BackupPolicy = null;
let showEveryPolicy = false;
export let showCreatePolicy = false;
export let policies: BackupPolicyList;
export let policies: Models.BackupPolicyList;
export let lastBackupDates: Record<string, string>;
async function deletePolicy() {
try {
await sdk
.forProject(page.params.region, page.params.project)
.backups.deletePolicy(selectedPolicy.$id);
await sdk.forProject(page.params.region, page.params.project).backups.deletePolicy({
policyId: selectedPolicy.$id
});
addNotification({
type: 'success',
message: 'Backup policy has been deleted'
@@ -15,10 +15,9 @@
import { addNotification } from '$lib/stores/notifications';
import { invalidate } from '$app/navigation';
import { calculateSize } from '$lib/helpers/sizeConvertion';
import { ID } from '@appwrite.io/console';
import { ID, type Models } from '@appwrite.io/console';
import { columns } from './store';
import { database } from '../store';
import type { BackupArchive, BackupPolicy } from '$lib/sdk/backups';
import { Click, Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { copy } from '$lib/helpers/copy';
import { LabelCard } from '$lib/components/index.js';
@@ -53,7 +52,7 @@
} = $props();
let showDelete = $state(false);
let selectedBackup: BackupArchive | null = $state(null);
let selectedBackup: Models.BackupArchive | null = $state(null);
let showDropdown = [];
@@ -87,15 +86,15 @@
);
});
function getPolicyDetails(policyId: string | null): BackupPolicy | null {
function getPolicyDetails(policyId: string | null): Models.BackupPolicy | null {
return data.policies.policies.find((policy) => policy.$id === policyId);
}
function getCleanBackupName(backup: BackupArchive): string {
function getCleanBackupName(backup: Models.BackupArchive): string {
return toLocaleDateTime(backup.$createdAt).replaceAll(',', '');
}
function getBackupStatus(backup: BackupArchive) {
function getBackupStatus(backup: Models.BackupArchive) {
switch (backup.status) {
case 'pending':
return 'pending';
@@ -115,7 +114,7 @@
try {
await sdk
.forProject(page.params.region, page.params.project)
.backups.deleteArchive(archiveId);
.backups.deleteArchive({ archiveId });
addNotification({
type: 'success',
@@ -135,7 +134,9 @@
async function deleteBackups(batchDelete: DeleteOperation): Promise<DeleteOperationState> {
const result = await batchDelete((archiveId) =>
sdk.forProject(page.params.region, page.params.project).backups.deleteArchive(archiveId)
sdk
.forProject(page.params.region, page.params.project)
.backups.deleteArchive({ archiveId })
);
try {
@@ -160,12 +161,12 @@
try {
await sdk
.forProject(page.params.region, page.params.project)
.backups.createRestoration(
selectedBackup.$id,
['databases'],
newDatabaseInfo.id ?? ID.unique(),
newDatabaseInfo.name
);
.backups.createRestoration({
archiveId: selectedBackup.$id,
services: ['databases'],
newResourceId: newDatabaseInfo.id ?? ID.unique(),
newResourceName: newDatabaseInfo.name
});
await invalidate(Dependencies.BACKUPS);
addNotification({
@@ -297,7 +297,7 @@
<Layout.Stack direction="row">
<InputSelect
{id}
required
required={column.required}
{options}
bind:value={relatedList[0]}
placeholder={`Select ${column.key}`}
@@ -316,7 +316,7 @@
<Layout.Stack direction="row">
<InputSelect
{id}
required
required={column.required}
autofocus={limited}
options={getAvailableOptions(actualIndex)}
bind:value={relatedList[actualIndex]}
@@ -346,7 +346,7 @@
<Layout.Stack direction="row">
<InputSelect
{id}
required
required={column.required}
options={getAvailableOptions(i)}
bind:value={item}
on:change={updateRelatedList}
@@ -376,7 +376,7 @@
<Layout.Stack direction="row">
<Input.ComboBox
{id}
required
required={column.required}
on:change={addNewItem}
bind:value={newItemValue}
options={availableOptions}
@@ -95,9 +95,20 @@
value = newArray as string[] | number[] | boolean[];
}
} else {
const parsedValue = isSpatialType(column)
? JSON.parse(stringValue)
: parseValue(stringValue);
let parsedValue = parseValue(stringValue);
if (isSpatialType(column)) {
if (!stringValue?.trim()) {
parsedValue = null;
} else {
try {
parsedValue = JSON.parse(stringValue);
} catch {
return;
}
}
}
if (JSON.stringify(parsedValue) !== JSON.stringify(value)) {
value = parsedValue;
}
@@ -6,10 +6,19 @@
import { columns } from './store';
import { IconExclamation } from '@appwrite.io/pink-icons-svelte';
import { Layout, Tooltip, Table, Icon } from '@appwrite.io/pink-svelte';
import type { BackupPolicy } from '$lib/sdk/backups';
import { type Models } from '@appwrite.io/console';
export let data;
const tables = data.tables;
let {
tables,
policies,
databases,
lastBackups
}: {
tables: Record<string, string>;
databases: Models.DatabaseList;
lastBackups: Record<string, string>;
policies: Record<string, Models.BackupPolicy[]>;
} = $props();
function getPolicyDescription(cron: string): string {
const [minute, hour, dayOfMonth, , dayOfWeek] = cron.split(' ');
@@ -19,6 +28,10 @@
if (minute !== '*' && hour === '*') return 'Hourly';
if (hour !== '*') return 'Daily';
}
function getPoliciesDescription(policies: Models.BackupPolicy[] | null): string {
return policies?.map((policy) => getPolicyDescription(policy.schedule)).join(', ') ?? '';
}
</script>
<Table.Root columns={$columns} let:root>
@@ -27,7 +40,7 @@
<Table.Header.Cell column={id} {root}>{title}</Table.Header.Cell>
{/each}
</svelte:fragment>
{#each data.databases.databases as database (database.$id)}
{#each databases.databases as database (database.$id)}
<!-- takes directly to the spreadsheet -->
{@const tableId = tables[database?.$id] ?? null}
{@const tableHref = tableId ? `/table-${tableId}` : ''}
@@ -45,18 +58,16 @@
{:else if column.id === 'name'}
{database.name}
{:else if column.id === 'backup'}
{@const policies = data.policies?.[database.$id] ?? null}
{@const lastBackup = data.lastBackups?.[database.$id] ?? null}
{@const description = policies
?.map((policy: BackupPolicy) => getPolicyDescription(policy.schedule))
.join(', ')}
{@const backupPolicies = policies?.[database.$id] ?? null}
{@const lastBackup = lastBackups?.[database.$id] ?? null}
{@const description = getPoliciesDescription(backupPolicies)}
<Tooltip
placement="bottom"
disabled={!policies || !lastBackup}
disabled={!backupPolicies || !lastBackup}
maxWidth="fit-content">
<span class="u-trim">
{#if !policies}
{#if !backupPolicies}
<Layout.Stack direction="row" gap="xxs" alignItems="center">
<Icon
icon={IconExclamation}
@@ -17,7 +17,7 @@
});
</script>
<Fieldset legend="Permissions">
<Fieldset legend="Security">
<Layout.Stack gap="l">
<Selector.Switch
id="execute"
@@ -38,9 +38,7 @@
footer?: Snippet;
} = $props();
let effectiveStatus = $derived(
getEffectiveBuildStatus(deployment.status, deployment.$createdAt, $regionalConsoleVariables)
);
let effectiveStatus = $derived(getEffectiveBuildStatus(deployment, $regionalConsoleVariables));
let totalSize = $derived(humanFileSize(deployment?.totalSize ?? 0));
</script>
@@ -41,11 +41,7 @@
let { data } = $props();
let effectiveStatus = $derived(
getEffectiveBuildStatus(
data.deployment.status,
data.deployment.$createdAt,
$regionalConsoleVariables
)
getEffectiveBuildStatus(data.deployment, $regionalConsoleVariables)
);
let showDelete = $state(false);
let showCancel = $state(false);
@@ -48,16 +48,24 @@
async function addDomain() {
const apexDomain = getApexDomain(domainName);
let domain = data.domainsList.domains.find((d: Models.Domain) => d.domain === apexDomain);
const domain = data.domainsList.domains.find((d: Models.Domain) => d.domain === apexDomain);
if (apexDomain && !domain && isCloud) {
try {
domain = await sdk.forConsole.domains.create({
await sdk.forConsole.domains.create({
teamId: $project.teamId,
domain: apexDomain
});
} catch (error) {
// Apex domain creation error needs to be silent.
// apex might already be added on organization level, skip.
const alreadyAdded = error?.type === 'domain_already_exists';
if (!alreadyAdded) {
addNotification({
type: 'error',
message: error.message
});
return;
}
}
}
@@ -89,12 +97,18 @@
functionId: page.params.function
});
}
if (rule?.status === 'verified') {
await invalidate(Dependencies.FUNCTION_DOMAINS);
const verified = rule?.status !== 'created';
if (verified) {
addNotification({
type: 'success',
message: 'Domain verified successfully'
});
await goto(routeBase);
await invalidate(Dependencies.FUNCTION_DOMAINS);
} else {
await goto(`${routeBase}/add-domain/verify-${domainName}?rule=${rule.$id}`);
await invalidate(Dependencies.FUNCTION_DOMAINS);
}
} catch (error) {
addNotification({
@@ -11,7 +11,6 @@
} from '@appwrite.io/pink-svelte';
import { Button, Form } from '$lib/elements/forms';
import { sdk } from '$lib/stores/sdk';
import { organization } from '$lib/stores/organization';
import { addNotification } from '$lib/stores/notifications';
import { goto, invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
@@ -23,6 +22,7 @@
import NameserverTable from '$lib/components/domains/nameserverTable.svelte';
import RecordTable from '$lib/components/domains/recordTable.svelte';
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
import { getApexDomain } from '$lib/helpers/tlds.js';
let { data } = $props();
@@ -44,6 +44,7 @@
);
const showNSTab = isCloud;
let proxyRule = $derived(data.proxyRule);
let selectedTab = $state<'cname' | 'nameserver' | 'a' | 'aaaa'>(getDefaultTab());
let routeBase = `${base}/project-${page.params.region}-${page.params.project}/functions/function-${page.params.function}/domains`;
let verified: boolean | undefined = $state(undefined);
@@ -54,44 +55,35 @@
}
async function verify() {
const isNewDomain =
data.domainsList.domains.find((rule) => rule.domain === data.proxyRule.domain) ===
undefined;
verified = undefined;
try {
if (selectedTab !== 'nameserver') {
const ruleData = await sdk
.forProject(page.params.region, page.params.project)
.proxy.updateRuleVerification({ ruleId });
verified = ruleData.status === 'verified';
const apexDomain = getApexDomain(proxyRule.domain);
const domain = data.domainsList.domains.find((d) => d.domain === apexDomain);
// This means domain verification using DNS records hasn't succeeded and the rule is still in initial state.
if (ruleData.status === 'created') {
throw new Error(
'Domain verification failed. Please check your domain settings or try again later'
);
}
} else if (isNewDomain && isCloud) {
const domainData = await sdk.forConsole.domains.create({
teamId: $organization.$id,
domain: data.proxyRule.domain
});
verified = domainData.nameservers.toLowerCase() === 'appwrite';
}
if (verified) {
addNotification({
type: 'success',
message: 'Domain added successfully'
});
} else {
addNotification({
type: 'info',
message: 'Verification in progress'
if (isCloud && domain) {
await sdk.forConsole.domains.updateNameservers({
domainId: domain.$id
});
}
} catch (error) {
// Ignore error
}
try {
proxyRule = await sdk
.forProject(page.params.region, page.params.project)
.proxy.updateRuleVerification({ ruleId });
await Promise.all([
invalidate(Dependencies.DOMAINS),
invalidate(Dependencies.FUNCTION_DOMAINS)
]);
await goto(routeBase);
await invalidate(Dependencies.DOMAINS);
await invalidate(Dependencies.FUNCTION_DOMAINS);
addNotification({
type: 'success',
message: 'Domain verified successfully'
});
} catch (error) {
verified = false;
isSubmitting.set(false);
@@ -108,7 +100,7 @@
.forProject(page.params.region, page.params.project)
.proxy.deleteRule({ ruleId });
}
await goto(`${routeBase}/add-domain?domain=${data.proxyRule.domain}`);
await goto(`${routeBase}/add-domain?domain=${proxyRule.domain}`);
}
</script>
@@ -125,7 +117,7 @@
<Icon icon={IconGlobeAlt} color="--fgcolor-neutral-primary" />
<Typography.Text variation="m-500" color="--fgcolor-neutral-primary">
{data.proxyRule.domain}
{proxyRule.domain}
</Typography.Text>
</Layout.Stack>
<Button secondary on:click={back}>Change</Button>
@@ -172,14 +164,17 @@
<Divider />
</div>
{#if selectedTab === 'nameserver'}
<NameserverTable domain={data.proxyRule.domain} {verified} />
<NameserverTable
{verified}
domain={proxyRule.domain}
ruleStatus={proxyRule.status} />
{:else}
<RecordTable
{verified}
service="functions"
variant={selectedTab}
domain={data.proxyRule.domain}
ruleStatus={data.proxyRule.status}
domain={proxyRule.domain}
ruleStatus={proxyRule.status}
onNavigateToNameservers={() => (selectedTab = 'nameserver')}
onNavigateToA={() => (selectedTab = 'a')}
onNavigateToAAAA={() => (selectedTab = 'aaaa')} />
@@ -25,8 +25,8 @@
<Badge variant="secondary" type="warning" content="Pending verification" />
</Layout.Stack>
<Typography.Text variant="m-400">
Add the following nameservers on your DNS provider. Note that changes may take
up to 48 hours to propagate fully.
Add the following nameservers on your DNS provider. Note that DNS changes may
take up to 48 hours to propagate fully.
</Typography.Text>
</Layout.Stack>
@@ -13,13 +13,16 @@
import { Divider, Tabs } from '@appwrite.io/pink-svelte';
import NameserverTable from '$lib/components/domains/nameserverTable.svelte';
import RecordTable from '$lib/components/domains/recordTable.svelte';
import { getApexDomain } from '$lib/helpers/tlds';
let {
show = $bindable(false),
selectedProxyRule
selectedProxyRule,
domainsList
}: {
show: boolean;
selectedProxyRule: Models.ProxyRule;
domainsList?: Models.DomainsList;
} = $props();
const showCNAMETab = $derived(
@@ -40,43 +43,42 @@
let selectedTab = $state<'cname' | 'nameserver' | 'a' | 'aaaa'>(getDefaultTab());
let error = $state(null);
let verified = $state(false);
let verified: boolean | undefined = $state(undefined);
function getDefaultTab() {
return showCNAMETab ? 'cname' : showATab ? 'a' : showAAAATab ? 'aaaa' : 'nameserver';
}
async function retryProxyRule() {
error = null;
verified = undefined;
try {
error = null;
const proxyRule = await sdk
const apexDomain = getApexDomain(selectedProxyRule.domain);
const domain = domainsList?.domains.find((d) => d.domain === apexDomain);
if (isCloud && domain) {
await sdk.forConsole.domains.updateNameservers({
domainId: domain.$id
});
}
} catch {
// Ignore error
}
try {
selectedProxyRule = await sdk
.forProject(page.params.region, page.params.project)
.proxy.updateRuleVerification({ ruleId: selectedProxyRule.$id });
verified = proxyRule.status === 'verified';
await invalidate(Dependencies.FUNCTION_DOMAINS);
// This means domain verification using DNS records hasn't succeeded and the rule is still in initial state.
if (proxyRule.status === 'created') {
throw new Error(
'Domain verification failed. Please check your domain settings or try again later'
);
}
if (verified) {
addNotification({
type: 'success',
message: `${selectedProxyRule.domain} has been verified`
});
} else {
addNotification({
type: 'info',
message: 'Verification in progress'
});
}
show = false;
addNotification({
type: 'success',
message: 'Domain verified successfully'
});
trackEvent(Submit.DomainUpdateVerification);
} catch (e) {
verified = false;
error =
e.message ??
'Domain verification failed. Please check your domain settings or try again later';
@@ -130,7 +132,10 @@
<Divider />
</div>
{#if selectedTab === 'nameserver'}
<NameserverTable domain={selectedProxyRule.domain} {verified} />
<NameserverTable
{verified}
domain={selectedProxyRule.domain}
ruleStatus={selectedProxyRule.status} />
{:else}
<RecordTable
{verified}
@@ -7,14 +7,14 @@ export const columns = writable<Column[]>([
title: 'Domain',
type: 'string',
format: 'string',
width: { min: 300 }
width: { min: 600 }
},
{
id: 'target',
title: 'Target',
type: 'string',
width: { min: 120, max: 400 }
width: { min: 160, max: 400 }
},
{
@@ -222,7 +222,7 @@
{/if}
{#if showRetry}
<RetryDomainModal bind:show={showRetry} {selectedProxyRule} />
<RetryDomainModal bind:show={showRetry} {selectedProxyRule} domainsList={organizationDomains} />
{/if}
{#if showLogs}
@@ -23,6 +23,8 @@
import { logStatusConverter } from './store';
import { LogsRequest, LogsResponse } from '$lib/components/logs';
import { timer } from '$lib/actions/timer';
import { getEffectiveExecutionStatus } from '$lib/helpers/executionTimeout';
import { func } from '../store';
export let selectedLogId: string;
export let logs: Models.Execution[];
@@ -47,6 +49,7 @@
}
$: selectedLog = logs?.find((log) => log.$id === selectedLogId);
$: effectiveStatus = selectedLog ? getEffectiveExecutionStatus(selectedLog, $func) : null;
$: isFirstLog = logs.findIndex((log) => log.$id === selectedLogId) === 0;
$: isLastLog = logs.findIndex((log) => log.$id === selectedLogId) === logs.length - 1;
@@ -113,12 +116,12 @@
<Tooltip
disabled={!selectedLog?.scheduledAt ||
selectedLog.status !== ExecutionStatus.Scheduled}
effectiveStatus !== ExecutionStatus.Scheduled}
maxWidth="400px">
<div>
<Status
status={logStatusConverter(selectedLog.status)}
label={capitalize(selectedLog.status)}>
status={logStatusConverter(effectiveStatus)}
label={capitalize(effectiveStatus)}>
</Status>
</div>
<span slot="tooltip">
@@ -18,6 +18,7 @@
import DualTimeView from '$lib/components/dualTimeView.svelte';
import { func } from '../store';
import { sdk } from '$lib/stores/sdk';
import { getEffectiveExecutionStatus } from '$lib/helpers/executionTimeout';
import { page } from '$app/state';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { invalidate } from '$app/navigation';
@@ -66,6 +67,7 @@
{#snippet children(root)}
{#each executions.executions as log (log.$id)}
{@const effectiveStatus = getEffectiveExecutionStatus(log, $func)}
<Table.Row.Button
{root}
id={log.$id}
@@ -100,14 +102,14 @@
{:else if column.id === 'trigger'}
{capitalize(log.trigger)}
{:else if column.id === 'status'}
{@const status = log.status}
<Tooltip
disabled={!log?.scheduledAt || status !== ExecutionStatus.Scheduled}
disabled={!log?.scheduledAt ||
effectiveStatus !== ExecutionStatus.Scheduled}
maxWidth="400px">
<div>
<Status
status={logStatusConverter(status)}
label={capitalize(status)}>
status={logStatusConverter(effectiveStatus)}
label={capitalize(effectiveStatus)}>
</Status>
</div>
<span slot="tooltip">
@@ -95,8 +95,7 @@
{#snippet children(root)}
{#each data.deploymentList.deployments as deployment (deployment.$id)}
{@const effectiveStatus = getEffectiveBuildStatus(
deployment.status,
deployment.$createdAt,
deployment,
$regionalConsoleVariables
)}
<Table.Row.Link
@@ -15,6 +15,7 @@
import ChangeOrganization from './changeOrganization.svelte';
import UpdateVariables from '../updateVariables.svelte';
import { page } from '$app/state';
import UpdateLabels from './updateLabels.svelte';
export let data;
@@ -84,8 +85,9 @@
<Container>
{#if $project}
<UpdateName />
{#if $canWriteProjects}
<UpdateName />
<UpdateLabels />
<UpdateServices />
<UpdateInstallations {...data.installations} limit={data.limit} offset={data.offset} />
<UpdateVariables
@@ -30,16 +30,24 @@
async function addDomain() {
const apexDomain = getApexDomain(domainName);
let domain = data.domainsList.domains.find((d: Models.Domain) => d.domain === apexDomain);
const domain = data.domainsList.domains.find((d: Models.Domain) => d.domain === apexDomain);
if (apexDomain && !domain && isCloud) {
try {
domain = await sdk.forConsole.domains.create({
await sdk.forConsole.domains.create({
teamId: $project.teamId,
domain: apexDomain
});
} catch (error) {
// Apex domain creation error needs to be silent.
// apex might already be added on organization level, skip.
const alreadyAdded = error?.type === 'domain_already_exists';
if (!alreadyAdded) {
addNotification({
type: 'error',
message: error.message
});
return;
}
}
}
@@ -47,12 +55,18 @@
const rule = await sdk
.forProject(page.params.region, page.params.project)
.proxy.createAPIRule({ domain: domainName.toLocaleLowerCase() });
if (rule?.status === 'verified') {
await invalidate(Dependencies.DOMAINS);
const verified = rule?.status !== 'created';
if (verified) {
addNotification({
type: 'success',
message: 'Domain verified successfully'
});
await goto(routeBase);
await invalidate(Dependencies.DOMAINS);
} else {
await goto(`${routeBase}/add-domain/verify-${domainName}?rule=${rule.$id}`);
await invalidate(Dependencies.DOMAINS);
}
} catch (error) {
addNotification({
@@ -11,7 +11,6 @@
} from '@appwrite.io/pink-svelte';
import { Button, Form } from '$lib/elements/forms';
import { sdk } from '$lib/stores/sdk';
import { organization } from '$lib/stores/organization';
import { addNotification } from '$lib/stores/notifications';
import { goto, invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
@@ -23,6 +22,7 @@
import NameserverTable from '$lib/components/domains/nameserverTable.svelte';
import RecordTable from '$lib/components/domains/recordTable.svelte';
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
import { getApexDomain } from '$lib/helpers/tlds.js';
let { data } = $props();
@@ -44,8 +44,8 @@
);
const showNSTab = isCloud;
let proxyRule = $derived(data.proxyRule);
let selectedTab = $state<'cname' | 'nameserver' | 'a' | 'aaaa'>(getDefaultTab());
const routeBase = `${base}/project-${page.params.region}-${page.params.project}/settings/domains`;
let verified: boolean | undefined = $state(undefined);
const isSubmitting = writable(false);
@@ -55,43 +55,31 @@
}
async function verify() {
const isNewDomain =
data.domainsList.domains.find((rule) => rule.domain === data.proxyRule.domain) ===
undefined;
verified = undefined;
try {
if (selectedTab !== 'nameserver') {
const ruleData = await sdk
.forProject(page.params.region, page.params.project)
.proxy.updateRuleVerification({ ruleId });
verified = ruleData.status === 'verified';
// This means domain verification using DNS records hasn't succeeded and the rule is still in initial state.
if (ruleData.status === 'created') {
throw new Error(
'Domain verification failed. Please check your domain settings or try again later'
);
}
} else if (isNewDomain && isCloud) {
const domainData = await sdk.forConsole.domains.create({
teamId: $organization.$id,
domain: data.proxyRule.domain
});
verified = domainData.nameservers.toLowerCase() === 'appwrite';
}
if (verified) {
addNotification({
type: 'success',
message: 'Domain added successfully'
});
} else {
addNotification({
type: 'info',
message: 'Verification in progress'
const apexDomain = getApexDomain(proxyRule.domain);
const domain = data.domainsList.domains.find((d) => d.domain === apexDomain);
if (isCloud && domain) {
await sdk.forConsole.domains.updateNameservers({
domainId: domain.$id
});
}
await goto(routeBase);
} catch (error) {
// Ignore error
}
try {
proxyRule = await sdk
.forProject(page.params.region, page.params.project)
.proxy.updateRuleVerification({ ruleId });
await invalidate(Dependencies.DOMAINS);
await goto(routeBase);
addNotification({
type: 'success',
message: 'Domain verified successfully'
});
} catch (error) {
verified = false;
isSubmitting.set(false);
@@ -108,7 +96,7 @@
.forProject(page.params.region, page.params.project)
.proxy.deleteRule({ ruleId });
}
await goto(`${routeBase}/add-domain?domain=${data.proxyRule.domain}`);
await goto(`${routeBase}/add-domain?domain=${proxyRule.domain}`);
}
</script>
@@ -125,7 +113,7 @@
<Icon icon={IconGlobeAlt} color="--fgcolor-neutral-primary" />
<Typography.Text variation="m-500" color="--fgcolor-neutral-primary">
{data.proxyRule.domain}
{proxyRule.domain}
</Typography.Text>
</Layout.Stack>
<Button secondary on:click={back}>Change</Button>
@@ -172,14 +160,17 @@
<Divider />
</div>
{#if selectedTab === 'nameserver'}
<NameserverTable domain={data.proxyRule.domain} {verified} />
<NameserverTable
{verified}
domain={proxyRule.domain}
ruleStatus={proxyRule.status} />
{:else}
<RecordTable
{verified}
service="general"
variant={selectedTab}
domain={data.proxyRule.domain}
ruleStatus={data.proxyRule.status}
domain={proxyRule.domain}
ruleStatus={proxyRule.status}
onNavigateToNameservers={() => (selectedTab = 'nameserver')}
onNavigateToA={() => (selectedTab = 'a')}
onNavigateToAAAA={() => (selectedTab = 'aaaa')} />
@@ -13,13 +13,16 @@
import { Divider, Tabs } from '@appwrite.io/pink-svelte';
import NameserverTable from '$lib/components/domains/nameserverTable.svelte';
import RecordTable from '$lib/components/domains/recordTable.svelte';
import { getApexDomain } from '$lib/helpers/tlds';
let {
show = $bindable(),
selectedProxyRule
selectedProxyRule,
domainsList
}: {
show: boolean;
selectedProxyRule: Models.ProxyRule;
domainsList?: Models.DomainsList;
} = $props();
const showCNAMETab = $derived(
@@ -40,43 +43,42 @@
let selectedTab = $state<'cname' | 'nameserver' | 'a' | 'aaaa'>(getDefaultTab());
let error = $state(null);
let verified = $state(false);
let verified: boolean | undefined = $state(undefined);
function getDefaultTab() {
return showCNAMETab ? 'cname' : showATab ? 'a' : showAAAATab ? 'aaaa' : 'nameserver';
}
async function retryDomain() {
error = null;
verified = undefined;
try {
error = null;
const proxyRule = await sdk
const apexDomain = getApexDomain(selectedProxyRule.domain);
const domain = domainsList?.domains.find((d) => d.domain === apexDomain);
if (isCloud && domain) {
await sdk.forConsole.domains.updateNameservers({
domainId: domain.$id
});
}
} catch {
// Ignore error
}
try {
selectedProxyRule = await sdk
.forProject(page.params.region, page.params.project)
.proxy.updateRuleVerification({ ruleId: selectedProxyRule.$id });
verified = proxyRule.status === 'verified';
await invalidate(Dependencies.DOMAINS);
// This means domain verification using DNS records hasn't succeeded and the rule is still in initial state.
if (proxyRule.status === 'created') {
throw new Error(
'Domain verification failed. Please check your domain settings or try again later'
);
}
if (verified) {
addNotification({
type: 'success',
message: `${selectedProxyRule.domain} has been verified`
});
} else {
addNotification({
type: 'info',
message: 'Verification in progress'
});
}
show = false;
addNotification({
type: 'success',
message: 'Domain verified successfully'
});
trackEvent(Submit.DomainUpdateVerification);
} catch (e) {
verified = false;
error =
e.message ??
'Domain verification failed. Please check your domain settings or try again later';
@@ -130,7 +132,10 @@
<Divider />
</div>
{#if selectedTab === 'nameserver'}
<NameserverTable domain={selectedProxyRule.domain} {verified} />
<NameserverTable
{verified}
domain={selectedProxyRule.domain}
ruleStatus={selectedProxyRule.status} />
{:else}
<RecordTable
{verified}
@@ -45,7 +45,7 @@
title: 'Domain',
type: 'string',
format: 'string',
width: { min: 300, max: 550 }
width: { min: 600 }
},
{
id: 'updated',
@@ -227,7 +227,7 @@
{/if}
{#if showRetry}
<RetryDomainModal bind:show={showRetry} {selectedProxyRule} />
<RetryDomainModal bind:show={showRetry} {selectedProxyRule} domainsList={organizationDomains} />
{/if}
{#if showLogs}
@@ -0,0 +1,102 @@
<script lang="ts">
import { onMount } from 'svelte';
import { invalidate } from '$app/navigation';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { CardGrid } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, InputTags } from '$lib/elements/forms';
import { symmetricDifference } from '$lib/helpers/array';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { project } from '../store';
import { Tag, Input, Layout, Icon } from '@appwrite.io/pink-svelte';
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
const alphaNumericRegExp = /^[a-zA-Z0-9]+$/;
let suggestedLabels = ['live', 'stage', 'internal'];
let labels: string[] = [];
let error = '';
onMount(async () => {
labels = [...$project.labels];
});
async function updateLabels() {
try {
await sdk.forConsole.projects.updateLabels({ projectId: $project.$id, labels });
await invalidate(Dependencies.PROJECT);
isDisabled = true;
addNotification({
type: 'success',
message: 'Project labels have been updated'
});
trackEvent(Submit.ProjectUpdateLabels);
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
trackError(error, Submit.ProjectUpdateLabels);
}
}
$: isDisabled = !!error || !symmetricDifference(labels, $project.labels).length;
$: if (labels) {
const invalidLabels = [];
labels.forEach((label) => {
if (!alphaNumericRegExp.test(label)) {
invalidLabels.push(label);
}
});
if (invalidLabels.length) {
error = `Invalid labels: ${invalidLabels.join(', ')}`;
} else {
error = '';
}
}
</script>
<Form onSubmit={updateLabels}>
<CardGrid>
<svelte:fragment slot="title">Labels</svelte:fragment>
Categorize and manage your projects for easy searching based on specific criteria by assigning
them customizable labels.
<svelte:fragment slot="aside">
<Layout.Stack gap="s">
{#key labels.length}
<InputTags
id="project-labels"
label="Labels"
placeholder="Select or type project labels"
bind:tags={labels} />
{/key}
<Layout.Stack direction="row">
{#each suggestedLabels as suggestedLabel}
<Tag
size="s"
selected={labels.includes(suggestedLabel)}
on:click={() => {
if (!labels.includes(suggestedLabel)) {
labels = [...labels, suggestedLabel];
} else {
labels = labels.filter((e) => e !== suggestedLabel);
}
}}>
<Icon icon={IconPlus} size="s" slot="start" />
{suggestedLabel}
</Tag>
{/each}
</Layout.Stack>
<Input.Helper state={error ? 'warning' : 'default'}
>{error ? error : 'Only alphanumeric characters are allowed'}</Input.Helper>
</Layout.Stack>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={isDisabled} submit>Update</Button>
</svelte:fragment>
</CardGrid>
</Form>
@@ -54,8 +54,7 @@
<svelte:fragment slot="menu" let:toggle>
<ActionMenu.Root>
{@const effectiveStatus = getEffectiveBuildStatus(
deployment.status,
deployment.$createdAt,
deployment,
$regionalConsoleVariables
)}
{#if !inCard}
@@ -6,6 +6,7 @@
case 'ready':
return 'success';
case 'building':
case 'finalizing':
return 'warning';
case 'processing':
return undefined;
@@ -40,9 +41,7 @@
emptyCopy?: string;
} = $props();
let effectiveStatus = $derived(
getEffectiveBuildStatus(deployment.status, deployment.$createdAt, $regionalConsoleVariables)
);
let effectiveStatus = $derived(getEffectiveBuildStatus(deployment, $regionalConsoleVariables));
function setCopy() {
if (effectiveStatus === 'failed') {
@@ -73,7 +72,7 @@
variant="secondary"
type={badgeTypeDeployment(effectiveStatus)} />
</Layout.Stack>
<LogsTimer status={effectiveStatus} {deployment} />
<LogsTimer {deployment} />
</Layout.Stack>
{/if}
@@ -7,15 +7,13 @@
import { Layout, Spinner, Typography } from '@appwrite.io/pink-svelte';
let { status, deployment }: { status: string; deployment: Models.Deployment } = $props();
let { deployment }: { deployment: Models.Deployment } = $props();
let effectiveStatus = $derived(
getEffectiveBuildStatus(status, deployment.$createdAt, $regionalConsoleVariables)
);
let effectiveStatus = $derived(getEffectiveBuildStatus(deployment, $regionalConsoleVariables));
</script>
<Layout.Stack direction="row" alignItems="center" inline>
{#if ['processing', 'building'].includes(effectiveStatus)}
{#if ['processing', 'building', 'finalizing'].includes(effectiveStatus)}
<Typography.Code color="--fgcolor-neutral-secondary">
<Layout.Stack direction="row" alignItems="center" inline>
<p use:timer={{ start: deployment.$createdAt }}></p>
@@ -42,9 +42,7 @@
footer?: Snippet;
} = $props();
let effectiveStatus = $derived(
getEffectiveBuildStatus(deployment.status, deployment.$createdAt, $regionalConsoleVariables)
);
let effectiveStatus = $derived(getEffectiveBuildStatus(deployment, $regionalConsoleVariables));
let show = $state(false);
const totalSize = $derived(humanFileSize(deployment?.totalSize ?? 0));
@@ -12,32 +12,76 @@
import { onMount } from 'svelte';
import { getFrameworkIcon } from '$lib/stores/sites';
import type { Models } from '@appwrite.io/console';
import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout';
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
let { data } = $props();
let deployment = $state(data.deployment);
let skipScreenshotTimeout: ReturnType<typeof setTimeout> | null = $state(null);
let effectiveStatus = $derived(getEffectiveBuildStatus(deployment, $regionalConsoleVariables));
onMount(() => {
return realtime.forConsole(page.params.region, 'console', async (response) => {
if (
response.events.includes(
`sites.${data.site.$id}.deployments.${data.deployment.$id}.update`
)
) {
deployment = response.payload as Models.Deployment;
if (deployment.status === 'ready') {
const resolvedUrl = resolve(
'/(console)/project-[region]-[project]/sites/create-site/finish',
{
region: page.params.region,
project: page.params.project
const timeoutCleanup = () => {
if (skipScreenshotTimeout) {
clearTimeout(skipScreenshotTimeout);
skipScreenshotTimeout = null;
}
};
const realtimeUnsubscribe = realtime.forConsole(
page.params.region,
'console',
async (response) => {
if (
response.events.includes(
`sites.${data.site.$id}.deployments.${data.deployment.$id}.update`
)
) {
deployment = response.payload as Models.Deployment;
const isReady = deployment.status === 'ready';
const isFinished =
isReady && deployment.screenshotLight && deployment.screenshotDark;
// Fallback mechanism
// If ready but not finished for over 30 seconds, go anyway
if (isReady && !skipScreenshotTimeout) {
skipScreenshotTimeout = setTimeout(async () => {
goToFinishScreen();
}, 30000);
}
if (isReady && isFinished) {
if (skipScreenshotTimeout) {
clearTimeout(skipScreenshotTimeout);
skipScreenshotTimeout = null;
}
);
await goto(`${resolvedUrl}?site=${data.site.$id}`);
goToFinishScreen();
}
}
}
});
);
return () => {
realtimeUnsubscribe();
timeoutCleanup();
};
});
async function goToFinishScreen() {
const resolvedUrl = resolve(
'/(console)/project-[region]-[project]/sites/create-site/finish',
{
region: page.params.region,
project: page.params.project
}
);
await goto(`${resolvedUrl}?site=${data.site.$id}`);
}
</script>
<Wizard
@@ -77,7 +121,7 @@
</svelte:fragment>
<svelte:fragment slot="footer">
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
{#if ['processing', 'building'].includes(data.deployment.status)}
{#if ['processing', 'building', 'finalizing'].includes(effectiveStatus)}
<Typography.Text variant="m-400" color="--fgcolor-neutral-tertiary">
Deployment will continue in the background
</Typography.Text>
@@ -26,9 +26,7 @@
let { data }: PageProps = $props();
let deployment = $derived(data.deployment);
let effectiveStatus = $derived(
getEffectiveBuildStatus(deployment.status, deployment.$createdAt, $regionalConsoleVariables)
);
let effectiveStatus = $derived(getEffectiveBuildStatus(deployment, $regionalConsoleVariables));
let showRedeploy = $state(false);
let showActivate = $state(false);
@@ -90,7 +88,7 @@
hideDivider>
<Logs {deployment} hideTitle fullHeight />
<svelte:fragment slot="end">
<LogsTimer status={effectiveStatus} {deployment} />
<LogsTimer {deployment} />
</svelte:fragment>
</Accordion>
</Card>
@@ -82,10 +82,10 @@
{#snippet children(root)}
{#each data.deploymentList.deployments as deployment (deployment.$id)}
{@const effectiveStatus = getEffectiveBuildStatus(
deployment.status,
deployment.$createdAt,
deployment,
$regionalConsoleVariables
)}
{@const displayStatus = effectiveStatus === 'finalizing' ? 'ready' : effectiveStatus}
<Table.Row.Link
{root}
id={deployment.$id}
@@ -101,8 +101,8 @@
<Status status="complete" label="Active" />
{:else}
<Status
status={deploymentStatusConverter(effectiveStatus)}
label={capitalize(effectiveStatus)} />
status={deploymentStatusConverter(displayStatus)}
label={capitalize(displayStatus)} />
{/if}
{:else if column.id === 'type'}
<DeploymentSource {deployment} />
@@ -66,6 +66,12 @@
<Table.Header.Cell {root} />
</svelte:fragment>
{#each deploymentList?.deployments as deployment (deployment.$id)}
{@const effectiveStatus = getEffectiveBuildStatus(
deployment,
$regionalConsoleVariables
)}
{@const displayStatus =
effectiveStatus === 'finalizing' ? 'ready' : effectiveStatus}
<Table.Row.Link
{root}
href={`${base}/project-${page.params.region}-${page.params.project}/sites/site-${page.params.site}/deployments/deployment-${deployment.$id}`}>
@@ -73,18 +79,12 @@
<Id value={deployment.$id}>{deployment.$id}</Id>
</Table.Cell>
<Table.Cell {root}>
{@const status = deployment.status}
{@const effectiveStatus = getEffectiveBuildStatus(
status,
deployment.$createdAt,
$regionalConsoleVariables
)}
{#if activeDeployment?.$id === deployment?.$id}
<Status status="complete" label="Active" />
{:else}
<Status
status={deploymentStatusConverter(effectiveStatus)}
label={capitalize(effectiveStatus)} />
status={deploymentStatusConverter(displayStatus)}
label={capitalize(displayStatus)} />
{/if}
</Table.Cell>
@@ -58,16 +58,24 @@
async function addDomain() {
const apexDomain = getApexDomain(domainName);
const isSiteDomain = domainName.endsWith($regionalConsoleVariables._APP_DOMAIN_SITES);
let domain = data.domainsList.domains.find((d) => d.domain === apexDomain);
const domain = data.domainsList.domains.find((d) => d.domain === apexDomain);
if (isCloud && apexDomain && !domain && !isSiteDomain) {
try {
domain = await sdk.forConsole.domains.create({
await sdk.forConsole.domains.create({
teamId: $project.teamId,
domain: apexDomain
});
} catch (error) {
// Apex domain creation error needs to be silent.
// apex might already be added on organization level, skip.
const alreadyAdded = error?.type === 'domain_already_exists';
if (!alreadyAdded) {
addNotification({
type: 'error',
message: error.message
});
return;
}
}
}
@@ -99,12 +107,18 @@
siteId: page.params.site
});
}
if (rule?.status === 'verified') {
await invalidate(Dependencies.SITES_DOMAINS);
const verified = rule?.status !== 'created';
if (verified) {
addNotification({
type: 'success',
message: 'Domain verified successfully'
});
await goto(routeBase);
await invalidate(Dependencies.SITES_DOMAINS);
} else {
await goto(`${routeBase}/add-domain/verify-${domainName}?rule=${rule.$id}`);
await invalidate(Dependencies.SITES_DOMAINS);
}
} catch (error) {
addNotification({
@@ -11,7 +11,6 @@
} from '@appwrite.io/pink-svelte';
import { Button, Form } from '$lib/elements/forms';
import { sdk } from '$lib/stores/sdk';
import { organization } from '$lib/stores/organization';
import { addNotification } from '$lib/stores/notifications';
import { goto, invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
@@ -23,6 +22,7 @@
import RecordTable from '$lib/components/domains/recordTable.svelte';
import NameserverTable from '$lib/components/domains/nameserverTable.svelte';
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
import { getApexDomain } from '$lib/helpers/tlds.js';
let { data } = $props();
@@ -44,6 +44,7 @@
);
const showNSTab = isCloud;
let proxyRule = $derived(data.proxyRule);
let selectedTab = $state<'cname' | 'nameserver' | 'a' | 'aaaa'>(getDefaultTab());
let routeBase = `${base}/project-${page.params.region}-${page.params.project}/sites/site-${page.params.site}/domains`;
let verified: boolean | undefined = $state(undefined);
@@ -54,44 +55,34 @@
}
async function verify() {
const isNewDomain =
data.domainsList.domains.findIndex((rule) => rule.domain === data.proxyRule.domain) ===
-1;
verified = undefined;
try {
if (selectedTab !== 'nameserver') {
const ruleData = await sdk
.forProject(page.params.region, page.params.project)
.proxy.updateRuleVerification({ ruleId });
verified = ruleData.status === 'verified';
// This means domain verification using DNS records hasn't succeeded and the rule is still in initial state.
if (ruleData.status === 'created') {
throw new Error(
'Domain verification failed. Please check your domain settings or try again later'
);
}
} else if (isNewDomain && isCloud) {
const domainData = await sdk.forConsole.domains.create({
teamId: $organization.$id,
domain: data.proxyRule.domain
});
verified = domainData.nameservers.toLowerCase() === 'appwrite';
}
if (verified) {
addNotification({
type: 'success',
message: 'Domain added successfully'
});
} else {
addNotification({
type: 'info',
message: 'Verification in progress'
const apexDomain = getApexDomain(proxyRule.domain);
const domain = data.domainsList.domains.find((d) => d.domain === apexDomain);
if (isCloud && domain) {
await sdk.forConsole.domains.updateNameservers({
domainId: domain.$id
});
}
} catch (error) {
// Ignore error
}
try {
proxyRule = await sdk
.forProject(page.params.region, page.params.project)
.proxy.updateRuleVerification({ ruleId });
await Promise.all([
invalidate(Dependencies.DOMAINS),
invalidate(Dependencies.SITES_DOMAINS)
]);
await goto(routeBase);
await invalidate(Dependencies.DOMAINS);
await invalidate(Dependencies.SITES_DOMAINS);
addNotification({
type: 'success',
message: 'Domain verified successfully'
});
} catch (error) {
verified = false;
isSubmitting.set(false);
@@ -108,7 +99,7 @@
.forProject(page.params.region, page.params.project)
.proxy.deleteRule({ ruleId });
}
await goto(`${routeBase}/add-domain?domain=${data.proxyRule.domain}`);
await goto(`${routeBase}/add-domain?domain=${proxyRule.domain}`);
}
</script>
@@ -125,7 +116,7 @@
<Icon icon={IconGlobeAlt} color="--fgcolor-neutral-primary" />
<Typography.Text variation="m-500" color="--fgcolor-neutral-primary">
{data.proxyRule.domain}
{proxyRule.domain}
</Typography.Text>
</Layout.Stack>
<Button secondary on:click={back}>Change</Button>
@@ -172,14 +163,17 @@
<Divider />
</div>
{#if selectedTab === 'nameserver'}
<NameserverTable domain={data.proxyRule.domain} {verified} />
<NameserverTable
{verified}
domain={proxyRule.domain}
ruleStatus={proxyRule.status} />
{:else}
<RecordTable
{verified}
service="sites"
variant={selectedTab}
domain={data.proxyRule.domain}
ruleStatus={data.proxyRule.status}
domain={proxyRule.domain}
ruleStatus={proxyRule.status}
onNavigateToNameservers={() => (selectedTab = 'nameserver')}
onNavigateToA={() => (selectedTab = 'a')}
onNavigateToAAAA={() => (selectedTab = 'aaaa')} />
@@ -13,13 +13,16 @@
import NameserverTable from '$lib/components/domains/nameserverTable.svelte';
import RecordTable from '$lib/components/domains/recordTable.svelte';
import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
import { getApexDomain } from '$lib/helpers/tlds';
let {
show = $bindable(false),
selectedProxyRule
selectedProxyRule,
domainsList
}: {
show: boolean;
selectedProxyRule: Models.ProxyRule;
domainsList?: Models.DomainsList;
} = $props();
const showCNAMETab = $derived(
@@ -40,43 +43,42 @@
let selectedTab = $state<'cname' | 'nameserver' | 'a' | 'aaaa'>(getDefaultTab());
let error = $state(null);
let verified = $state(false);
let verified: boolean | undefined = $state(undefined);
function getDefaultTab() {
return showCNAMETab ? 'cname' : showATab ? 'a' : showAAAATab ? 'aaaa' : 'nameserver';
}
async function retryDomain() {
error = null;
verified = undefined;
try {
error = null;
const proxyRule = await sdk
const apexDomain = getApexDomain(selectedProxyRule.domain);
const domain = domainsList?.domains.find((d) => d.domain === apexDomain);
if (isCloud && domain) {
await sdk.forConsole.domains.updateNameservers({
domainId: domain.$id
});
}
} catch {
// Ignore error
}
try {
selectedProxyRule = await sdk
.forProject(page.params.region, page.params.project)
.proxy.updateRuleVerification({ ruleId: selectedProxyRule.$id });
verified = proxyRule.status === 'verified';
await invalidate(Dependencies.SITES_DOMAINS);
// This means domain verification using DNS records hasn't succeeded and the rule is still in initial state.
if (proxyRule.status === 'created') {
throw new Error(
'Domain verification failed. Please check your domain settings or try again later'
);
}
if (verified) {
addNotification({
type: 'success',
message: `${selectedProxyRule.domain} has been verified`
});
} else {
addNotification({
type: 'info',
message: 'Verification in progress'
});
}
show = false;
addNotification({
type: 'success',
message: 'Domain verified successfully'
});
trackEvent(Submit.DomainUpdateVerification);
} catch (e) {
verified = false;
error =
e.message ??
'Domain verification failed. Please check your domain settings or try again later';
@@ -130,7 +132,10 @@
<Divider />
</div>
{#if selectedTab === 'nameserver'}
<NameserverTable domain={selectedProxyRule.domain} {verified} />
<NameserverTable
{verified}
domain={selectedProxyRule.domain}
ruleStatus={selectedProxyRule.status} />
{:else}
<RecordTable
{verified}
@@ -7,14 +7,14 @@ export const columns = writable<Column[]>([
title: 'Domain',
type: 'string',
format: 'string',
width: { min: 300 }
width: { min: 600 }
},
{
id: 'target',
title: 'Target',
type: 'string',
width: { min: 120, max: 400 }
width: { min: 160, max: 400 }
},
{
@@ -222,7 +222,7 @@
{/if}
{#if showRetry}
<RetryDomainModal bind:show={showRetry} {selectedProxyRule} />
<RetryDomainModal bind:show={showRetry} {selectedProxyRule} domainsList={organizationDomains} />
{/if}
{#if showLogs}
@@ -57,7 +57,7 @@
const res = await fetch(`${endpoint}/account/invite`, {
method: 'POST',
headers: {
'X-Appwrite-Project': project,
'X-Appwrite-Project': project as string,
'Content-Type': 'application/json'
},
body: JSON.stringify({
+1
View File
@@ -17,6 +17,7 @@
import { ThemeDark, ThemeLight, ThemeDarkCloud, ThemeLightCloud } from '../themes';
import { isSmallViewport, updateViewport } from '$lib/stores/viewport';
import { feedback } from '$lib/stores/feedback';
import '$lib/profiles/css/base.css';
function resolveTheme(theme: AppStore['themeInUse']) {
switch (theme) {