diff --git a/web/src/locales/de.ts b/web/src/locales/de.ts index 3ada0e7..5ec180b 100644 --- a/web/src/locales/de.ts +++ b/web/src/locales/de.ts @@ -1754,6 +1754,9 @@ export const catalog: Catalog = { // ── Spam filter: the language model's opinion (inbuxa) ────────── "Language model's opinion": "Einschätzung des Sprachmodells", "One of several signals the spam filter weighed": "Eines von mehreren Signalen, die der Spamfilter berücksichtigt hat", + // inbuxa: deleting a person is the console's (audit-hold-lock spec) + "Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "Löschen, Sperren und rechtliche Aufbewahrungspflichten werden in der Verwaltungskonsole erledigt, die den Grund festhält und bewahrt, was eine Aufbewahrungspflicht umfasst.", + "Open in the console": "In der Konsole öffnen", // inbuxa AL-7, AL-8: a locked account handed to the reader "You no longer have access to {name}. Back to your own mail.": "Sie haben keinen Zugriff mehr auf {name}. Zurück zu Ihren eigenen E-Mails.", "You can't send from {name}. It was handed to you to read, not to send as.": "Sie können nicht als {name} senden. Das Konto wurde Ihnen zum Lesen übergeben, nicht zum Senden.", diff --git a/web/src/locales/es.ts b/web/src/locales/es.ts index 19b32ce..96da847 100644 --- a/web/src/locales/es.ts +++ b/web/src/locales/es.ts @@ -1727,6 +1727,9 @@ export const catalog: Catalog = { // ── Spam filter: the language model's opinion (inbuxa) ────────── "Language model's opinion": "Opinión del modelo de lenguaje", "One of several signals the spam filter weighed": "Una de varias señales que el filtro de spam ha tenido en cuenta", + // inbuxa: deleting a person is the console's (audit-hold-lock spec) + "Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "Eliminar, bloquear y las retenciones legales se gestionan en la consola de administración, que registra el motivo y conserva lo que cubre una retención.", + "Open in the console": "Abrir en la consola", // inbuxa AL-7, AL-8: a locked account handed to the reader "You no longer have access to {name}. Back to your own mail.": "Ya no tiene acceso a {name}. Vuelve a su propio correo.", "You can't send from {name}. It was handed to you to read, not to send as.": "No puede enviar desde {name}. Se le entregó para leerla, no para enviar en su nombre.", diff --git a/web/src/locales/fr.ts b/web/src/locales/fr.ts index 3a37a94..bebce11 100644 --- a/web/src/locales/fr.ts +++ b/web/src/locales/fr.ts @@ -1732,6 +1732,9 @@ export const catalog: Catalog = { // ── Spam filter: the language model's opinion (inbuxa) ────────── "Language model's opinion": "Avis du modèle de langage", "One of several signals the spam filter weighed": "Un signal parmi d'autres pris en compte par le filtre antispam", + // inbuxa: deleting a person is the console's (audit-hold-lock spec) + "Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "La suppression, le verrouillage et les conservations légales se font dans la console d’administration, qui enregistre le motif et conserve ce qu’une conservation couvre.", + "Open in the console": "Ouvrir dans la console", // inbuxa AL-7, AL-8: a locked account handed to the reader "You no longer have access to {name}. Back to your own mail.": "Vous n’avez plus accès à {name}. Retour à votre propre courrier.", "You can't send from {name}. It was handed to you to read, not to send as.": "Vous ne pouvez pas envoyer depuis {name}. Ce compte vous a été confié pour le lire, pas pour envoyer en son nom.", diff --git a/web/src/locales/ja.ts b/web/src/locales/ja.ts index 517af6d..837347b 100644 --- a/web/src/locales/ja.ts +++ b/web/src/locales/ja.ts @@ -1735,6 +1735,9 @@ export const catalog: Catalog = { // ── Spam filter: the language model's opinion (inbuxa) ────────── "Language model's opinion": "言語モデルの見解", "One of several signals the spam filter weighed": "迷惑メールフィルターが考慮した複数の判断材料のひとつ", + // inbuxa: deleting a person is the console's (audit-hold-lock spec) + "Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "削除、ロック、訴訟ホールドは管理コンソールで行います。コンソールでは理由が記録され、ホールドの対象は保持されます。", + "Open in the console": "コンソールで開く", // inbuxa AL-7, AL-8: a locked account handed to the reader "You no longer have access to {name}. Back to your own mail.": "{name} にアクセスできなくなりました。自分のメールに戻ります。", "You can't send from {name}. It was handed to you to read, not to send as.": "{name} から送信することはできません。このアカウントは閲覧のために委任されています。", diff --git a/web/src/locales/nl.ts b/web/src/locales/nl.ts index 789f5b4..79645a8 100644 --- a/web/src/locales/nl.ts +++ b/web/src/locales/nl.ts @@ -1727,6 +1727,9 @@ export const catalog: Catalog = { // ── Spam filter: the language model's opinion (inbuxa) ────────── "Language model's opinion": "Oordeel van het taalmodel", "One of several signals the spam filter weighed": "Een van meerdere signalen die het spamfilter heeft meegewogen", + // inbuxa: deleting a person is the console's (audit-hold-lock spec) + "Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "Verwijderen, vergrendelen en juridische bewaarplichten gebeuren in de beheerconsole, die de reden vastlegt en bewaart wat onder een bewaarplicht valt.", + "Open in the console": "Openen in de console", // inbuxa AL-7, AL-8: a locked account handed to the reader "You no longer have access to {name}. Back to your own mail.": "U hebt geen toegang meer tot {name}. Terug naar uw eigen mail.", "You can't send from {name}. It was handed to you to read, not to send as.": "U kunt niet verzenden vanuit {name}. Het account is u gegeven om te lezen, niet om namens te verzenden.", diff --git a/web/src/locales/pt-BR.ts b/web/src/locales/pt-BR.ts index c396e48..5308219 100644 --- a/web/src/locales/pt-BR.ts +++ b/web/src/locales/pt-BR.ts @@ -1730,6 +1730,9 @@ export const catalog: Catalog = { // ── Spam filter: the language model's opinion (inbuxa) ────────── "Language model's opinion": "Opinião do modelo de linguagem", "One of several signals the spam filter weighed": "Um dos vários sinais considerados pelo filtro de spam", + // inbuxa: deleting a person is the console's (audit-hold-lock spec) + "Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "Excluir, bloquear e retenções legais são feitos no console de administração, que registra o motivo e preserva o que uma retenção abrange.", + "Open in the console": "Abrir no console", // inbuxa AL-7, AL-8: a locked account handed to the reader "You no longer have access to {name}. Back to your own mail.": "Você não tem mais acesso a {name}. De volta ao seu próprio e-mail.", "You can't send from {name}. It was handed to you to read, not to send as.": "Você não pode enviar de {name}. A conta foi entregue a você para leitura, não para enviar em nome dela.", diff --git a/web/src/locales/ru.ts b/web/src/locales/ru.ts index f4bbc00..fda1c8a 100644 --- a/web/src/locales/ru.ts +++ b/web/src/locales/ru.ts @@ -1729,6 +1729,9 @@ export const catalog: Catalog = { // ── Spam filter: the language model's opinion (inbuxa) ────────── "Language model's opinion": "Мнение языковой модели", "One of several signals the spam filter weighed": "Один из нескольких признаков, которые учёл спам-фильтр", + // inbuxa: deleting a person is the console's (audit-hold-lock spec) + "Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "Удаление, блокировка и юридическое удержание выполняются в консоли администрирования: она записывает причину и сохраняет всё, что охватывает удержание.", + "Open in the console": "Открыть в консоли", // inbuxa AL-7, AL-8: a locked account handed to the reader "You no longer have access to {name}. Back to your own mail.": "У вас больше нет доступа к {name}. Возвращаемся к вашей почте.", "You can't send from {name}. It was handed to you to read, not to send as.": "Вы не можете отправлять письма от имени {name}. Учётная запись передана вам для чтения, а не для отправки.", diff --git a/web/src/locales/uk.ts b/web/src/locales/uk.ts index e1b615c..0e30882 100644 --- a/web/src/locales/uk.ts +++ b/web/src/locales/uk.ts @@ -1723,6 +1723,9 @@ export const catalog: Catalog = { // ── Spam filter: the language model's opinion (inbuxa) ────────── "Language model's opinion": "Думка мовної моделі", "One of several signals the spam filter weighed": "Одна з кількох ознак, які врахував спам-фільтр", + // inbuxa: deleting a person is the console's (audit-hold-lock spec) + "Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "Видалення, блокування та юридичне утримання виконуються в консолі адміністрування: вона записує причину й зберігає все, що охоплює утримання.", + "Open in the console": "Відкрити в консолі", // inbuxa AL-7, AL-8: a locked account handed to the reader "You no longer have access to {name}. Back to your own mail.": "У вас більше немає доступу до {name}. Повертаємося до вашої пошти.", "You can't send from {name}. It was handed to you to read, not to send as.": "Ви не можете надсилати листи від імені {name}. Обліковий запис передано вам для читання, а не для надсилання.", diff --git a/web/src/locales/zh-Hans.ts b/web/src/locales/zh-Hans.ts index 85aada8..8ca8a54 100644 --- a/web/src/locales/zh-Hans.ts +++ b/web/src/locales/zh-Hans.ts @@ -1734,6 +1734,9 @@ export const catalog: Catalog = { // ── Spam filter: the language model's opinion (inbuxa) ────────── "Language model's opinion": "语言模型的判断", "One of several signals the spam filter weighed": "垃圾邮件过滤考虑的多个信号之一", + // inbuxa: deleting a person is the console's (audit-hold-lock spec) + "Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.": "删除、锁定和法律保留均在管理控制台中进行,控制台会记录原因,并保留保留范围内的内容。", + "Open in the console": "在控制台中打开", // inbuxa AL-7, AL-8: a locked account handed to the reader "You no longer have access to {name}. Back to your own mail.": "您已无法访问 {name}。已返回您自己的邮件。", "You can't send from {name}. It was handed to you to read, not to send as.": "您不能从 {name} 发送邮件。该账户交给您是为了阅读,而不是代其发送。", diff --git a/web/src/views/admin/AccountSheet.tsx b/web/src/views/admin/AccountSheet.tsx index 8cdb3fb..6a04173 100644 --- a/web/src/views/admin/AccountSheet.tsx +++ b/web/src/views/admin/AccountSheet.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState } from "react"; -import { Copy, Dices, KeyRound, Lock, Plus, Trash2, X } from "lucide-react"; +import { Copy, Dices, ExternalLink, KeyRound, Lock, Plus, X } from "lucide-react"; import { ADMIN_BASELINE, can, @@ -12,7 +12,6 @@ import { aliasList, createAccount, describeDirectoryError, - destroyAccount, hasPassword, passwordPatch, quotasWithDisk, @@ -25,7 +24,7 @@ import { formatSize } from "@/lib/format"; import { t, tNode } from "@/lib/i18n"; import { Link } from "wouter"; import { Avatar } from "@/ui/misc"; -import { Dialog } from "@/ui/dialog"; +import { useSession } from "@/store/session"; import { toast } from "@/ui/toast"; import { isSelf, roleName, type DirectoryContext } from "./directoryContext"; import { usePermissions } from "./usePermissions"; @@ -39,7 +38,6 @@ interface Props { onClose: () => void; onChanged: () => void; onCreated: (id: string) => void; - onDeleted: () => void; } /** A role as one select value: "User", "Admin", or "custom:". */ @@ -71,7 +69,7 @@ const bytesOf = (gib: string) => { * a password and a delete are their own calls, because each is a decision of * its own and should never ride along with a renamed display name. */ -export function AccountSheet({ account, ctx, onClose, onChanged, onCreated, onDeleted }: Props) { +export function AccountSheet({ account, ctx, onClose, onChanged, onCreated }: Props) { const perms = usePermissions(); const creating = account === null; const self = account ? isSelf(account, ctx) : false; @@ -288,9 +286,7 @@ export function AccountSheet({ account, ctx, onClose, onChanged, onCreated, onDe {error &&

{error}

} - {!creating && can(perms, "Account", "Destroy") && ( - - )} + {!creating && can(perms, "Account", "Destroy") && } {editable && ( @@ -433,59 +429,24 @@ export function Aliases({ aliases, setAliases, editable, domains, defaultDomain, ); } -function DeleteAccount({ account, blocked, onDeleted }: { account: DirectoryAccount; blocked: string | null; onDeleted: () => void }) { - const [open, setOpen] = useState(false); - const [typed, setTyped] = useState(""); - const [busy, setBusy] = useState(false); - const [error, setError] = useState(null); - const address = account.emailAddress ?? account.name; +/** + * inbuxa: deleting a person's account is the console's, beside locking it + * and legal holds: it asks why, for the audit log, and says when a hold + * keeps the data. Only the pointer is here. + */ +function DeleteInConsole({ accountId }: { accountId: string }) { + const adminUrl = useSession((s) => s.session?.ihasmail?.server?.adminUrl ?? null); return ( <>

{t("Delete")}

-

{blocked ?? t("Deletes the mailbox and everything in it.")}

- +

{t("Deleting, locking and legal holds are done in the administration console, which records why and keeps what a hold covers.")}

+ {adminUrl && ( + + {t("Open in the console")} + + )}
- setOpen(false)} - title={t("Delete {address}?", { address })} - size="sm" - footer={ - <> - - - - } - > -

{t("This deletes the mail, calendars, contacts and files in this account. The server removes them in the background, and it can't be undone.")}

-
- - setTyped(e.target.value)} /> -
- {error &&

{error}

} -
); } diff --git a/web/src/views/admin/AccountsAdmin.tsx b/web/src/views/admin/AccountsAdmin.tsx index ebb3661..297d842 100644 --- a/web/src/views/admin/AccountsAdmin.tsx +++ b/web/src/views/admin/AccountsAdmin.tsx @@ -217,10 +217,6 @@ export function AccountsAdmin({ selectedId }: { selectedId?: string }) { changed(); navigate(`/admin/accounts/${id}`); }} - onDeleted={() => { - changed(); - close(); - }} /> )} diff --git a/web/src/views/admin/__tests__/account-sheet.test.tsx b/web/src/views/admin/__tests__/account-sheet.test.tsx index 5b304bb..baf4e51 100644 --- a/web/src/views/admin/__tests__/account-sheet.test.tsx +++ b/web/src/views/admin/__tests__/account-sheet.test.tsx @@ -48,7 +48,7 @@ describe("the account sheet", () => { await act(async () => { root.render( - {}} onChanged={() => {}} onCreated={() => {}} onDeleted={() => {}} /> + {}} onChanged={() => {}} onCreated={() => {}} /> , ); }); @@ -89,7 +89,20 @@ describe("the account sheet", () => { expect(host.querySelector('a[href="/settings/security"]')).not.toBeNull(); expect(button(host, "Set a new password")).toBeUndefined(); expect((host.querySelector('select[aria-label="Role"]') as HTMLSelectElement).disabled).toBe(true); - expect(button(host, "Delete account")?.disabled).toBe(true); + expect(host.textContent).not.toContain("Delete account"); + expect(host.textContent).toContain("administration console"); + }); + + it("sends deleting a person to the console, opened on that account (inbuxa)", async () => { + signIn([...HELPDESK, "sysAccountDestroy"]); + const s = useSession.getState().session!; + useSession.setState({ + session: { ...s, ihasmail: { ...s.ihasmail, server: { ...(s.ihasmail?.server ?? {}), adminUrl: "https://admin.example.com/" } } } as never, + }); + await render(account({ id: "k7" })); + expect(host.textContent).not.toContain("Delete account"); + const link = host.querySelector('a[href="https://admin.example.com/Management/x:Account/User/k7"]'); + expect(link?.textContent).toContain("Open in the console"); }); }); @@ -108,7 +121,7 @@ describe("an account's tenant", () => { const render = async (a: DirectoryAccount) => { const { hook } = memoryLocation({ path: `/admin/accounts/${a.id}` }); await act(async () => { - root.render( {}} onChanged={() => {}} onCreated={() => {}} onDeleted={() => {}} />); + root.render( {}} onChanged={() => {}} onCreated={() => {}} />); }); }; beforeEach(() => {