@plutocms/supabase 0.15.0 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +6 -0
- package/CHANGELOG.md +14 -0
- package/app/components/access/DeleteRoleModal.vue +17 -4
- package/app/components/access/EditRolesSlideover.vue +11 -5
- package/app/components/access/InviteModal.vue +44 -19
- package/app/components/access/LinkField.vue +14 -3
- package/app/components/access/LinkModal.vue +5 -1
- package/app/components/access/RevokeModal.vue +17 -4
- package/app/components/access/RoleSlideover.vue +32 -11
- package/app/components/mail/AuthEmailsPanel.vue +87 -20
- package/app/components/migrations/MigrationsBanner.vue +5 -4
- package/app/composables/migrations.ts +13 -6
- package/app/pages/admin/(auth)/accept-invite.vue +21 -5
- package/app/pages/admin/(auth)/forgot-password.vue +11 -4
- package/app/pages/admin/(auth)/signup.vue +10 -9
- package/app/pages/admin/(auth)/update-password.vue +8 -3
- package/app/pages/admin/migrations.vue +8 -3
- package/app/pages/admin/settings/roles.vue +58 -15
- package/app/pages/admin/settings/users.vue +105 -30
- package/app/pages/admin/setup.vue +26 -7
- package/app/plugins/pluto-extension.ts +21 -8
- package/app/utils/access-ui.ts +29 -8
- package/app/utils/api-failure.ts +3 -1
- package/app/utils/auth-driver.ts +1 -3
- package/app/utils/auth-error.ts +1 -1
- package/app/utils/invite-link.ts +3 -3
- package/app/utils/supabase-project.ts +8 -3
- package/modules/pluto-migrations.ts +14 -3
- package/package.json +6 -4
- package/server/api/_pluto/supabase/access/users/[id]/resend.post.ts +15 -5
- package/server/api/_pluto/supabase/access/users/[id]/roles.put.ts +4 -1
- package/server/api/_pluto/supabase/access/users/invite-link.post.ts +12 -3
- package/server/api/_pluto/supabase/access/users/invite.post.ts +7 -2
- package/server/api/_pluto/supabase/auth/signup.post.ts +5 -1
- package/server/api/_pluto/supabase/me/capabilities.get.ts +4 -1
- package/server/api/_pluto/supabase/migrations/run.post.ts +9 -2
- package/server/api/_pluto/supabase/setup/index.post.ts +4 -1
- package/server/utils/access-errors.ts +2 -1
- package/server/utils/access-invite.ts +1 -1
- package/server/utils/access-mail.ts +72 -28
- package/server/utils/capability-guard.ts +5 -2
- package/server/utils/content-adapter.ts +75 -24
- package/server/utils/migrations.ts +0 -0
- package/server/utils/pending-migrations.ts +0 -0
- package/server/utils/service-client.ts +3 -1
- package/server/utils/sign-in-link.ts +25 -9
- package/server/utils/typegen.ts +6 -4
- package/server/utils/validate.ts +4 -1
- package/shared/types/supabase.ts +62 -65
- package/tailwind.lint.css +9 -0
package/.prettierignore
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.15.2](https://github.com/plutocms/supabase/compare/v0.15.1...v0.15.2) (2026-10-29)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **deps:** use @plutocms/pluto 0.32 ([#117](https://github.com/plutocms/supabase/issues/117)) ([51e841d](https://github.com/plutocms/supabase/commit/51e841dc33cd887b22e14d05335eaa75a953ed09))
|
|
9
|
+
|
|
10
|
+
## [0.15.1](https://github.com/plutocms/supabase/compare/v0.15.0...v0.15.1) (2026-10-28)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **deps:** use @plutocms/pluto 0.30; Prettier formatting and canonical Tailwind classes ([#111](https://github.com/plutocms/supabase/issues/111)) ([925c138](https://github.com/plutocms/supabase/commit/925c1386ad2c4380e2e3217766c0ae342d96cae8))
|
|
16
|
+
|
|
3
17
|
## [0.15.0](https://github.com/plutocms/supabase/compare/v0.14.1...v0.15.0) (2026-10-28)
|
|
4
18
|
|
|
5
19
|
|
|
@@ -23,7 +23,9 @@ async function remove() {
|
|
|
23
23
|
working.value = true
|
|
24
24
|
|
|
25
25
|
try {
|
|
26
|
-
await $fetch(`/api/_pluto/supabase/access/roles/${props.role.key}`, {
|
|
26
|
+
await $fetch(`/api/_pluto/supabase/access/roles/${props.role.key}`, {
|
|
27
|
+
method: 'DELETE',
|
|
28
|
+
})
|
|
27
29
|
|
|
28
30
|
toast.add({
|
|
29
31
|
title: t('supabase.access.toasts.roleDeleted'),
|
|
@@ -52,7 +54,9 @@ async function remove() {
|
|
|
52
54
|
>
|
|
53
55
|
<template #body>
|
|
54
56
|
<UAlert
|
|
55
|
-
:description="
|
|
57
|
+
:description="
|
|
58
|
+
t('supabase.access.deleteRole.usersLose', role.user_count)
|
|
59
|
+
"
|
|
56
60
|
:color="role.user_count > 0 ? 'warning' : 'neutral'"
|
|
57
61
|
variant="subtle"
|
|
58
62
|
icon="lucide:users"
|
|
@@ -61,10 +65,19 @@ async function remove() {
|
|
|
61
65
|
|
|
62
66
|
<template #footer>
|
|
63
67
|
<div class="flex w-full justify-end gap-2">
|
|
64
|
-
<UButton
|
|
68
|
+
<UButton
|
|
69
|
+
color="neutral"
|
|
70
|
+
variant="outline"
|
|
71
|
+
@click="emit('close', false)"
|
|
72
|
+
>
|
|
65
73
|
{{ t('pluto.common.cancel') }}
|
|
66
74
|
</UButton>
|
|
67
|
-
<UButton
|
|
75
|
+
<UButton
|
|
76
|
+
:loading="working"
|
|
77
|
+
color="error"
|
|
78
|
+
icon="lucide:trash-2"
|
|
79
|
+
@click="remove"
|
|
80
|
+
>
|
|
68
81
|
{{ t('supabase.access.deleteRole.confirm') }}
|
|
69
82
|
</UButton>
|
|
70
83
|
</div>
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
// roles of the signed-in admin loads the permissions again, so the menu and
|
|
4
4
|
// the tabs follow at once.
|
|
5
5
|
import type { AccessRole, AccessUser } from '../../../shared/types/access'
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
ADMIN_ROLE_KEY,
|
|
8
|
+
sameKeys,
|
|
9
|
+
userDisplayName,
|
|
10
|
+
} from '../../utils/access-ui'
|
|
7
11
|
|
|
8
12
|
const props = defineProps<{
|
|
9
13
|
user: AccessUser | null
|
|
@@ -43,14 +47,16 @@ const items = computed(() =>
|
|
|
43
47
|
)
|
|
44
48
|
|
|
45
49
|
const name = computed(() => (props.user ? userDisplayName(props.user) : ''))
|
|
46
|
-
const unchanged = computed(
|
|
50
|
+
const unchanged = computed(
|
|
51
|
+
() => !props.user || sameKeys(roleKeys.value, props.user.roles)
|
|
52
|
+
)
|
|
47
53
|
|
|
48
54
|
// The warning shows when the admin removes their own admin role.
|
|
49
55
|
const removesOwnAdmin = computed(
|
|
50
56
|
() =>
|
|
51
|
-
props.self
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
props.self &&
|
|
58
|
+
(props.user?.roles.includes(ADMIN_ROLE_KEY) ?? false) &&
|
|
59
|
+
!roleKeys.value.includes(ADMIN_ROLE_KEY)
|
|
54
60
|
)
|
|
55
61
|
|
|
56
62
|
async function save() {
|
|
@@ -85,16 +85,21 @@ async function submit() {
|
|
|
85
85
|
mailProblem.value = null
|
|
86
86
|
|
|
87
87
|
try {
|
|
88
|
-
const result = await $fetch<AccessInviteResult>(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
const result = await $fetch<AccessInviteResult>(
|
|
89
|
+
'/api/_pluto/supabase/access/users/invite',
|
|
90
|
+
{
|
|
91
|
+
method: 'POST',
|
|
92
|
+
body: { ...body(), locale: emailLocale.value },
|
|
93
|
+
}
|
|
94
|
+
)
|
|
92
95
|
|
|
93
96
|
emit('done')
|
|
94
97
|
|
|
95
98
|
if (result.status === 'granted') {
|
|
96
99
|
toast.add({
|
|
97
|
-
title: t('supabase.access.toasts.granted', {
|
|
100
|
+
title: t('supabase.access.toasts.granted', {
|
|
101
|
+
email: email.value.trim(),
|
|
102
|
+
}),
|
|
98
103
|
color: 'success',
|
|
99
104
|
icon: 'lucide:circle-check',
|
|
100
105
|
})
|
|
@@ -103,13 +108,17 @@ async function submit() {
|
|
|
103
108
|
// The user exists and has the roles. Only the email failed.
|
|
104
109
|
link.value = result.link
|
|
105
110
|
toast.add({
|
|
106
|
-
title: t('supabase.access.toasts.emailFailed', {
|
|
111
|
+
title: t('supabase.access.toasts.emailFailed', {
|
|
112
|
+
email: email.value.trim(),
|
|
113
|
+
}),
|
|
107
114
|
color: 'warning',
|
|
108
115
|
icon: 'lucide:triangle-alert',
|
|
109
116
|
})
|
|
110
117
|
} else {
|
|
111
118
|
toast.add({
|
|
112
|
-
title: t('supabase.access.toasts.invited', {
|
|
119
|
+
title: t('supabase.access.toasts.invited', {
|
|
120
|
+
email: email.value.trim(),
|
|
121
|
+
}),
|
|
113
122
|
color: 'success',
|
|
114
123
|
icon: 'lucide:circle-check',
|
|
115
124
|
})
|
|
@@ -139,10 +148,13 @@ async function copyLink() {
|
|
|
139
148
|
submitting.value = true
|
|
140
149
|
|
|
141
150
|
try {
|
|
142
|
-
const result = await $fetch<{
|
|
143
|
-
'
|
|
144
|
-
|
|
145
|
-
|
|
151
|
+
const result = await $fetch<{
|
|
152
|
+
status: 'invited' | 'granted'
|
|
153
|
+
link: string | null
|
|
154
|
+
}>('/api/_pluto/supabase/access/users/invite-link', {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
body: body(),
|
|
157
|
+
})
|
|
146
158
|
|
|
147
159
|
mailProblem.value = null
|
|
148
160
|
emit('done')
|
|
@@ -180,8 +192,16 @@ async function setupEmail() {
|
|
|
180
192
|
<template>
|
|
181
193
|
<UModal
|
|
182
194
|
v-model:open="open"
|
|
183
|
-
:title="
|
|
184
|
-
|
|
195
|
+
:title="
|
|
196
|
+
link
|
|
197
|
+
? t('supabase.access.invite.linkTitle')
|
|
198
|
+
: t('supabase.access.invite.title')
|
|
199
|
+
"
|
|
200
|
+
:description="
|
|
201
|
+
link
|
|
202
|
+
? t('supabase.access.invite.linkDescription')
|
|
203
|
+
: t('supabase.access.invite.description')
|
|
204
|
+
"
|
|
185
205
|
>
|
|
186
206
|
<template #body>
|
|
187
207
|
<AccessLinkField v-if="link" :link="link" />
|
|
@@ -219,10 +239,7 @@ async function setupEmail() {
|
|
|
219
239
|
</template>
|
|
220
240
|
</UAlert>
|
|
221
241
|
|
|
222
|
-
<p
|
|
223
|
-
v-if="mailProblem && !canCopyLink"
|
|
224
|
-
class="text-muted text-sm"
|
|
225
|
-
>
|
|
242
|
+
<p v-if="mailProblem && !canCopyLink" class="text-muted text-sm">
|
|
226
243
|
{{ t('supabase.access.invite.needsKey') }}
|
|
227
244
|
</p>
|
|
228
245
|
|
|
@@ -248,7 +265,11 @@ async function setupEmail() {
|
|
|
248
265
|
|
|
249
266
|
<UFormField
|
|
250
267
|
:label="t('supabase.access.invite.roles')"
|
|
251
|
-
:error="
|
|
268
|
+
:error="
|
|
269
|
+
showRolesError && roleKeys.length === 0
|
|
270
|
+
? t('supabase.access.invite.rolesRequired')
|
|
271
|
+
: undefined
|
|
272
|
+
"
|
|
252
273
|
required
|
|
253
274
|
>
|
|
254
275
|
<UCheckboxGroup v-model="roleKeys" :items="roleItems" />
|
|
@@ -258,7 +279,11 @@ async function setupEmail() {
|
|
|
258
279
|
:label="t('supabase.access.invite.locale')"
|
|
259
280
|
:description="t('supabase.access.invite.localeHint')"
|
|
260
281
|
>
|
|
261
|
-
<USelect
|
|
282
|
+
<USelect
|
|
283
|
+
v-model="emailLocale"
|
|
284
|
+
:items="localeItems"
|
|
285
|
+
class="w-full sm:w-64"
|
|
286
|
+
/>
|
|
262
287
|
</UFormField>
|
|
263
288
|
|
|
264
289
|
<!-- Enter in a field submits the form. -->
|
|
@@ -9,10 +9,21 @@ const { t } = usePlutoI18n()
|
|
|
9
9
|
</script>
|
|
10
10
|
|
|
11
11
|
<template>
|
|
12
|
-
<UFormField
|
|
13
|
-
|
|
12
|
+
<UFormField
|
|
13
|
+
:label="t('supabase.access.link.label')"
|
|
14
|
+
:help="t('supabase.access.link.note')"
|
|
15
|
+
>
|
|
16
|
+
<UInput
|
|
17
|
+
:model-value="link"
|
|
18
|
+
class="w-full"
|
|
19
|
+
readonly
|
|
20
|
+
@focus="($event.target as HTMLInputElement).select()"
|
|
21
|
+
>
|
|
14
22
|
<template #trailing>
|
|
15
|
-
<AccessCopyButton
|
|
23
|
+
<AccessCopyButton
|
|
24
|
+
:text="link"
|
|
25
|
+
:label="t('supabase.access.link.copy')"
|
|
26
|
+
/>
|
|
16
27
|
</template>
|
|
17
28
|
</UInput>
|
|
18
29
|
</UFormField>
|
|
@@ -20,7 +20,11 @@ const { t } = usePlutoI18n()
|
|
|
20
20
|
|
|
21
21
|
<template #footer>
|
|
22
22
|
<div class="flex w-full justify-end">
|
|
23
|
-
<UButton
|
|
23
|
+
<UButton
|
|
24
|
+
color="neutral"
|
|
25
|
+
variant="outline"
|
|
26
|
+
@click="emit('close', false)"
|
|
27
|
+
>
|
|
24
28
|
{{ t('pluto.common.close') }}
|
|
25
29
|
</UButton>
|
|
26
30
|
</div>
|
|
@@ -27,7 +27,9 @@ async function revoke() {
|
|
|
27
27
|
working.value = true
|
|
28
28
|
|
|
29
29
|
try {
|
|
30
|
-
await $fetch(`/api/_pluto/supabase/access/users/${props.user.id}/roles`, {
|
|
30
|
+
await $fetch(`/api/_pluto/supabase/access/users/${props.user.id}/roles`, {
|
|
31
|
+
method: 'DELETE',
|
|
32
|
+
})
|
|
31
33
|
|
|
32
34
|
toast.add({
|
|
33
35
|
title: t('supabase.access.toasts.revoked'),
|
|
@@ -56,7 +58,9 @@ async function revoke() {
|
|
|
56
58
|
<template>
|
|
57
59
|
<UModal
|
|
58
60
|
:title="t('supabase.access.revoke.title')"
|
|
59
|
-
:description="
|
|
61
|
+
:description="
|
|
62
|
+
t('supabase.access.revoke.body', { name: userDisplayName(user) })
|
|
63
|
+
"
|
|
60
64
|
:close="{ onClick: () => emit('close', false) }"
|
|
61
65
|
>
|
|
62
66
|
<template #body>
|
|
@@ -71,10 +75,19 @@ async function revoke() {
|
|
|
71
75
|
|
|
72
76
|
<template #footer>
|
|
73
77
|
<div class="flex w-full justify-end gap-2">
|
|
74
|
-
<UButton
|
|
78
|
+
<UButton
|
|
79
|
+
color="neutral"
|
|
80
|
+
variant="outline"
|
|
81
|
+
@click="emit('close', false)"
|
|
82
|
+
>
|
|
75
83
|
{{ t('pluto.common.cancel') }}
|
|
76
84
|
</UButton>
|
|
77
|
-
<UButton
|
|
85
|
+
<UButton
|
|
86
|
+
:loading="working"
|
|
87
|
+
color="error"
|
|
88
|
+
icon="lucide:user-x"
|
|
89
|
+
@click="revoke"
|
|
90
|
+
>
|
|
78
91
|
{{ t('supabase.access.revoke.confirm') }}
|
|
79
92
|
</UButton>
|
|
80
93
|
</div>
|
|
@@ -94,7 +94,10 @@ function groupValue(group: CapabilityGroup): string[] {
|
|
|
94
94
|
function setGroup(group: CapabilityGroup, value: string[]) {
|
|
95
95
|
const keys = new Set(group.items.map((item) => item.key))
|
|
96
96
|
|
|
97
|
-
selected.value = [
|
|
97
|
+
selected.value = [
|
|
98
|
+
...selected.value.filter((current) => !keys.has(current)),
|
|
99
|
+
...value,
|
|
100
|
+
]
|
|
98
101
|
}
|
|
99
102
|
|
|
100
103
|
const keyError = computed(() =>
|
|
@@ -112,11 +115,11 @@ async function save() {
|
|
|
112
115
|
submitted.value = true
|
|
113
116
|
|
|
114
117
|
if (
|
|
115
|
-
saving.value
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
saving.value ||
|
|
119
|
+
fullAccess.value ||
|
|
120
|
+
!label.value.trim() ||
|
|
121
|
+
keyError.value ||
|
|
122
|
+
capabilitiesError.value
|
|
120
123
|
) {
|
|
121
124
|
return
|
|
122
125
|
}
|
|
@@ -136,7 +139,10 @@ async function save() {
|
|
|
136
139
|
body: { ...body, key: key.value },
|
|
137
140
|
})
|
|
138
141
|
} else {
|
|
139
|
-
await $fetch(`/api/_pluto/supabase/access/roles/${props.role!.key}`, {
|
|
142
|
+
await $fetch(`/api/_pluto/supabase/access/roles/${props.role!.key}`, {
|
|
143
|
+
method: 'PUT',
|
|
144
|
+
body,
|
|
145
|
+
})
|
|
140
146
|
}
|
|
141
147
|
|
|
142
148
|
toast.add({
|
|
@@ -180,7 +186,11 @@ async function save() {
|
|
|
180
186
|
<form v-else class="flex flex-col gap-y-5" @submit.prevent="save">
|
|
181
187
|
<UFormField
|
|
182
188
|
:label="t('supabase.access.role.label')"
|
|
183
|
-
:error="
|
|
189
|
+
:error="
|
|
190
|
+
submitted && !label.trim()
|
|
191
|
+
? t('supabase.access.errors.invalidLabel')
|
|
192
|
+
: undefined
|
|
193
|
+
"
|
|
184
194
|
required
|
|
185
195
|
>
|
|
186
196
|
<UInput
|
|
@@ -222,8 +232,14 @@ async function save() {
|
|
|
222
232
|
required
|
|
223
233
|
>
|
|
224
234
|
<div class="flex flex-col gap-y-4">
|
|
225
|
-
<div
|
|
226
|
-
|
|
235
|
+
<div
|
|
236
|
+
v-for="group in groups"
|
|
237
|
+
:key="group.id"
|
|
238
|
+
class="flex flex-col gap-y-2"
|
|
239
|
+
>
|
|
240
|
+
<h3 class="text-highlighted text-sm font-semibold">
|
|
241
|
+
{{ group.label }}
|
|
242
|
+
</h3>
|
|
227
243
|
<UCheckboxGroup
|
|
228
244
|
:model-value="groupValue(group)"
|
|
229
245
|
:items="groupItems(group)"
|
|
@@ -242,7 +258,12 @@ async function save() {
|
|
|
242
258
|
<UButton color="neutral" variant="outline" @click="open = false">
|
|
243
259
|
{{ fullAccess ? t('pluto.common.close') : t('pluto.common.cancel') }}
|
|
244
260
|
</UButton>
|
|
245
|
-
<UButton
|
|
261
|
+
<UButton
|
|
262
|
+
v-if="!fullAccess"
|
|
263
|
+
:loading="saving"
|
|
264
|
+
icon="lucide:save"
|
|
265
|
+
@click="save"
|
|
266
|
+
>
|
|
246
267
|
{{ t('supabase.access.role.save') }}
|
|
247
268
|
</UButton>
|
|
248
269
|
</div>
|
|
@@ -12,24 +12,68 @@ import {
|
|
|
12
12
|
|
|
13
13
|
const { t } = usePlutoI18n()
|
|
14
14
|
|
|
15
|
-
const projectRef = computed(() =>
|
|
16
|
-
|
|
15
|
+
const projectRef = computed(() =>
|
|
16
|
+
supabaseProjectRef(useRuntimeConfig().public.supabase?.url)
|
|
17
|
+
)
|
|
18
|
+
const links = computed(() =>
|
|
19
|
+
projectRef.value ? supabaseDashboardLinks(projectRef.value) : null
|
|
20
|
+
)
|
|
17
21
|
|
|
18
22
|
const smtpRows = computed(() => [
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
{
|
|
24
|
+
key: 'host',
|
|
25
|
+
label: t('supabase.mail.authEmails.host'),
|
|
26
|
+
value: RESEND_SMTP.host,
|
|
27
|
+
copy: RESEND_SMTP.host,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: 'port',
|
|
31
|
+
label: t('supabase.mail.authEmails.port'),
|
|
32
|
+
value: RESEND_SMTP.port,
|
|
33
|
+
copy: RESEND_SMTP.port,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
key: 'username',
|
|
37
|
+
label: t('supabase.mail.authEmails.username'),
|
|
38
|
+
value: RESEND_SMTP.username,
|
|
39
|
+
copy: RESEND_SMTP.username,
|
|
40
|
+
},
|
|
22
41
|
// The password is the API key. Pluto never shows it, so there is nothing to copy.
|
|
23
|
-
{
|
|
42
|
+
{
|
|
43
|
+
key: 'password',
|
|
44
|
+
label: t('supabase.mail.authEmails.password'),
|
|
45
|
+
value: t('supabase.mail.authEmails.passwordValue'),
|
|
46
|
+
copy: null,
|
|
47
|
+
},
|
|
24
48
|
])
|
|
25
49
|
|
|
26
50
|
const dashboardLinks = computed(() =>
|
|
27
51
|
links.value
|
|
28
52
|
? [
|
|
29
|
-
{
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
53
|
+
{
|
|
54
|
+
key: 'smtp',
|
|
55
|
+
label: t('supabase.mail.authEmails.dashboardSmtp'),
|
|
56
|
+
to: links.value.smtp,
|
|
57
|
+
icon: 'lucide:mail',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: 'templates',
|
|
61
|
+
label: t('supabase.mail.authEmails.dashboardTemplates'),
|
|
62
|
+
to: links.value.templates,
|
|
63
|
+
icon: 'lucide:layout-template',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
key: 'rateLimits',
|
|
67
|
+
label: t('supabase.mail.authEmails.dashboardRateLimits'),
|
|
68
|
+
to: links.value.rateLimits,
|
|
69
|
+
icon: 'lucide:gauge',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
key: 'project',
|
|
73
|
+
label: t('supabase.mail.authEmails.dashboardHome'),
|
|
74
|
+
to: links.value.project,
|
|
75
|
+
icon: 'lucide:external-link',
|
|
76
|
+
},
|
|
33
77
|
]
|
|
34
78
|
: []
|
|
35
79
|
)
|
|
@@ -40,8 +84,12 @@ const dashboardLinks = computed(() =>
|
|
|
40
84
|
<p class="text-muted text-sm">{{ t('supabase.mail.authEmails.intro') }}</p>
|
|
41
85
|
|
|
42
86
|
<section class="flex flex-col gap-y-2">
|
|
43
|
-
<h3 class="text-highlighted text-sm font-semibold">
|
|
44
|
-
|
|
87
|
+
<h3 class="text-highlighted text-sm font-semibold">
|
|
88
|
+
{{ t('supabase.mail.authEmails.resendTitle') }}
|
|
89
|
+
</h3>
|
|
90
|
+
<p class="text-muted text-sm">
|
|
91
|
+
{{ t('supabase.mail.authEmails.resendBody') }}
|
|
92
|
+
</p>
|
|
45
93
|
<div>
|
|
46
94
|
<UButton
|
|
47
95
|
:to="RESEND_SUPABASE_INTEGRATION_URL"
|
|
@@ -57,24 +105,41 @@ const dashboardLinks = computed(() =>
|
|
|
57
105
|
</section>
|
|
58
106
|
|
|
59
107
|
<section class="flex flex-col gap-y-2">
|
|
60
|
-
<h3 class="text-highlighted text-sm font-semibold">
|
|
61
|
-
|
|
108
|
+
<h3 class="text-highlighted text-sm font-semibold">
|
|
109
|
+
{{ t('supabase.mail.authEmails.manualTitle') }}
|
|
110
|
+
</h3>
|
|
111
|
+
<p class="text-muted text-sm">
|
|
112
|
+
{{ t('supabase.mail.authEmails.manualBody') }}
|
|
113
|
+
</p>
|
|
62
114
|
|
|
63
|
-
<ul
|
|
64
|
-
|
|
115
|
+
<ul
|
|
116
|
+
class="border-default bg-elevated/50 divide-default divide-y rounded-md border"
|
|
117
|
+
>
|
|
118
|
+
<li
|
|
119
|
+
v-for="row in smtpRows"
|
|
120
|
+
:key="row.key"
|
|
121
|
+
class="flex items-center gap-3 px-3 py-2"
|
|
122
|
+
>
|
|
65
123
|
<span class="text-muted w-24 shrink-0 text-sm">{{ row.label }}</span>
|
|
66
|
-
<code
|
|
124
|
+
<code
|
|
125
|
+
class="min-w-0 flex-1 overflow-x-auto text-sm whitespace-nowrap"
|
|
126
|
+
>{{ row.value }}</code
|
|
127
|
+
>
|
|
67
128
|
<AccessCopyButton
|
|
68
129
|
v-if="row.copy"
|
|
69
130
|
:text="row.copy"
|
|
70
|
-
:label="
|
|
131
|
+
:label="
|
|
132
|
+
t('supabase.mail.authEmails.copyValue', { label: row.label })
|
|
133
|
+
"
|
|
71
134
|
/>
|
|
72
135
|
</li>
|
|
73
136
|
</ul>
|
|
74
137
|
</section>
|
|
75
138
|
|
|
76
139
|
<section class="flex flex-col gap-y-2">
|
|
77
|
-
<h3 class="text-highlighted text-sm font-semibold">
|
|
140
|
+
<h3 class="text-highlighted text-sm font-semibold">
|
|
141
|
+
{{ t('supabase.mail.authEmails.dashboardTitle') }}
|
|
142
|
+
</h3>
|
|
78
143
|
|
|
79
144
|
<div v-if="dashboardLinks.length > 0" class="flex flex-wrap gap-2">
|
|
80
145
|
<UButton
|
|
@@ -91,7 +156,9 @@ const dashboardLinks = computed(() =>
|
|
|
91
156
|
{{ link.label }}
|
|
92
157
|
</UButton>
|
|
93
158
|
</div>
|
|
94
|
-
<p v-else class="text-muted text-sm">
|
|
159
|
+
<p v-else class="text-muted text-sm">
|
|
160
|
+
{{ t('supabase.mail.authEmails.noProject') }}
|
|
161
|
+
</p>
|
|
95
162
|
|
|
96
163
|
<p v-if="dashboardLinks.length > 0" class="text-muted text-xs">
|
|
97
164
|
{{ t('supabase.mail.authEmails.dashboardNote') }}
|
|
@@ -14,9 +14,9 @@ const title = computed(() =>
|
|
|
14
14
|
|
|
15
15
|
const isVisible = computed(
|
|
16
16
|
() =>
|
|
17
|
-
Boolean(status.value)
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
Boolean(status.value) &&
|
|
18
|
+
pendingCount.value > 0 &&
|
|
19
|
+
!route.path.startsWith('/admin/migrations')
|
|
20
20
|
)
|
|
21
21
|
</script>
|
|
22
22
|
|
|
@@ -30,7 +30,8 @@ const isVisible = computed(
|
|
|
30
30
|
icon="lucide:triangle-alert"
|
|
31
31
|
>
|
|
32
32
|
<template #description>
|
|
33
|
-
<ULink to="/admin/migrations" class="underline">
|
|
33
|
+
<ULink to="/admin/migrations" class="underline">
|
|
34
|
+
{{ t('supabase.migrations.goTo') }} </ULink
|
|
34
35
|
>.
|
|
35
36
|
</template>
|
|
36
37
|
</UAlert>
|
|
@@ -73,10 +73,13 @@ export function useMigrations() {
|
|
|
73
73
|
let result: RunMigrationsResponse
|
|
74
74
|
|
|
75
75
|
try {
|
|
76
|
-
result = await $fetch<RunMigrationsResponse>(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
result = await $fetch<RunMigrationsResponse>(
|
|
77
|
+
'/api/_pluto/supabase/migrations/run',
|
|
78
|
+
{
|
|
79
|
+
method: 'POST',
|
|
80
|
+
body: connectionString ? { connectionString } : undefined,
|
|
81
|
+
}
|
|
82
|
+
)
|
|
80
83
|
} catch (error) {
|
|
81
84
|
// The route answers a failed run with an HTTP error that carries
|
|
82
85
|
// `needsConnectionString` or `error` in its data. Turn it back into
|
|
@@ -84,13 +87,17 @@ export function useMigrations() {
|
|
|
84
87
|
// (for example a dropped connection) goes on to the caller.
|
|
85
88
|
const failure = failureData(error)
|
|
86
89
|
|
|
87
|
-
if (
|
|
90
|
+
if (
|
|
91
|
+
failure?.needsConnectionString !== true &&
|
|
92
|
+
typeof failure?.error !== 'string'
|
|
93
|
+
) {
|
|
88
94
|
throw error
|
|
89
95
|
}
|
|
90
96
|
|
|
91
97
|
result = {
|
|
92
98
|
success: false,
|
|
93
|
-
needsConnectionString:
|
|
99
|
+
needsConnectionString:
|
|
100
|
+
failure.needsConnectionString === true ? true : undefined,
|
|
94
101
|
error: typeof failure.error === 'string' ? failure.error : undefined,
|
|
95
102
|
}
|
|
96
103
|
}
|
|
@@ -86,11 +86,23 @@ async function submitForm() {
|
|
|
86
86
|
|
|
87
87
|
<template>
|
|
88
88
|
<PlutoAdminAuthCard
|
|
89
|
-
:title="
|
|
90
|
-
|
|
89
|
+
:title="
|
|
90
|
+
state === 'ready'
|
|
91
|
+
? t('supabase.auth.invite.title')
|
|
92
|
+
: t('supabase.auth.invite.pageTitle')
|
|
93
|
+
"
|
|
94
|
+
:description="
|
|
95
|
+
state === 'ready' ? t('supabase.auth.invite.description') : undefined
|
|
96
|
+
"
|
|
91
97
|
>
|
|
92
|
-
<div
|
|
93
|
-
|
|
98
|
+
<div
|
|
99
|
+
v-if="state === 'checking'"
|
|
100
|
+
class="flex items-center justify-center py-10"
|
|
101
|
+
>
|
|
102
|
+
<UIcon
|
|
103
|
+
name="lucide:loader-circle"
|
|
104
|
+
class="size-6 animate-spin text-muted"
|
|
105
|
+
/>
|
|
94
106
|
</div>
|
|
95
107
|
|
|
96
108
|
<div v-else-if="state !== 'ready'" class="space-y-6 py-10 text-center">
|
|
@@ -99,7 +111,11 @@ async function submitForm() {
|
|
|
99
111
|
</span>
|
|
100
112
|
|
|
101
113
|
<p>
|
|
102
|
-
{{
|
|
114
|
+
{{
|
|
115
|
+
state === 'expired'
|
|
116
|
+
? t('supabase.auth.invite.expired')
|
|
117
|
+
: t('supabase.auth.invite.invalid')
|
|
118
|
+
}}
|
|
103
119
|
</p>
|
|
104
120
|
|
|
105
121
|
<div class="flex items-center justify-center">
|