diff --git a/web/src/jmap/__tests__/legacy-partly-off.test.ts b/web/src/jmap/__tests__/legacy-partly-off.test.ts
new file mode 100644
index 0000000..0fca462
--- /dev/null
+++ b/web/src/jmap/__tests__/legacy-partly-off.test.ts
@@ -0,0 +1,42 @@
+import { describe, expect, it } from "vitest";
+import { INBUXA_CAP, legacyProtocolsPartlyOff } from "../client";
+import { parseTenantLegacy } from "@/lib/admin/adminLegacyProtocols";
+import type { JmapSession } from "../types";
+
+/**
+ * INBUXA can switch IMAP, POP3 and ManageSieve off one at a time. The session
+ * lists what is still allowed (`legacyAllowed`); the webmail names what isn't,
+ * only while some but not all are off, and never from a server that doesn't
+ * say.
+ */
+describe("legacyProtocolsPartlyOff", () => {
+ const session = (cap: Record | undefined) =>
+ ({
+ accounts: { a: { name: "u@example.org", accountCapabilities: cap ? { [INBUXA_CAP]: cap } : {} } },
+ }) as unknown as JmapSession;
+
+ it("names what is off when only some are", () => {
+ const s = session({ legacyProtocols: "enabled", legacyAllowed: ["imap", "manageSieve", "submission"] });
+ expect(legacyProtocolsPartlyOff(s, "a")).toEqual(["POP3"]);
+ const two = session({ legacyProtocols: "enabled", legacyAllowed: ["pop3", "submission"] });
+ expect(legacyProtocolsPartlyOff(two, "a")).toEqual(["IMAP", "ManageSieve"]);
+ });
+
+ it("is empty with none off, all off, or an older server", () => {
+ const all = ["imap", "pop3", "manageSieve", "submission"];
+ expect(legacyProtocolsPartlyOff(session({ legacyProtocols: "enabled", legacyAllowed: all }), "a")).toEqual([]);
+ expect(legacyProtocolsPartlyOff(session({ legacyProtocols: "disabled", legacyAllowed: [] }), "a")).toEqual([]);
+ expect(legacyProtocolsPartlyOff(session({ legacyProtocols: "enabled" }), "a")).toEqual([]);
+ expect(legacyProtocolsPartlyOff(null, "a")).toEqual([]);
+ });
+});
+
+describe("parseTenantLegacy", () => {
+ it("reads a tenant with only some off, and an older server's one switch", () => {
+ expect(parseTenantLegacy({ legacyProtocols: "enabled", pop3: "disabled" }).partlyOff).toEqual(["POP3"]);
+ const all = parseTenantLegacy({ legacyProtocols: "disabled", imap: "disabled", pop3: "disabled" });
+ expect(all.off).toBe(true);
+ expect(all.partlyOff).toEqual([]);
+ expect(parseTenantLegacy({ legacyProtocols: "enabled" }).partlyOff).toEqual([]);
+ });
+});
diff --git a/web/src/jmap/client.ts b/web/src/jmap/client.ts
index 7427f9c..0816147 100644
--- a/web/src/jmap/client.ts
+++ b/web/src/jmap/client.ts
@@ -37,6 +37,24 @@ export function legacyProtocolsOff(session: JmapSession | null, accountId: Id |
return cap?.legacyProtocols === "disabled";
}
+/** The protocols the server can switch off one at a time, as it names them. */
+const SWITCHED = ["imap", "pop3", "manageSieve"] as const;
+const PROTOCOL_NAMES: Record = { imap: "IMAP", pop3: "POP3", manageSieve: "ManageSieve" };
+
+/**
+ * Which of IMAP, POP3 and ManageSieve are off for this account, by name, when
+ * only some are (INBUXA legacy-protocols, one switch per protocol). Empty when
+ * none are, when all are (see `legacyProtocolsOff`), and from a server that
+ * doesn't say which (`legacyAllowed`).
+ */
+export function legacyProtocolsPartlyOff(session: JmapSession | null, accountId: Id | null): string[] {
+ if (!session || !accountId || legacyProtocolsOff(session, accountId)) return [];
+ const cap = session.accounts[accountId]?.accountCapabilities?.[INBUXA_CAP] as { legacyAllowed?: unknown } | undefined;
+ if (!Array.isArray(cap?.legacyAllowed)) return [];
+ const allowed = cap.legacyAllowed;
+ return SWITCHED.filter((p) => !allowed.includes(p)).map((p) => PROTOCOL_NAMES[p] ?? p);
+}
+
export class JmapMethodError extends Error {
constructor(
public readonly method: string,
diff --git a/web/src/lib/admin/__tests__/adminLegacyProtocols.test.ts b/web/src/lib/admin/__tests__/adminLegacyProtocols.test.ts
index 8dfe02e..f63ba47 100644
--- a/web/src/lib/admin/__tests__/adminLegacyProtocols.test.ts
+++ b/web/src/lib/admin/__tests__/adminLegacyProtocols.test.ts
@@ -3,8 +3,8 @@ import { CONFIRM_PHRASE, impactEntries, parseTenantLegacy, phraseMatches } from
describe("a tenant's legacy mail protocols switch, as the server sends it", () => {
it("reads the switch, and tells an older server from nobody", () => {
- expect(parseTenantLegacy({ legacyProtocols: "disabled", recentLegacyUse: [] })).toEqual({ off: true, recent: [] });
- expect(parseTenantLegacy({ legacyProtocols: "enabled" })).toEqual({ off: false, recent: null });
+ expect(parseTenantLegacy({ legacyProtocols: "disabled", recentLegacyUse: [] })).toEqual({ off: true, partlyOff: [], recent: [] });
+ expect(parseTenantLegacy({ legacyProtocols: "enabled" })).toEqual({ off: false, partlyOff: [], recent: null });
});
it("puts each account on the panel once, with every protocol and its latest use", () => {
diff --git a/web/src/lib/admin/adminLegacyProtocols.ts b/web/src/lib/admin/adminLegacyProtocols.ts
index dfa03eb..e534dfa 100644
--- a/web/src/lib/admin/adminLegacyProtocols.ts
+++ b/web/src/lib/admin/adminLegacyProtocols.ts
@@ -34,7 +34,10 @@ export interface RecentUse {
}
export interface TenantLegacy {
+ /** All of IMAP, POP3 and ManageSieve off: the kill-all's state. */
off: boolean;
+ /** When only some are off, which, by name; set one at a time in the console. */
+ partlyOff: string[];
/** Null from a server too old to say who uses legacy apps -- not the same as nobody. */
recent: RecentUse[] | null;
}
@@ -49,8 +52,13 @@ function parseRecent(raw: unknown): RecentUse[] | null {
});
}
+const PROTOCOL_NAMES: [string, string][] = [["imap", "IMAP"], ["pop3", "POP3"], ["manageSieve", "ManageSieve"]];
+
export function parseTenantLegacy(raw: Record): TenantLegacy {
- return { off: raw.legacyProtocols === "disabled", recent: parseRecent(raw.recentLegacyUse) };
+ const off = raw.legacyProtocols === "disabled";
+ // An older server sends only legacyProtocols, which stands for all three.
+ const partlyOff = off ? [] : PROTOCOL_NAMES.filter(([key]) => raw[key] === "disabled").map(([, name]) => name);
+ return { off, partlyOff, recent: parseRecent(raw.recentLegacyUse) };
}
/** The tenant's switch, or null where the server has none (not INBUXA, or too old). */
diff --git a/web/src/locales/de.ts b/web/src/locales/de.ts
index 5ec180b..a42fc20 100644
--- a/web/src/locales/de.ts
+++ b/web/src/locales/de.ts
@@ -1754,6 +1754,10 @@ 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: legacy mail protocols, one switch per protocol
+ "Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "Ihre Organisation hat {protocols} für Mail-Apps ausgeschaltet. Mail-Apps, die das nutzen, können sich nicht mit diesem Konto verbinden; andere schon.",
+ "Some legacy mail protocols are off for your organization: {protocols}.": "Einige ältere Mailprotokolle sind für Ihre Organisation ausgeschaltet: {protocols}.",
+ "Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "Einige sind für {tenant} aus: {protocols}. In der Verwaltungskonsole lassen sie sich einzeln ein- und ausschalten.",
// 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",
diff --git a/web/src/locales/es.ts b/web/src/locales/es.ts
index 96da847..8b97110 100644
--- a/web/src/locales/es.ts
+++ b/web/src/locales/es.ts
@@ -1727,6 +1727,10 @@ 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: legacy mail protocols, one switch per protocol
+ "Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "Su organización ha desactivado {protocols} para las aplicaciones de correo. Las que lo usan no pueden conectarse a esta cuenta; las demás sí.",
+ "Some legacy mail protocols are off for your organization: {protocols}.": "Algunos protocolos de correo heredados están desactivados para su organización: {protocols}.",
+ "Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "Algunos están desactivados para {tenant}: {protocols}. Actívelos o desactívelos uno a uno en la consola de administración.",
// 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",
diff --git a/web/src/locales/fr.ts b/web/src/locales/fr.ts
index bebce11..4603980 100644
--- a/web/src/locales/fr.ts
+++ b/web/src/locales/fr.ts
@@ -1732,6 +1732,10 @@ 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: legacy mail protocols, one switch per protocol
+ "Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "Votre organisation a désactivé {protocols} pour les applications de messagerie. Celles qui l’utilisent ne peuvent pas se connecter à ce compte ; les autres le peuvent toujours.",
+ "Some legacy mail protocols are off for your organization: {protocols}.": "Certains protocoles de messagerie historiques sont désactivés pour votre organisation : {protocols}.",
+ "Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "Certains sont désactivés pour {tenant} : {protocols}. Activez-les ou désactivez-les un par un dans la console d’administration.",
// 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",
diff --git a/web/src/locales/ja.ts b/web/src/locales/ja.ts
index 837347b..fa5965b 100644
--- a/web/src/locales/ja.ts
+++ b/web/src/locales/ja.ts
@@ -1735,6 +1735,10 @@ 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: legacy mail protocols, one switch per protocol
+ "Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "組織ではメールアプリ向けの {protocols} がオフになっています。これを使うメールアプリはこのアカウントに接続できませんが、ほかのアプリは接続できます。",
+ "Some legacy mail protocols are off for your organization: {protocols}.": "組織では一部の従来のメールプロトコルがオフになっています: {protocols}。",
+ "Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "{tenant} では一部がオフです: {protocols}。管理コンソールで 1 つずつ切り替えてください。",
// 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": "コンソールで開く",
diff --git a/web/src/locales/nl.ts b/web/src/locales/nl.ts
index 79645a8..ad54aba 100644
--- a/web/src/locales/nl.ts
+++ b/web/src/locales/nl.ts
@@ -1727,6 +1727,10 @@ 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: legacy mail protocols, one switch per protocol
+ "Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "Uw organisatie heeft {protocols} uitgeschakeld voor mail-apps. Mail-apps die dat gebruiken, kunnen geen verbinding maken met dit account; andere nog wel.",
+ "Some legacy mail protocols are off for your organization: {protocols}.": "Sommige verouderde mailprotocollen zijn uitgeschakeld voor uw organisatie: {protocols}.",
+ "Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "Sommige zijn uit voor {tenant}: {protocols}. In de beheerconsole zijn ze één voor één aan en uit te zetten.",
// 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",
diff --git a/web/src/locales/pt-BR.ts b/web/src/locales/pt-BR.ts
index 5308219..c781ce3 100644
--- a/web/src/locales/pt-BR.ts
+++ b/web/src/locales/pt-BR.ts
@@ -1730,6 +1730,10 @@ 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: legacy mail protocols, one switch per protocol
+ "Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "Sua organização desativou {protocols} para aplicativos de e-mail. Os que usam isso não conseguem se conectar a esta conta; os outros ainda conseguem.",
+ "Some legacy mail protocols are off for your organization: {protocols}.": "Alguns protocolos de e-mail legados estão desativados para sua organização: {protocols}.",
+ "Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "Alguns estão desativados para {tenant}: {protocols}. Ative ou desative um de cada vez no console de administração.",
// 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",
diff --git a/web/src/locales/ru.ts b/web/src/locales/ru.ts
index fda1c8a..429d556 100644
--- a/web/src/locales/ru.ts
+++ b/web/src/locales/ru.ts
@@ -1729,6 +1729,10 @@ 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: legacy mail protocols, one switch per protocol
+ "Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "Ваша организация отключила {protocols} для почтовых приложений. Приложения, которые его используют, не могут подключиться к этому аккаунту; остальные могут.",
+ "Some legacy mail protocols are off for your organization: {protocols}.": "Некоторые устаревшие почтовые протоколы отключены для вашей организации: {protocols}.",
+ "Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "Для {tenant} отключены некоторые: {protocols}. Включайте и отключайте их по одному в консоли администрирования.",
// 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": "Открыть в консоли",
diff --git a/web/src/locales/uk.ts b/web/src/locales/uk.ts
index 0e30882..5bc34fb 100644
--- a/web/src/locales/uk.ts
+++ b/web/src/locales/uk.ts
@@ -1723,6 +1723,10 @@ 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: legacy mail protocols, one switch per protocol
+ "Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "Ваша організація вимкнула {protocols} для поштових програм. Програми, які його використовують, не можуть підключитися до цього облікового запису; інші можуть.",
+ "Some legacy mail protocols are off for your organization: {protocols}.": "Деякі застарілі поштові протоколи вимкнено для вашої організації: {protocols}.",
+ "Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "Для {tenant} вимкнено деякі: {protocols}. Вмикайте й вимикайте їх по одному в консолі адміністрування.",
// 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": "Відкрити в консолі",
diff --git a/web/src/locales/zh-Hans.ts b/web/src/locales/zh-Hans.ts
index 8ca8a54..d4b9917 100644
--- a/web/src/locales/zh-Hans.ts
+++ b/web/src/locales/zh-Hans.ts
@@ -1734,6 +1734,10 @@ 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: legacy mail protocols, one switch per protocol
+ "Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.": "您的组织已为邮件应用关闭 {protocols}。使用它的邮件应用无法连接到此账户;其他应用仍可连接。",
+ "Some legacy mail protocols are off for your organization: {protocols}.": "您的组织已关闭部分传统邮件协议:{protocols}。",
+ "Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.": "{tenant} 已关闭部分协议:{protocols}。请在管理控制台中逐个开启或关闭。",
// 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": "在控制台中打开",
diff --git a/web/src/views/admin/AdminDashboard.tsx b/web/src/views/admin/AdminDashboard.tsx
index 3d48dc9..b572972 100644
--- a/web/src/views/admin/AdminDashboard.tsx
+++ b/web/src/views/admin/AdminDashboard.tsx
@@ -1,7 +1,7 @@
import { useEffect, useState, type ReactNode } from "react";
import { Link } from "wouter";
import { ArrowDownToLine, ArrowUpFromLine, ExternalLink, Globe, Hourglass, LayoutDashboard, MemoryStick, RefreshCw, ShieldCheck, Users } from "lucide-react";
-import { legacyProtocolsOff } from "@/jmap/client";
+import { legacyProtocolsOff, legacyProtocolsPartlyOff } from "@/jmap/client";
import { useAppName } from "@/lib/brand";
import { adminSections, dashboardCards, type DashboardCard } from "@/lib/admin/adminAccess";
import { balancedColumns, countObjects, DASHBOARD_WINDOW_MS, isRefused, loadMetrics, summarizeMetrics, type MessageStats } from "@/lib/admin/adminDashboard";
@@ -38,6 +38,7 @@ export function AdminDashboard() {
const perms = usePermissions();
const adminUrl = useSession((s) => s.session?.ihasmail?.server?.adminUrl ?? null);
const legacyOff = useSession((s) => legacyProtocolsOff(s.session, s.accountId));
+ const legacyPartlyOff = useSession((s) => legacyProtocolsPartlyOff(s.session, s.accountId).join(", "));
const app = useAppName();
const cards = dashboardCards(perms);
const sections = adminSections(perms);
@@ -113,6 +114,13 @@ export function AdminDashboard() {
{t("Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.", { app })}
)}
+ {legacyPartlyOff && (
+ // INBUXA: one switch per protocol, some of them off
+
+
+ {t("Some legacy mail protocols are off for your organization: {protocols}.", { protocols: legacyPartlyOff })}
+
{state.off
? t("Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.", { tenant: tenantName, app })
- : t("On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.", { tenant: tenantName })}
+ : state.partlyOff.length > 0
+ ? t("Some are off for {tenant}: {protocols}. Switch them one at a time in the administration console.", {
+ tenant: tenantName,
+ protocols: state.partlyOff.join(", "),
+ })
+ : t("On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.", { tenant: tenantName })}
- {canChange && state.off && (
+ {canChange && (state.off || state.partlyOff.length > 0) && (
diff --git a/web/src/views/admin/__tests__/tenant-legacy-protocols.test.tsx b/web/src/views/admin/__tests__/tenant-legacy-protocols.test.tsx
index 29cf605..2a2c701 100644
--- a/web/src/views/admin/__tests__/tenant-legacy-protocols.test.tsx
+++ b/web/src/views/admin/__tests__/tenant-legacy-protocols.test.tsx
@@ -62,7 +62,7 @@ describe("a tenant's legacy mail protocols switch", () => {
});
it("shows who would notice and what it means, then asks for the exact phrase", async () => {
- api.state = { off: false, recent: [{ accountId: "a", name: "maria@acme.example", protocol: "imap", lastUsedAt: Date.now() - 2 * 86400_000 }] };
+ api.state = { off: false, partlyOff: [], recent: [{ accountId: "a", name: "maria@acme.example", protocol: "imap", lastUsedAt: Date.now() - 2 * 86400_000 }] };
signIn(["sysDomainGet", "sysDomainUpdate"]);
await render();
await act(async () => button(host, "Turn off legacy protocols…")!.click());
@@ -84,7 +84,7 @@ describe("a tenant's legacy mail protocols switch", () => {
});
it("turns it back on with one click", async () => {
- api.state = { off: true, recent: [] };
+ api.state = { off: true, partlyOff: [], recent: [] };
signIn(["sysDomainGet", "sysDomainUpdate"]);
await render();
await act(async () => button(host, "Turn legacy protocols back on")!.click());
@@ -92,7 +92,7 @@ describe("a tenant's legacy mail protocols switch", () => {
});
it("shows the state but no switch to someone who can't change domains", async () => {
- api.state = { off: true, recent: null };
+ api.state = { off: true, partlyOff: [], recent: null };
signIn(["sysDomainGet"]);
await render();
expect(host.textContent).toContain("Off for Acme Corp");
diff --git a/web/src/views/settings/SecuritySettings.tsx b/web/src/views/settings/SecuritySettings.tsx
index 17d0acc..940a71e 100644
--- a/web/src/views/settings/SecuritySettings.tsx
+++ b/web/src/views/settings/SecuritySettings.tsx
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useState } from "react";
import { useAppName } from "@/lib/brand";
import { Copy, KeyRound, ShieldCheck, Smartphone } from "lucide-react";
-import { apiFetch, ApiError, legacyProtocolsOff } from "@/jmap/client";
+import { apiFetch, ApiError, legacyProtocolsOff, legacyProtocolsPartlyOff } from "@/jmap/client";
import { useSession } from "@/store/session";
import { formatFullDate } from "@/lib/format";
import { toast } from "@/ui/toast";
@@ -89,6 +89,14 @@ export function SecuritySettings() {
{t("Your organization allows only {app} and JMAP apps, so phone and desktop mail apps can't connect to this account.", { app: appName })}
)}
+ {legacyProtocolsPartlyOff(session, accountId).length > 0 && (
+ // INBUXA: one switch per protocol; mail apps using the rest still work
+
+ {t("Your organization has turned off {protocols} for mail apps. Mail apps that use it can't connect to this account; others still can.", {
+ protocols: legacyProtocolsPartlyOff(session, accountId).join(", "),
+ })}
+
+ )}
{unsupported ? (
{t("App passwords are managed by your mail administrator.")}