A tenant's administrator can turn legacy mail apps off for the organization #5
@@ -0,0 +1,30 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { CONFIRM_PHRASE, impactEntries, parseTenantLegacy, phraseMatches } from "../adminLegacyProtocols";
|
||||||
|
|
||||||
|
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 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("puts each account on the panel once, with every protocol and its latest use", () => {
|
||||||
|
const { recent } = parseTenantLegacy({
|
||||||
|
recentLegacyUse: [
|
||||||
|
{ accountId: "a", name: "[email protected]", protocol: "submission", lastUsedAt: 100 },
|
||||||
|
{ accountId: "a", name: "[email protected]", protocol: "imap", lastUsedAt: 300 },
|
||||||
|
{ accountId: "b", name: "[email protected]", protocol: "pop3", lastUsedAt: 200 },
|
||||||
|
{ accountId: "c", name: "broken" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(impactEntries(recent!)).toEqual([
|
||||||
|
{ name: "[email protected]", protocols: ["IMAP", "SMTP"], lastUsedAt: 300 },
|
||||||
|
{ name: "[email protected]", protocols: ["POP3"], lastUsedAt: 200 },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("takes only the exact phrase", () => {
|
||||||
|
expect(phraseMatches(CONFIRM_PHRASE)).toBe(true);
|
||||||
|
expect(phraseMatches(` ${CONFIRM_PHRASE}`)).toBe(false);
|
||||||
|
expect(phraseMatches(CONFIRM_PHRASE.toUpperCase())).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { client, INBUXA_CAP } from "@/jmap/client";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One tenant's legacy mail protocols switch: INBUXA's
|
||||||
|
* `inbuxa:TenantProtocolPolicy` (legacy-protocols LP-9 to LP-18).
|
||||||
|
*
|
||||||
|
* Turning it off refuses sign-in over IMAP, POP3, ManageSieve and SMTP
|
||||||
|
* submission on the tenant's domains, so only this webmail and other JMAP
|
||||||
|
* apps work there. It closes no port -- other tenants share them. Turning it
|
||||||
|
* back on is refused by the server while the server has legacy protocols off
|
||||||
|
* for everyone.
|
||||||
|
*
|
||||||
|
* Only INBUXA serves it. On any other server the method is unknown, which
|
||||||
|
* `fetchTenantLegacy` reports as `null` so the sheet shows nothing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const OBJECT = "inbuxa:TenantProtocolPolicy";
|
||||||
|
|
||||||
|
/** The phrase that turns legacy protocols off (LP-17). Turning them back on needs none. */
|
||||||
|
export const CONFIRM_PHRASE = "turn off legacy mail";
|
||||||
|
|
||||||
|
/** Whether the typed confirmation matches: exactly, no trimming, no case folding. */
|
||||||
|
export function phraseMatches(typed: string): boolean {
|
||||||
|
return typed === CONFIRM_PHRASE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** One account's last sign-in over one legacy protocol, as the server reports it (LP-15). */
|
||||||
|
export interface RecentUse {
|
||||||
|
accountId: string;
|
||||||
|
name: string;
|
||||||
|
protocol: string;
|
||||||
|
/** Milliseconds since the epoch. */
|
||||||
|
lastUsedAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TenantLegacy {
|
||||||
|
off: boolean;
|
||||||
|
/** Null from a server too old to say who uses legacy apps -- not the same as nobody. */
|
||||||
|
recent: RecentUse[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRecent(raw: unknown): RecentUse[] | null {
|
||||||
|
if (!Array.isArray(raw)) return null;
|
||||||
|
return raw.flatMap((entry) => {
|
||||||
|
if (!entry || typeof entry !== "object") return [];
|
||||||
|
const r = entry as Record<string, unknown>;
|
||||||
|
if (typeof r.name !== "string" || typeof r.protocol !== "string" || typeof r.lastUsedAt !== "number") return [];
|
||||||
|
return [{ accountId: typeof r.accountId === "string" ? r.accountId : "", name: r.name, protocol: r.protocol, lastUsedAt: r.lastUsedAt }];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseTenantLegacy(raw: Record<string, unknown>): TenantLegacy {
|
||||||
|
return { off: raw.legacyProtocols === "disabled", recent: parseRecent(raw.recentLegacyUse) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The tenant's switch, or null where the server has none (not INBUXA, or too old). */
|
||||||
|
export async function fetchTenantLegacy(tenantId: string): Promise<TenantLegacy | null> {
|
||||||
|
try {
|
||||||
|
const res = await client.call<{ list?: Record<string, unknown>[] }>(`${OBJECT}/get`, { ids: [tenantId] }, [INBUXA_CAP]);
|
||||||
|
return res.list?.[0] ? parseTenantLegacy(res.list[0]) : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Turns the tenant's switch. A refusal (LP-9) comes back as the server's words. */
|
||||||
|
export async function setTenantLegacy(tenantId: string, off: boolean): Promise<void> {
|
||||||
|
const res = await client.call<{ notUpdated?: Record<string, { type: string; description?: string }> | null }>(
|
||||||
|
`${OBJECT}/set`,
|
||||||
|
{ update: { [tenantId]: { legacyProtocols: off ? "disabled" : "enabled" } } },
|
||||||
|
[INBUXA_CAP],
|
||||||
|
);
|
||||||
|
const failed = res.notUpdated?.[tenantId];
|
||||||
|
if (failed) throw new Error(failed.description ?? failed.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
const PROTOCOL_ORDER = ["imap", "pop3", "manageSieve", "submission"];
|
||||||
|
const PROTOCOL_LABELS: Record<string, string> = { imap: "IMAP", pop3: "POP3", manageSieve: "ManageSieve", submission: "SMTP" };
|
||||||
|
|
||||||
|
/** One account on the impact panel: every protocol it used, and when it last used any. */
|
||||||
|
export interface ImpactEntry {
|
||||||
|
name: string;
|
||||||
|
protocols: string[];
|
||||||
|
lastUsedAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The impact panel's lines (LP-15): one per account, most recent first. */
|
||||||
|
export function impactEntries(recent: RecentUse[]): ImpactEntry[] {
|
||||||
|
const byAccount = new Map<string, { name: string; protocols: Set<string>; lastUsedAt: number }>();
|
||||||
|
for (const use of recent) {
|
||||||
|
const key = use.accountId || use.name;
|
||||||
|
const entry = byAccount.get(key) ?? { name: use.name, protocols: new Set<string>(), lastUsedAt: 0 };
|
||||||
|
entry.protocols.add(use.protocol);
|
||||||
|
entry.lastUsedAt = Math.max(entry.lastUsedAt, use.lastUsedAt);
|
||||||
|
byAccount.set(key, entry);
|
||||||
|
}
|
||||||
|
return [...byAccount.values()]
|
||||||
|
.map((e) => ({
|
||||||
|
name: e.name,
|
||||||
|
protocols: [...e.protocols].sort((a, b) => PROTOCOL_ORDER.indexOf(a) - PROTOCOL_ORDER.indexOf(b)).map((p) => PROTOCOL_LABELS[p] ?? p),
|
||||||
|
lastUsedAt: e.lastUsedAt,
|
||||||
|
}))
|
||||||
|
.sort((a, b) => b.lastUsedAt - a.lastUsedAt || a.name.localeCompare(b.name));
|
||||||
|
}
|
||||||
@@ -1721,8 +1721,29 @@ export const catalog: Catalog = {
|
|||||||
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} weist Sie darauf hin, wenn eine spätere Nachricht von dieser Adresse von jemand anderem signiert ist.",
|
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} weist Sie darauf hin, wenn eine spätere Nachricht von dieser Adresse von jemand anderem signiert ist.",
|
||||||
"itself, or an issuer it does not name": "sich selbst, oder einem nicht genannten Aussteller",
|
"itself, or an issuer it does not name": "sich selbst, oder einem nicht genannten Aussteller",
|
||||||
"no address": "keine Adresse",
|
"no address": "keine Adresse",
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"Legacy mail apps": "Mail-Apps über ältere Protokolle",
|
||||||
|
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "Aus für {tenant}. Nur {app} und JMAP-Apps können sich bei den Domains dieser Organisation anmelden.",
|
||||||
|
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "An für {tenant}. Mail-Apps können auf den Domains dieser Organisation IMAP, POP3 und ManageSieve nutzen.",
|
||||||
|
"Turn legacy protocols back on": "Ältere Mailprotokolle wieder einschalten",
|
||||||
|
"Turn off legacy protocols…": "Ältere Mailprotokolle ausschalten…",
|
||||||
|
"No account used a legacy mail app in the last 30 days.": "In den letzten 30 Tagen hat kein Konto eine Mail-App mit älteren Protokollen verwendet.",
|
||||||
|
"Their mail apps will stop working the moment you turn this on:": "Deren Mail-Apps funktionieren nicht mehr, sobald Sie dies einschalten:",
|
||||||
|
"Only {app} and JMAP apps will work.": "Nur {app} und JMAP-Apps funktionieren dann noch.",
|
||||||
|
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "Ältere Mailprotokolle (IMAP, POP3, ManageSieve und das Senden aus Mail-Apps) werden für alle in {tenant} ausgeschaltet.",
|
||||||
|
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "Mail-Apps auf Smartphones und Computern können keine Mails mehr empfangen und senden. Das betrifft Mail auf iPhone und iPad, die Gmail- und Outlook-Apps, Outlook, Thunderbird und Apple Mail. Dort erscheinen Anmeldefehler.",
|
||||||
|
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "Filter, die über eine Mail-App verwaltet werden (ManageSieve), funktionieren nicht mehr. In {app} eingerichtete Filter funktionieren weiterhin.",
|
||||||
|
"Incoming mail is not affected. Calendars and contacts are not affected.": "Eingehende Mails sind nicht betroffen. Kalender und Kontakte sind nicht betroffen.",
|
||||||
|
"People keep full access through {app}, which can be installed as an app on phones and computers.": "Alle behalten vollen Zugriff über {app}, das sich auf Smartphones und Computern als App installieren lässt.",
|
||||||
|
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "Das Senden aus Mail-Apps (SMTP-Submission) funktioniert nicht mehr, die Ports bleiben aber offen: Mail-Apps erfahren, dass sie sich nicht anmelden können. Eingehende Mails (SMTP) und {app} (JMAP) sind nicht betroffen und lassen sich hier nicht ausschalten.",
|
||||||
|
"You can turn legacy protocols back on at any time.": "Sie können ältere Mailprotokolle jederzeit wieder einschalten.",
|
||||||
|
"To confirm, type {phrase}": "Zur Bestätigung {phrase} eingeben",
|
||||||
|
"Turn off legacy protocols": "Ältere Mailprotokolle ausschalten",
|
||||||
|
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "Ältere Mailprotokolle sind für Ihre Organisation ausgeschaltet. Nur {app} und JMAP-Apps können sich anmelden.",
|
||||||
},
|
},
|
||||||
plurals: {
|
plurals: {
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"{n} accounts used a legacy mail app in the last 30 days.": {one: "{n} Konto hat in den letzten 30 Tagen eine Mail-App mit älteren Protokollen verwendet.", other: "{n} Konten haben in den letzten 30 Tagen eine Mail-App mit älteren Protokollen verwendet."},
|
||||||
// ── Administration: domains ────────────────────────────────────
|
// ── Administration: domains ────────────────────────────────────
|
||||||
"{n} accounts use this domain. Move or delete them first.": { one: "{n} Konto verwendet diese Domain. Verschieben oder löschen Sie es zuerst.", other: "{n} Konten verwenden diese Domain. Verschieben oder löschen Sie sie zuerst." },
|
"{n} accounts use this domain. Move or delete them first.": { one: "{n} Konto verwendet diese Domain. Verschieben oder löschen Sie es zuerst.", other: "{n} Konten verwenden diese Domain. Verschieben oder löschen Sie sie zuerst." },
|
||||||
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "Der Server nimmt keine E-Mails mehr für diese Domain an, und ihr {n} DKIM-Schlüssel wird gelöscht. Dies kann nicht rückgängig gemacht werden.", other: "Der Server nimmt keine E-Mails mehr für diese Domain an, und ihre {n} DKIM-Schlüssel werden gelöscht. Dies kann nicht rückgängig gemacht werden." },
|
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "Der Server nimmt keine E-Mails mehr für diese Domain an, und ihr {n} DKIM-Schlüssel wird gelöscht. Dies kann nicht rückgängig gemacht werden.", other: "Der Server nimmt keine E-Mails mehr für diese Domain an, und ihre {n} DKIM-Schlüssel werden gelöscht. Dies kann nicht rückgängig gemacht werden." },
|
||||||
|
|||||||
@@ -1694,8 +1694,29 @@ export const catalog: Catalog = {
|
|||||||
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} le avisará si un mensaje posterior de esta dirección lo firma otra persona.",
|
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} le avisará si un mensaje posterior de esta dirección lo firma otra persona.",
|
||||||
"itself, or an issuer it does not name": "sí mismo, o un emisor que no nombra",
|
"itself, or an issuer it does not name": "sí mismo, o un emisor que no nombra",
|
||||||
"no address": "ninguna dirección",
|
"no address": "ninguna dirección",
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"Legacy mail apps": "Aplicaciones de correo con protocolos heredados",
|
||||||
|
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "Desactivados para {tenant}. Solo {app} y las aplicaciones JMAP pueden iniciar sesión en sus dominios.",
|
||||||
|
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "Activados para {tenant}. Las aplicaciones de correo pueden usar IMAP, POP3 y ManageSieve en sus dominios.",
|
||||||
|
"Turn legacy protocols back on": "Volver a activar los protocolos de correo heredados",
|
||||||
|
"Turn off legacy protocols…": "Desactivar los protocolos de correo heredados…",
|
||||||
|
"No account used a legacy mail app in the last 30 days.": "Ninguna cuenta ha usado una aplicación de correo con protocolos heredados en los últimos 30 días.",
|
||||||
|
"Their mail apps will stop working the moment you turn this on:": "Sus aplicaciones de correo dejarán de funcionar en cuanto active esto:",
|
||||||
|
"Only {app} and JMAP apps will work.": "Solo funcionarán {app} y las aplicaciones JMAP.",
|
||||||
|
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "Los protocolos de correo heredados (IMAP, POP3, ManageSieve y el envío desde aplicaciones de correo) se desactivarán para todos en {tenant}.",
|
||||||
|
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "Las aplicaciones de correo del teléfono y del ordenador dejarán de recibir y enviar correo. Eso incluye Mail de iPhone y iPad, las aplicaciones de Gmail y Outlook, Outlook, Thunderbird y Apple Mail. Se verán errores de inicio de sesión en ellas.",
|
||||||
|
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "Los filtros gestionados desde una aplicación de correo (ManageSieve) dejarán de funcionar. Los filtros configurados en {app} siguen funcionando.",
|
||||||
|
"Incoming mail is not affected. Calendars and contacts are not affected.": "El correo entrante no se ve afectado. Los calendarios y los contactos no se ven afectados.",
|
||||||
|
"People keep full access through {app}, which can be installed as an app on phones and computers.": "Todos conservan el acceso completo a través de {app}, que se puede instalar como aplicación en teléfonos y ordenadores.",
|
||||||
|
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "El envío desde aplicaciones de correo (envío SMTP) dejará de funcionar, pero sus puertos siguen abiertos: se indicará a las aplicaciones de correo que no pueden iniciar sesión. El correo entrante (SMTP) y {app} (JMAP) no se ven afectados y no se pueden desactivar aquí.",
|
||||||
|
"You can turn legacy protocols back on at any time.": "Puede volver a activar los protocolos de correo heredados en cualquier momento.",
|
||||||
|
"To confirm, type {phrase}": "Para confirmar, escriba {phrase}",
|
||||||
|
"Turn off legacy protocols": "Desactivar los protocolos de correo heredados",
|
||||||
|
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "Los protocolos de correo heredados están desactivados para su organización. Solo {app} y las aplicaciones JMAP pueden iniciar sesión.",
|
||||||
},
|
},
|
||||||
plurals: {
|
plurals: {
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"{n} accounts used a legacy mail app in the last 30 days.": {one: "{n} cuenta ha usado una aplicación de correo con protocolos heredados en los últimos 30 días.", other: "{n} cuentas han usado una aplicación de correo con protocolos heredados en los últimos 30 días."},
|
||||||
// ── Administration: domains ────────────────────────────────────
|
// ── Administration: domains ────────────────────────────────────
|
||||||
"{n} accounts use this domain. Move or delete them first.": { one: "{n} cuenta usa este dominio. Muévala o elimínela primero.", other: "{n} cuentas usan este dominio. Muévalas o elimínelas primero." },
|
"{n} accounts use this domain. Move or delete them first.": { one: "{n} cuenta usa este dominio. Muévala o elimínela primero.", other: "{n} cuentas usan este dominio. Muévalas o elimínelas primero." },
|
||||||
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "El servidor deja de aceptar correo para este dominio y se elimina su {n} clave DKIM. No se puede deshacer.", other: "El servidor deja de aceptar correo para este dominio y se eliminan sus {n} claves DKIM. No se puede deshacer." },
|
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "El servidor deja de aceptar correo para este dominio y se elimina su {n} clave DKIM. No se puede deshacer.", other: "El servidor deja de aceptar correo para este dominio y se eliminan sus {n} claves DKIM. No se puede deshacer." },
|
||||||
|
|||||||
@@ -1699,8 +1699,29 @@ export const catalog: Catalog = {
|
|||||||
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} vous préviendra si un message ultérieur de cette adresse est signé par quelqu'un d'autre.",
|
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} vous préviendra si un message ultérieur de cette adresse est signé par quelqu'un d'autre.",
|
||||||
"itself, or an issuer it does not name": "lui-même, ou un émetteur qu'il ne nomme pas",
|
"itself, or an issuer it does not name": "lui-même, ou un émetteur qu'il ne nomme pas",
|
||||||
"no address": "aucune adresse",
|
"no address": "aucune adresse",
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"Legacy mail apps": "Applications de messagerie à protocoles historiques",
|
||||||
|
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "Désactivés pour {tenant}. Seuls {app} et les applications JMAP peuvent se connecter à ses domaines.",
|
||||||
|
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "Activés pour {tenant}. Les applications de messagerie peuvent utiliser IMAP, POP3 et ManageSieve sur ses domaines.",
|
||||||
|
"Turn legacy protocols back on": "Réactiver les protocoles de messagerie historiques",
|
||||||
|
"Turn off legacy protocols…": "Désactiver les protocoles de messagerie historiques…",
|
||||||
|
"No account used a legacy mail app in the last 30 days.": "Aucun compte n'a utilisé d'application de messagerie à protocoles historiques ces 30 derniers jours.",
|
||||||
|
"Their mail apps will stop working the moment you turn this on:": "Leurs applications de messagerie cesseront de fonctionner dès que vous activerez ceci :",
|
||||||
|
"Only {app} and JMAP apps will work.": "Seuls {app} et les applications JMAP fonctionneront.",
|
||||||
|
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "Les protocoles de messagerie historiques (IMAP, POP3, ManageSieve et l'envoi depuis les applications de messagerie) seront désactivés pour tout le monde dans {tenant}.",
|
||||||
|
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "Les applications de messagerie sur téléphone et ordinateur ne pourront plus recevoir ni envoyer de courrier : Mail sur iPhone et iPad, les applications Gmail et Outlook, Outlook, Thunderbird et Apple Mail. Des erreurs de connexion s'y afficheront.",
|
||||||
|
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "Les filtres gérés depuis une application de messagerie (ManageSieve) cesseront de fonctionner. Les filtres configurés dans {app} continuent de fonctionner.",
|
||||||
|
"Incoming mail is not affected. Calendars and contacts are not affected.": "Le courrier entrant n'est pas concerné. Les agendas et les contacts ne sont pas concernés.",
|
||||||
|
"People keep full access through {app}, which can be installed as an app on phones and computers.": "Chacun garde un accès complet via {app}, qui peut s'installer comme application sur téléphone et ordinateur.",
|
||||||
|
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "L'envoi depuis les applications de messagerie (soumission SMTP) cessera de fonctionner, mais ses ports restent ouverts : les applications de messagerie seront informées qu'elles ne peuvent pas se connecter. Le courrier entrant (SMTP) et {app} (JMAP) ne sont pas concernés et ne peuvent pas être désactivés ici.",
|
||||||
|
"You can turn legacy protocols back on at any time.": "Vous pouvez réactiver les protocoles de messagerie historiques à tout moment.",
|
||||||
|
"To confirm, type {phrase}": "Pour confirmer, saisissez {phrase}",
|
||||||
|
"Turn off legacy protocols": "Désactiver les protocoles de messagerie historiques",
|
||||||
|
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "Les protocoles de messagerie historiques sont désactivés pour votre organisation. Seuls {app} et les applications JMAP peuvent se connecter.",
|
||||||
},
|
},
|
||||||
plurals: {
|
plurals: {
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"{n} accounts used a legacy mail app in the last 30 days.": {one: "{n} compte a utilisé une application de messagerie à protocoles historiques ces 30 derniers jours.", other: "{n} comptes ont utilisé une application de messagerie à protocoles historiques ces 30 derniers jours."},
|
||||||
// ── Administration: domains ────────────────────────────────────
|
// ── Administration: domains ────────────────────────────────────
|
||||||
"{n} accounts use this domain. Move or delete them first.": { one: "{n} compte utilise ce domaine. Déplacez-le ou supprimez-le d’abord.", other: "{n} comptes utilisent ce domaine. Déplacez-les ou supprimez-les d’abord." },
|
"{n} accounts use this domain. Move or delete them first.": { one: "{n} compte utilise ce domaine. Déplacez-le ou supprimez-le d’abord.", other: "{n} comptes utilisent ce domaine. Déplacez-les ou supprimez-les d’abord." },
|
||||||
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "Le serveur n’accepte plus de messages pour ce domaine, et sa {n} clé DKIM est supprimée. C’est irréversible.", other: "Le serveur n’accepte plus de messages pour ce domaine, et ses {n} clés DKIM sont supprimées. C’est irréversible." },
|
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "Le serveur n’accepte plus de messages pour ce domaine, et sa {n} clé DKIM est supprimée. C’est irréversible.", other: "Le serveur n’accepte plus de messages pour ce domaine, et ses {n} clés DKIM sont supprimées. C’est irréversible." },
|
||||||
|
|||||||
@@ -1702,8 +1702,29 @@ export const catalog: Catalog = {
|
|||||||
"{app} will tell you if a later message from this address is signed by anybody else.": "このアドレスからの以降のメールが別の人の署名だった場合、{app} がお知らせします。",
|
"{app} will tell you if a later message from this address is signed by anybody else.": "このアドレスからの以降のメールが別の人の署名だった場合、{app} がお知らせします。",
|
||||||
"itself, or an issuer it does not name": "自分自身、または名前のない発行者",
|
"itself, or an issuer it does not name": "自分自身、または名前のない発行者",
|
||||||
"no address": "アドレスなし",
|
"no address": "アドレスなし",
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"Legacy mail apps": "従来のプロトコルを使うメールアプリ",
|
||||||
|
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "{tenant} ではオフです。そのドメインにサインインできるのは {app} と JMAP アプリのみです。",
|
||||||
|
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "{tenant} ではオンです。そのドメインではメールアプリが IMAP、POP3、ManageSieve を利用できます。",
|
||||||
|
"Turn legacy protocols back on": "従来のメールプロトコルを再びオンにする",
|
||||||
|
"Turn off legacy protocols…": "従来のメールプロトコルをオフにする…",
|
||||||
|
"No account used a legacy mail app in the last 30 days.": "過去 30 日間に従来のプロトコルでメールアプリを使用したアカウントはありません。",
|
||||||
|
"Their mail apps will stop working the moment you turn this on:": "これをオンにした時点で、これらのメールアプリは動作しなくなります:",
|
||||||
|
"Only {app} and JMAP apps will work.": "{app} と JMAP アプリのみが動作します。",
|
||||||
|
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "{tenant} のすべてのユーザーに対して、従来のメールプロトコル(IMAP、POP3、ManageSieve、メールアプリからの送信)がオフになります。",
|
||||||
|
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "スマートフォンやパソコンのメールアプリでメールの送受信ができなくなります。iPhone と iPad のメール、Gmail と Outlook のアプリ、Outlook、Thunderbird、Apple Mail が対象です。これらのアプリにはサインインエラーが表示されます。",
|
||||||
|
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "メールアプリから管理しているフィルタ(ManageSieve)は動作しなくなります。{app} で設定したフィルタは引き続き動作します。",
|
||||||
|
"Incoming mail is not affected. Calendars and contacts are not affected.": "受信メールには影響しません。カレンダーと連絡先にも影響しません。",
|
||||||
|
"People keep full access through {app}, which can be installed as an app on phones and computers.": "{app} からは引き続きすべて利用できます。{app} はスマートフォンやパソコンにアプリとしてインストールできます。",
|
||||||
|
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "メールアプリからの送信(SMTP submission)は動作しなくなりますが、ポートは開いたままです。メールアプリにはサインインできないことが通知されます。受信メール(SMTP)と {app}(JMAP)には影響せず、ここでオフにすることはできません。",
|
||||||
|
"You can turn legacy protocols back on at any time.": "従来のメールプロトコルはいつでも再びオンにできます。",
|
||||||
|
"To confirm, type {phrase}": "確認のため {phrase} と入力してください",
|
||||||
|
"Turn off legacy protocols": "従来のメールプロトコルをオフにする",
|
||||||
|
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "組織では従来のメールプロトコルがオフになっています。サインインできるのは {app} と JMAP アプリのみです。",
|
||||||
},
|
},
|
||||||
plurals: {
|
plurals: {
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"{n} accounts used a legacy mail app in the last 30 days.": {other: "過去 30 日間に {n} 件のアカウントが従来のプロトコルでメールアプリを使用しました。"},
|
||||||
// ── Administration: domains ────────────────────────────────────
|
// ── Administration: domains ────────────────────────────────────
|
||||||
"{n} accounts use this domain. Move or delete them first.": { other: "{n} 件のアカウントがこのドメインを使用しています。先に移動または削除してください。" },
|
"{n} accounts use this domain. Move or delete them first.": { other: "{n} 件のアカウントがこのドメインを使用しています。先に移動または削除してください。" },
|
||||||
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { other: "サーバーはこのドメイン宛てのメールを受け付けなくなり、{n} 個の DKIM 鍵も削除されます。元に戻すことはできません。" },
|
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { other: "サーバーはこのドメイン宛てのメールを受け付けなくなり、{n} 個の DKIM 鍵も削除されます。元に戻すことはできません。" },
|
||||||
|
|||||||
@@ -1691,8 +1691,29 @@ export const catalog: Catalog = {
|
|||||||
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} laat het weten als een later bericht van dit adres door iemand anders is ondertekend.",
|
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} laat het weten als een later bericht van dit adres door iemand anders is ondertekend.",
|
||||||
"itself, or an issuer it does not name": "zichzelf, of een uitgever die niet wordt genoemd",
|
"itself, or an issuer it does not name": "zichzelf, of een uitgever die niet wordt genoemd",
|
||||||
"no address": "geen adres",
|
"no address": "geen adres",
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"Legacy mail apps": "Mail-apps met verouderde protocollen",
|
||||||
|
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "Uit voor {tenant}. Alleen {app} en JMAP-apps kunnen inloggen op de domeinen ervan.",
|
||||||
|
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "Aan voor {tenant}. Mail-apps kunnen IMAP, POP3 en ManageSieve gebruiken op de domeinen ervan.",
|
||||||
|
"Turn legacy protocols back on": "Verouderde mailprotocollen weer inschakelen",
|
||||||
|
"Turn off legacy protocols…": "Verouderde mailprotocollen uitschakelen…",
|
||||||
|
"No account used a legacy mail app in the last 30 days.": "Geen enkel account heeft de afgelopen 30 dagen een mail-app met verouderde protocollen gebruikt.",
|
||||||
|
"Their mail apps will stop working the moment you turn this on:": "Hun mail-apps werken niet meer zodra u dit inschakelt:",
|
||||||
|
"Only {app} and JMAP apps will work.": "Alleen {app} en JMAP-apps werken dan nog.",
|
||||||
|
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "Verouderde mailprotocollen (IMAP, POP3, ManageSieve en verzenden vanuit mail-apps) worden uitgeschakeld voor iedereen in {tenant}.",
|
||||||
|
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "Mail-apps op telefoon en computer kunnen geen mail meer ontvangen en verzenden. Het gaat om Mail op iPhone en iPad, de Gmail- en Outlook-apps, Outlook, Thunderbird en Apple Mail. Gebruikers zien daarin inlogfouten.",
|
||||||
|
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "Filters die vanuit een mail-app worden beheerd (ManageSieve) werken niet meer. Filters die in {app} zijn ingesteld blijven werken.",
|
||||||
|
"Incoming mail is not affected. Calendars and contacts are not affected.": "Inkomende mail wordt niet beïnvloed. Agenda's en contacten worden niet beïnvloed.",
|
||||||
|
"People keep full access through {app}, which can be installed as an app on phones and computers.": "Iedereen houdt volledige toegang via {app}, dat als app op telefoons en computers kan worden geïnstalleerd.",
|
||||||
|
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "Verzenden vanuit mail-apps (SMTP-submission) werkt niet meer, maar de poorten blijven open: mail-apps krijgen te horen dat ze niet kunnen inloggen. Inkomende mail (SMTP) en {app} (JMAP) worden niet beïnvloed en kunnen hier niet worden uitgeschakeld.",
|
||||||
|
"You can turn legacy protocols back on at any time.": "U kunt verouderde mailprotocollen op elk moment weer inschakelen.",
|
||||||
|
"To confirm, type {phrase}": "Typ ter bevestiging {phrase}",
|
||||||
|
"Turn off legacy protocols": "Verouderde mailprotocollen uitschakelen",
|
||||||
|
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "Verouderde mailprotocollen zijn uitgeschakeld voor uw organisatie. Alleen {app} en JMAP-apps kunnen inloggen.",
|
||||||
},
|
},
|
||||||
plurals: {
|
plurals: {
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"{n} accounts used a legacy mail app in the last 30 days.": {one: "{n} account heeft de afgelopen 30 dagen een mail-app met verouderde protocollen gebruikt.", other: "{n} accounts hebben de afgelopen 30 dagen een mail-app met verouderde protocollen gebruikt."},
|
||||||
// ── Administration: domains ────────────────────────────────────
|
// ── Administration: domains ────────────────────────────────────
|
||||||
"{n} accounts use this domain. Move or delete them first.": { one: "{n} account gebruikt dit domein. Verplaats of verwijder deze eerst.", other: "{n} accounts gebruiken dit domein. Verplaats of verwijder ze eerst." },
|
"{n} accounts use this domain. Move or delete them first.": { one: "{n} account gebruikt dit domein. Verplaats of verwijder deze eerst.", other: "{n} accounts gebruiken dit domein. Verplaats of verwijder ze eerst." },
|
||||||
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "De server accepteert geen e-mails meer voor dit domein en de {n} DKIM-sleutel wordt verwijderd. Dit kan niet ongedaan worden gemaakt.", other: "De server accepteert geen e-mails meer voor dit domein en de {n} DKIM-sleutels worden verwijderd. Dit kan niet ongedaan worden gemaakt." },
|
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "De server accepteert geen e-mails meer voor dit domein en de {n} DKIM-sleutel wordt verwijderd. Dit kan niet ongedaan worden gemaakt.", other: "De server accepteert geen e-mails meer voor dit domein en de {n} DKIM-sleutels worden verwijderd. Dit kan niet ongedaan worden gemaakt." },
|
||||||
|
|||||||
@@ -1697,8 +1697,29 @@ export const catalog: Catalog = {
|
|||||||
"{app} will tell you if a later message from this address is signed by anybody else.": "O {app} avisará você se uma mensagem posterior deste endereço for assinada por outra pessoa.",
|
"{app} will tell you if a later message from this address is signed by anybody else.": "O {app} avisará você se uma mensagem posterior deste endereço for assinada por outra pessoa.",
|
||||||
"itself, or an issuer it does not name": "ele mesmo, ou um emissor que ele não nomeia",
|
"itself, or an issuer it does not name": "ele mesmo, ou um emissor que ele não nomeia",
|
||||||
"no address": "nenhum endereço",
|
"no address": "nenhum endereço",
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"Legacy mail apps": "Aplicativos de e-mail com protocolos legados",
|
||||||
|
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "Desativados para {tenant}. Só {app} e aplicativos JMAP podem entrar nos domínios dessa organização.",
|
||||||
|
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "Ativados para {tenant}. Aplicativos de e-mail podem usar IMAP, POP3 e ManageSieve nos domínios dessa organização.",
|
||||||
|
"Turn legacy protocols back on": "Reativar os protocolos de e-mail legados",
|
||||||
|
"Turn off legacy protocols…": "Desativar os protocolos de e-mail legados…",
|
||||||
|
"No account used a legacy mail app in the last 30 days.": "Nenhuma conta usou um aplicativo de e-mail com protocolos legados nos últimos 30 dias.",
|
||||||
|
"Their mail apps will stop working the moment you turn this on:": "Os aplicativos de e-mail dessas contas vão parar de funcionar assim que você ativar isto:",
|
||||||
|
"Only {app} and JMAP apps will work.": "Só {app} e aplicativos JMAP vão funcionar.",
|
||||||
|
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "Os protocolos de e-mail legados (IMAP, POP3, ManageSieve e o envio por aplicativos de e-mail) serão desativados para todos em {tenant}.",
|
||||||
|
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "Aplicativos de e-mail no celular e no computador vão parar de receber e enviar e-mail. Isso inclui o Mail do iPhone e do iPad, os aplicativos Gmail e Outlook, o Outlook, o Thunderbird e o Apple Mail. As pessoas verão erros de login neles.",
|
||||||
|
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "Filtros gerenciados por um aplicativo de e-mail (ManageSieve) vão parar de funcionar. Filtros configurados no {app} continuam funcionando.",
|
||||||
|
"Incoming mail is not affected. Calendars and contacts are not affected.": "O e-mail recebido não é afetado. Agendas e contatos não são afetados.",
|
||||||
|
"People keep full access through {app}, which can be installed as an app on phones and computers.": "Todos mantêm acesso completo pelo {app}, que pode ser instalado como aplicativo em celulares e computadores.",
|
||||||
|
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "O envio por aplicativos de e-mail (SMTP submission) vai parar de funcionar, mas as portas continuam abertas: os aplicativos de e-mail serão informados de que não podem entrar. O e-mail recebido (SMTP) e o {app} (JMAP) não são afetados e não podem ser desativados aqui.",
|
||||||
|
"You can turn legacy protocols back on at any time.": "Você pode reativar os protocolos de e-mail legados a qualquer momento.",
|
||||||
|
"To confirm, type {phrase}": "Para confirmar, digite {phrase}",
|
||||||
|
"Turn off legacy protocols": "Desativar os protocolos de e-mail legados",
|
||||||
|
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "Os protocolos de e-mail legados estão desativados para sua organização. Só {app} e aplicativos JMAP podem entrar.",
|
||||||
},
|
},
|
||||||
plurals: {
|
plurals: {
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"{n} accounts used a legacy mail app in the last 30 days.": {one: "{n} conta usou um aplicativo de e-mail com protocolos legados nos últimos 30 dias.", other: "{n} contas usaram um aplicativo de e-mail com protocolos legados nos últimos 30 dias."},
|
||||||
// ── Administration: domains ────────────────────────────────────
|
// ── Administration: domains ────────────────────────────────────
|
||||||
"{n} accounts use this domain. Move or delete them first.": { one: "{n} conta usa este domínio. Mova-a ou exclua-a primeiro.", other: "{n} contas usam este domínio. Mova-as ou exclua-as primeiro." },
|
"{n} accounts use this domain. Move or delete them first.": { one: "{n} conta usa este domínio. Mova-a ou exclua-a primeiro.", other: "{n} contas usam este domínio. Mova-as ou exclua-as primeiro." },
|
||||||
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "O servidor deixa de aceitar e-mails para este domínio, e a {n} chave DKIM dele é excluída. Não é possível desfazer.", other: "O servidor deixa de aceitar e-mails para este domínio, e as {n} chaves DKIM dele são excluídas. Não é possível desfazer." },
|
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "O servidor deixa de aceitar e-mails para este domínio, e a {n} chave DKIM dele é excluída. Não é possível desfazer.", other: "O servidor deixa de aceitar e-mails para este domínio, e as {n} chaves DKIM dele são excluídas. Não é possível desfazer." },
|
||||||
|
|||||||
@@ -1696,8 +1696,29 @@ export const catalog: Catalog = {
|
|||||||
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} сообщит, если следующее письмо с этого адреса подпишет кто-то другой.",
|
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} сообщит, если следующее письмо с этого адреса подпишет кто-то другой.",
|
||||||
"itself, or an issuer it does not name": "самим собой или неназванным издателем",
|
"itself, or an issuer it does not name": "самим собой или неназванным издателем",
|
||||||
"no address": "нет адреса",
|
"no address": "нет адреса",
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"Legacy mail apps": "Почтовые приложения на устаревших протоколах",
|
||||||
|
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "Отключены для {tenant}. Входить на домены этой организации могут только {app} и приложения JMAP.",
|
||||||
|
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "Включены для {tenant}. Почтовые приложения могут использовать IMAP, POP3 и ManageSieve на доменах этой организации.",
|
||||||
|
"Turn legacy protocols back on": "Снова включить устаревшие почтовые протоколы",
|
||||||
|
"Turn off legacy protocols…": "Отключить устаревшие почтовые протоколы…",
|
||||||
|
"No account used a legacy mail app in the last 30 days.": "За последние 30 дней ни одна учётная запись не использовала почтовое приложение на устаревших протоколах.",
|
||||||
|
"Their mail apps will stop working the moment you turn this on:": "Их почтовые приложения перестанут работать сразу после того, как вы это включите:",
|
||||||
|
"Only {app} and JMAP apps will work.": "Работать будут только {app} и приложения JMAP.",
|
||||||
|
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "Устаревшие почтовые протоколы (IMAP, POP3, ManageSieve и отправка из почтовых приложений) будут отключены для всех в {tenant}.",
|
||||||
|
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "Почтовые приложения на телефонах и компьютерах перестанут получать и отправлять почту. Это Почта на iPhone и iPad, приложения Gmail и Outlook, Outlook, Thunderbird и Apple Mail. В них появятся ошибки входа.",
|
||||||
|
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "Фильтры, управляемые из почтового приложения (ManageSieve), перестанут работать. Фильтры, настроенные в {app}, продолжат работать.",
|
||||||
|
"Incoming mail is not affected. Calendars and contacts are not affected.": "Входящая почта не затрагивается. Календари и контакты не затрагиваются.",
|
||||||
|
"People keep full access through {app}, which can be installed as an app on phones and computers.": "У всех остаётся полный доступ через {app}, который можно установить как приложение на телефоны и компьютеры.",
|
||||||
|
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "Отправка из почтовых приложений (SMTP submission) перестанет работать, но её порты останутся открытыми: почтовым приложениям будет сообщено, что войти нельзя. Входящая почта (SMTP) и {app} (JMAP) не затрагиваются, и отключить их здесь нельзя.",
|
||||||
|
"You can turn legacy protocols back on at any time.": "Вы можете снова включить устаревшие почтовые протоколы в любой момент.",
|
||||||
|
"To confirm, type {phrase}": "Для подтверждения введите {phrase}",
|
||||||
|
"Turn off legacy protocols": "Отключить устаревшие почтовые протоколы",
|
||||||
|
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "Устаревшие почтовые протоколы отключены для вашей организации. Входить могут только {app} и приложения JMAP.",
|
||||||
},
|
},
|
||||||
plurals: {
|
plurals: {
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"{n} accounts used a legacy mail app in the last 30 days.": {one: "{n} учётная запись использовала почтовое приложение на устаревших протоколах за последние 30 дней.", few: "{n} учётные записи использовали почтовое приложение на устаревших протоколах за последние 30 дней.", many: "{n} учётных записей использовали почтовое приложение на устаревших протоколах за последние 30 дней.", other: "{n} учётной записи использовали почтовое приложение на устаревших протоколах за последние 30 дней."},
|
||||||
// ── Administration: domains ────────────────────────────────────
|
// ── Administration: domains ────────────────────────────────────
|
||||||
"{n} accounts use this domain. Move or delete them first.": { one: "Этот домен использует {n} учётная запись. Сначала перенесите или удалите её.", few: "Этот домен используют {n} учётные записи. Сначала перенесите или удалите их.", many: "Этот домен используют {n} учётных записей. Сначала перенесите или удалите их.", other: "Этот домен используют {n} учётной записи. Сначала перенесите или удалите их." },
|
"{n} accounts use this domain. Move or delete them first.": { one: "Этот домен использует {n} учётная запись. Сначала перенесите или удалите её.", few: "Этот домен используют {n} учётные записи. Сначала перенесите или удалите их.", many: "Этот домен используют {n} учётных записей. Сначала перенесите или удалите их.", other: "Этот домен используют {n} учётной записи. Сначала перенесите или удалите их." },
|
||||||
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "Сервер перестанет принимать почту для этого домена, и его {n} ключ DKIM будет удалён. Отменить это нельзя.", few: "Сервер перестанет принимать почту для этого домена, и его {n} ключа DKIM будут удалены. Отменить это нельзя.", many: "Сервер перестанет принимать почту для этого домена, и его {n} ключей DKIM будут удалены. Отменить это нельзя.", other: "Сервер перестанет принимать почту для этого домена, и его {n} ключа DKIM будут удалены. Отменить это нельзя." },
|
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "Сервер перестанет принимать почту для этого домена, и его {n} ключ DKIM будет удалён. Отменить это нельзя.", few: "Сервер перестанет принимать почту для этого домена, и его {n} ключа DKIM будут удалены. Отменить это нельзя.", many: "Сервер перестанет принимать почту для этого домена, и его {n} ключей DKIM будут удалены. Отменить это нельзя.", other: "Сервер перестанет принимать почту для этого домена, и его {n} ключа DKIM будут удалены. Отменить это нельзя." },
|
||||||
|
|||||||
@@ -1690,8 +1690,29 @@ export const catalog: Catalog = {
|
|||||||
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} повідомить, якщо наступний лист із цієї адреси підпише хтось інший.",
|
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} повідомить, якщо наступний лист із цієї адреси підпише хтось інший.",
|
||||||
"itself, or an issuer it does not name": "самим собою або неназваним видавцем",
|
"itself, or an issuer it does not name": "самим собою або неназваним видавцем",
|
||||||
"no address": "немає адреси",
|
"no address": "немає адреси",
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"Legacy mail apps": "Поштові програми на застарілих протоколах",
|
||||||
|
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "Вимкнено для {tenant}. Входити на домени цієї організації можуть лише {app} і програми JMAP.",
|
||||||
|
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "Увімкнено для {tenant}. Поштові програми можуть використовувати IMAP, POP3 і ManageSieve на доменах цієї організації.",
|
||||||
|
"Turn legacy protocols back on": "Знову увімкнути застарілі поштові протоколи",
|
||||||
|
"Turn off legacy protocols…": "Вимкнути застарілі поштові протоколи…",
|
||||||
|
"No account used a legacy mail app in the last 30 days.": "За останні 30 днів жоден обліковий запис не використовував поштову програму на застарілих протоколах.",
|
||||||
|
"Their mail apps will stop working the moment you turn this on:": "Їхні поштові програми перестануть працювати, щойно ви це ввімкнете:",
|
||||||
|
"Only {app} and JMAP apps will work.": "Працюватимуть лише {app} і програми JMAP.",
|
||||||
|
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "Застарілі поштові протоколи (IMAP, POP3, ManageSieve і надсилання з поштових програм) буде вимкнено для всіх у {tenant}.",
|
||||||
|
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "Поштові програми на телефонах і комп'ютерах перестануть отримувати й надсилати пошту. Це Пошта на iPhone та iPad, програми Gmail і Outlook, Outlook, Thunderbird і Apple Mail. У них з'являтимуться помилки входу.",
|
||||||
|
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "Фільтри, якими керують із поштової програми (ManageSieve), перестануть працювати. Фільтри, налаштовані в {app}, працюватимуть і далі.",
|
||||||
|
"Incoming mail is not affected. Calendars and contacts are not affected.": "Вхідну пошту це не зачіпає. Календарі й контакти це не зачіпає.",
|
||||||
|
"People keep full access through {app}, which can be installed as an app on phones and computers.": "Усі зберігають повний доступ через {app}, який можна встановити як програму на телефони й комп'ютери.",
|
||||||
|
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "Надсилання з поштових програм (SMTP submission) перестане працювати, але його порти залишаться відкритими: поштовим програмам повідомлять, що увійти не можна. Вхідну пошту (SMTP) і {app} (JMAP) це не зачіпає, і вимкнути їх тут не можна.",
|
||||||
|
"You can turn legacy protocols back on at any time.": "Ви можете знову ввімкнути застарілі поштові протоколи будь-коли.",
|
||||||
|
"To confirm, type {phrase}": "Для підтвердження введіть {phrase}",
|
||||||
|
"Turn off legacy protocols": "Вимкнути застарілі поштові протоколи",
|
||||||
|
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "Застарілі поштові протоколи вимкнено для вашої організації. Входити можуть лише {app} і програми JMAP.",
|
||||||
},
|
},
|
||||||
plurals: {
|
plurals: {
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"{n} accounts used a legacy mail app in the last 30 days.": {one: "{n} обліковий запис використовував поштову програму на застарілих протоколах за останні 30 днів.", few: "{n} облікові записи використовували поштову програму на застарілих протоколах за останні 30 днів.", many: "{n} облікових записів використовували поштову програму на застарілих протоколах за останні 30 днів.", other: "{n} облікового запису використовували поштову програму на застарілих протоколах за останні 30 днів."},
|
||||||
// ── Administration: domains ────────────────────────────────────
|
// ── Administration: domains ────────────────────────────────────
|
||||||
"{n} accounts use this domain. Move or delete them first.": { one: "Цей домен використовує {n} обліковий запис. Спершу перенесіть або видаліть його.", few: "Цей домен використовують {n} облікові записи. Спершу перенесіть або видаліть їх.", many: "Цей домен використовують {n} облікових записів. Спершу перенесіть або видаліть їх.", other: "Цей домен використовують {n} облікового запису. Спершу перенесіть або видаліть їх." },
|
"{n} accounts use this domain. Move or delete them first.": { one: "Цей домен використовує {n} обліковий запис. Спершу перенесіть або видаліть його.", few: "Цей домен використовують {n} облікові записи. Спершу перенесіть або видаліть їх.", many: "Цей домен використовують {n} облікових записів. Спершу перенесіть або видаліть їх.", other: "Цей домен використовують {n} облікового запису. Спершу перенесіть або видаліть їх." },
|
||||||
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "Сервер перестане приймати пошту для цього домену, і його {n} ключ DKIM буде видалено. Скасувати це неможливо.", few: "Сервер перестане приймати пошту для цього домену, і його {n} ключі DKIM буде видалено. Скасувати це неможливо.", many: "Сервер перестане приймати пошту для цього домену, і його {n} ключів DKIM буде видалено. Скасувати це неможливо.", other: "Сервер перестане приймати пошту для цього домену, і його {n} ключа DKIM буде видалено. Скасувати це неможливо." },
|
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "Сервер перестане приймати пошту для цього домену, і його {n} ключ DKIM буде видалено. Скасувати це неможливо.", few: "Сервер перестане приймати пошту для цього домену, і його {n} ключі DKIM буде видалено. Скасувати це неможливо.", many: "Сервер перестане приймати пошту для цього домену, і його {n} ключів DKIM буде видалено. Скасувати це неможливо.", other: "Сервер перестане приймати пошту для цього домену, і його {n} ключа DKIM буде видалено. Скасувати це неможливо." },
|
||||||
|
|||||||
@@ -1701,8 +1701,29 @@ export const catalog: Catalog = {
|
|||||||
"{app} will tell you if a later message from this address is signed by anybody else.": "如果此地址之后的邮件由他人签名,{app} 会提醒您。",
|
"{app} will tell you if a later message from this address is signed by anybody else.": "如果此地址之后的邮件由他人签名,{app} 会提醒您。",
|
||||||
"itself, or an issuer it does not name": "其自身,或一个未具名的颁发者",
|
"itself, or an issuer it does not name": "其自身,或一个未具名的颁发者",
|
||||||
"no address": "无地址",
|
"no address": "无地址",
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"Legacy mail apps": "使用传统协议的邮件应用",
|
||||||
|
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "{tenant} 已关闭。只有 {app} 和 JMAP 应用可以登录其域名。",
|
||||||
|
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "{tenant} 已开启。邮件应用可以在其域名上使用 IMAP、POP3 和 ManageSieve。",
|
||||||
|
"Turn legacy protocols back on": "重新开启传统邮件协议",
|
||||||
|
"Turn off legacy protocols…": "关闭传统邮件协议…",
|
||||||
|
"No account used a legacy mail app in the last 30 days.": "过去 30 天内没有账户使用过传统协议的邮件应用。",
|
||||||
|
"Their mail apps will stop working the moment you turn this on:": "您开启此项后,这些账户的邮件应用将立即停止工作:",
|
||||||
|
"Only {app} and JMAP apps will work.": "只有 {app} 和 JMAP 应用可以使用。",
|
||||||
|
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "{tenant} 中所有人的传统邮件协议(IMAP、POP3、ManageSieve 以及从邮件应用发信)都将被关闭。",
|
||||||
|
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "手机和电脑上的邮件应用将无法收发邮件,包括 iPhone 和 iPad 上的邮件、Gmail 和 Outlook 应用、Outlook、Thunderbird 以及 Apple Mail。用户会在这些应用中看到登录错误。",
|
||||||
|
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "通过邮件应用管理的过滤器(ManageSieve)将停止工作。在 {app} 中设置的过滤器会继续工作。",
|
||||||
|
"Incoming mail is not affected. Calendars and contacts are not affected.": "收件不受影响。日历和联系人不受影响。",
|
||||||
|
"People keep full access through {app}, which can be installed as an app on phones and computers.": "所有人仍可通过 {app} 完整访问,{app} 可以作为应用安装在手机和电脑上。",
|
||||||
|
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "从邮件应用发信(SMTP submission)将停止工作,但其端口保持开放:邮件应用会被告知无法登录。收件(SMTP)和 {app}(JMAP)不受影响,也无法在此关闭。",
|
||||||
|
"You can turn legacy protocols back on at any time.": "您可以随时重新开启传统邮件协议。",
|
||||||
|
"To confirm, type {phrase}": "请输入 {phrase} 以确认",
|
||||||
|
"Turn off legacy protocols": "关闭传统邮件协议",
|
||||||
|
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "您的组织已关闭传统邮件协议。只有 {app} 和 JMAP 应用可以登录。",
|
||||||
},
|
},
|
||||||
plurals: {
|
plurals: {
|
||||||
|
// ── Administration: legacy mail protocols (INBUXA) ──────────────
|
||||||
|
"{n} accounts used a legacy mail app in the last 30 days.": {other: "过去 30 天内有 {n} 个账户使用过传统协议的邮件应用。"},
|
||||||
// ── Administration: domains ────────────────────────────────────
|
// ── Administration: domains ────────────────────────────────────
|
||||||
"{n} accounts use this domain. Move or delete them first.": { other: "有 {n} 个账户正在使用此域名。请先移动或删除它们。" },
|
"{n} accounts use this domain. Move or delete them first.": { other: "有 {n} 个账户正在使用此域名。请先移动或删除它们。" },
|
||||||
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { other: "服务器将不再接收此域名的邮件,其 {n} 个 DKIM 密钥也会被删除。此操作无法撤销。" },
|
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { other: "服务器将不再接收此域名的邮件,其 {n} 个 DKIM 密钥也会被删除。此操作无法撤销。" },
|
||||||
|
|||||||
@@ -1726,6 +1726,12 @@ select optgroup { background-color: var(--bg-elev); color: var(--fg); }
|
|||||||
.admin-notice { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; border-radius: var(--radius-sm); background: var(--bg-sunken); color: var(--fg-muted); font-size: .92em; margin: 8px 0; }
|
.admin-notice { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; border-radius: var(--radius-sm); background: var(--bg-sunken); color: var(--fg-muted); font-size: .92em; margin: 8px 0; }
|
||||||
.admin-notice svg { flex: none; margin-top: 2px; }
|
.admin-notice svg { flex: none; margin-top: 2px; }
|
||||||
.admin-notice.warn { background: var(--warn-soft); color: var(--fg); }
|
.admin-notice.warn { background: var(--warn-soft); color: var(--fg); }
|
||||||
|
/* INBUXA: a tenant's legacy mail protocols switch (legacy-protocols LP-15 to LP-17) */
|
||||||
|
.admin-legacy-confirm { display: grid; gap: 12px; margin-top: 12px; }
|
||||||
|
.admin-legacy-box { padding: 10px 12px; border-radius: var(--radius-sm); background: var(--bg-sunken); font-size: .92em; }
|
||||||
|
.admin-legacy-box.warn { background: var(--warn-soft); color: var(--fg); }
|
||||||
|
.admin-legacy-box p { margin: 0 0 6px; }
|
||||||
|
.admin-legacy-box ul { margin: 6px 0; padding-left: 18px; }
|
||||||
.admin-notice.warn svg { color: var(--warn); }
|
.admin-notice.warn svg { color: var(--warn); }
|
||||||
.admin-notice.error { background: var(--danger-soft); color: var(--fg); }
|
.admin-notice.error { background: var(--danger-soft); color: var(--fg); }
|
||||||
.admin-sheet { position: absolute; top: 0; right: 0; bottom: 0; width: min(460px, 100%); z-index: 20; display: flex; flex-direction: column; background: var(--bg-elev); border-left: 1px solid var(--border); box-shadow: var(--shadow-3); animation: admin-sheet-in .18s var(--ease); }
|
.admin-sheet { position: absolute; top: 0; right: 0; bottom: 0; width: min(460px, 100%); z-index: 20; display: flex; flex-direction: column; background: var(--bg-elev); border-left: 1px solid var(--border); box-shadow: var(--shadow-3); animation: admin-sheet-in .18s var(--ease); }
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { useEffect, useState, type ReactNode } from "react";
|
import { useEffect, useState, type ReactNode } from "react";
|
||||||
import { Link } from "wouter";
|
import { Link } from "wouter";
|
||||||
import { ArrowDownToLine, ArrowUpFromLine, ExternalLink, Globe, Hourglass, LayoutDashboard, MemoryStick, RefreshCw, Users } from "lucide-react";
|
import { ArrowDownToLine, ArrowUpFromLine, ExternalLink, Globe, Hourglass, LayoutDashboard, MemoryStick, RefreshCw, ShieldCheck, Users } from "lucide-react";
|
||||||
|
import { legacyProtocolsOff } from "@/jmap/client";
|
||||||
|
import { useAppName } from "@/lib/brand";
|
||||||
import { adminSections, dashboardCards, type DashboardCard } from "@/lib/admin/adminAccess";
|
import { adminSections, dashboardCards, type DashboardCard } from "@/lib/admin/adminAccess";
|
||||||
import { balancedColumns, countObjects, DASHBOARD_WINDOW_MS, isRefused, loadMetrics, summarizeMetrics, type MessageStats } from "@/lib/admin/adminDashboard";
|
import { balancedColumns, countObjects, DASHBOARD_WINDOW_MS, isRefused, loadMetrics, summarizeMetrics, type MessageStats } from "@/lib/admin/adminDashboard";
|
||||||
import { formatDayMonthTime, resolvedLocale } from "@/lib/datetime";
|
import { formatDayMonthTime, resolvedLocale } from "@/lib/datetime";
|
||||||
@@ -35,6 +37,8 @@ async function settle<T>(work: Promise<T>): Promise<Loaded<T>> {
|
|||||||
export function AdminDashboard() {
|
export function AdminDashboard() {
|
||||||
const perms = usePermissions();
|
const perms = usePermissions();
|
||||||
const adminUrl = useSession((s) => s.session?.ihasmail?.server?.adminUrl ?? null);
|
const adminUrl = useSession((s) => s.session?.ihasmail?.server?.adminUrl ?? null);
|
||||||
|
const legacyOff = useSession((s) => legacyProtocolsOff(s.session, s.accountId));
|
||||||
|
const app = useAppName();
|
||||||
const cards = dashboardCards(perms);
|
const cards = dashboardCards(perms);
|
||||||
const sections = adminSections(perms);
|
const sections = adminSections(perms);
|
||||||
const [reload, setReload] = useState(0);
|
const [reload, setReload] = useState(0);
|
||||||
@@ -102,6 +106,13 @@ export function AdminDashboard() {
|
|||||||
<RefreshCw size={18} />
|
<RefreshCw size={18} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
{legacyOff && (
|
||||||
|
// INBUXA legacy-protocols LP-18: while it's off for the organization
|
||||||
|
<p className="admin-notice">
|
||||||
|
<ShieldCheck size={16} />
|
||||||
|
<span>{t("Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.", { app })}</span>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
{shown.length ? (
|
{shown.length ? (
|
||||||
<div className="admin-cards-wrap">
|
<div className="admin-cards-wrap">
|
||||||
<div className="admin-cards">
|
<div className="admin-cards">
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useAppName } from "@/lib/brand";
|
||||||
|
import { can } from "@/lib/admin/adminAccess";
|
||||||
|
import {
|
||||||
|
CONFIRM_PHRASE,
|
||||||
|
fetchTenantLegacy,
|
||||||
|
impactEntries,
|
||||||
|
phraseMatches,
|
||||||
|
setTenantLegacy,
|
||||||
|
type TenantLegacy,
|
||||||
|
} from "@/lib/admin/adminLegacyProtocols";
|
||||||
|
import { formatRelative } from "@/lib/format";
|
||||||
|
import { plural, t, tNode } from "@/lib/i18n";
|
||||||
|
import { toast } from "@/ui/toast";
|
||||||
|
import { usePermissions } from "./usePermissions";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tenant's legacy mail protocols switch, in its sheet (INBUXA
|
||||||
|
* legacy-protocols LP-9 to LP-17, at tenant scope).
|
||||||
|
*
|
||||||
|
* Nobody should turn it on by accident or without understanding it: turning
|
||||||
|
* it off shows who would notice (LP-15) and what it means (LP-16) before the
|
||||||
|
* typed phrase is asked for (LP-17). Turning it back on is one click -- undoing
|
||||||
|
* a restriction must never be the hard part. A tenant's switch closes no port,
|
||||||
|
* so the statement names none.
|
||||||
|
*
|
||||||
|
* Only INBUXA has it; on any other server the section isn't there.
|
||||||
|
*/
|
||||||
|
export function TenantLegacyProtocols({ tenantId, tenantName }: { tenantId: string; tenantName: string }) {
|
||||||
|
const perms = usePermissions();
|
||||||
|
const app = useAppName();
|
||||||
|
const canChange = can(perms, "Domain", "Update");
|
||||||
|
const [state, setState] = useState<TenantLegacy | null>(null);
|
||||||
|
const [confirming, setConfirming] = useState(false);
|
||||||
|
const [typed, setTyped] = useState("");
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
const [revision, setRevision] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let canceled = false;
|
||||||
|
void fetchTenantLegacy(tenantId).then((s) => !canceled && setState(s));
|
||||||
|
return () => {
|
||||||
|
canceled = true;
|
||||||
|
};
|
||||||
|
}, [tenantId, revision]);
|
||||||
|
|
||||||
|
if (!state) return null;
|
||||||
|
|
||||||
|
const turn = async (off: boolean) => {
|
||||||
|
setBusy(true);
|
||||||
|
try {
|
||||||
|
await setTenantLegacy(tenantId, off);
|
||||||
|
setConfirming(false);
|
||||||
|
setTyped("");
|
||||||
|
setRevision((r) => r + 1);
|
||||||
|
} catch (err) {
|
||||||
|
toast.error((err as Error).message);
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const entries = state.recent ? impactEntries(state.recent) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h3>{t("Legacy mail apps")}</h3>
|
||||||
|
<p>
|
||||||
|
{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 })}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{canChange && state.off && (
|
||||||
|
<button className="btn" disabled={busy} onClick={() => void turn(false)}>
|
||||||
|
{t("Turn legacy protocols back on")}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{canChange && !state.off && !confirming && (
|
||||||
|
<button className="btn btn-danger" onClick={() => setConfirming(true)}>
|
||||||
|
{t("Turn off legacy protocols…")}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!state.off && confirming && (
|
||||||
|
<div className="admin-legacy-confirm">
|
||||||
|
{entries &&
|
||||||
|
(entries.length === 0 ? (
|
||||||
|
<p className="hint">{t("No account used a legacy mail app in the last 30 days.")}</p>
|
||||||
|
) : (
|
||||||
|
<div className="admin-legacy-box">
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
{plural(entries.length, {
|
||||||
|
one: "{n} account used a legacy mail app in the last 30 days.",
|
||||||
|
other: "{n} accounts used a legacy mail app in the last 30 days.",
|
||||||
|
}, { n: entries.length })}
|
||||||
|
</b>{" "}
|
||||||
|
{t("Their mail apps will stop working the moment you turn this on:")}
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
{entries.map((e) => (
|
||||||
|
<li key={e.name}>
|
||||||
|
<span className="notranslate" translate="no">{e.name}</span>: {e.protocols.join(", ")},{" "}
|
||||||
|
{formatRelative(new Date(e.lastUsedAt).toISOString())}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<div className="admin-legacy-box warn">
|
||||||
|
<p><b>{t("Only {app} and JMAP apps will work.", { app })}</b></p>
|
||||||
|
<p>
|
||||||
|
{t("Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.", { tenant: tenantName })}
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>{t("Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.")}</li>
|
||||||
|
<li>{t("Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.", { app })}</li>
|
||||||
|
<li>{t("Incoming mail is not affected. Calendars and contacts are not affected.")}</li>
|
||||||
|
<li>{t("People keep full access through {app}, which can be installed as an app on phones and computers.", { app })}</li>
|
||||||
|
</ul>
|
||||||
|
<p>{t("Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.", { app })}</p>
|
||||||
|
<p>{t("You can turn legacy protocols back on at any time.")}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="field">
|
||||||
|
<label htmlFor="admin-legacy-confirm">
|
||||||
|
{tNode("To confirm, type {phrase}", { phrase: <code>{CONFIRM_PHRASE}</code> })}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="admin-legacy-confirm"
|
||||||
|
className="input"
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck={false}
|
||||||
|
value={typed}
|
||||||
|
onChange={(e) => setTyped(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="row gap-8">
|
||||||
|
<button className="btn btn-danger" disabled={busy || !phraseMatches(typed)} onClick={() => void turn(true)}>
|
||||||
|
{t("Turn off legacy protocols")}
|
||||||
|
</button>
|
||||||
|
<button className="btn" disabled={busy} onClick={() => { setConfirming(false); setTyped(""); }}>
|
||||||
|
{t("Cancel")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ import { Dialog } from "@/ui/dialog";
|
|||||||
import { Spinner } from "@/ui/misc";
|
import { Spinner } from "@/ui/misc";
|
||||||
import { toast } from "@/ui/toast";
|
import { toast } from "@/ui/toast";
|
||||||
import { usePermissions } from "./usePermissions";
|
import { usePermissions } from "./usePermissions";
|
||||||
|
import { TenantLegacyProtocols } from "./TenantLegacyProtocols";
|
||||||
|
|
||||||
const GIB = 1024 ** 3;
|
const GIB = 1024 ** 3;
|
||||||
|
|
||||||
@@ -203,6 +204,8 @@ export function TenantSheet({ tenant, roles, onClose, onChanged, onCreated, onDe
|
|||||||
|
|
||||||
<h3>{t("Domains")}</h3>
|
<h3>{t("Domains")}</h3>
|
||||||
<TenantDomains tenant={tenant} canChange={can(perms, "Domain", "Update")} onChanged={() => { setRevision((n) => n + 1); onChanged(); }} />
|
<TenantDomains tenant={tenant} canChange={can(perms, "Domain", "Update")} onChanged={() => { setRevision((n) => n + 1); onChanged(); }} />
|
||||||
|
|
||||||
|
<TenantLegacyProtocols tenantId={tenant.id} tenantName={tenant.name} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { act } from "react";
|
||||||
|
import { createRoot, type Root } from "react-dom/client";
|
||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { useSession } from "@/store/session";
|
||||||
|
import type { JmapSession } from "@/jmap/types";
|
||||||
|
import type { TenantLegacy } from "@/lib/admin/adminLegacyProtocols";
|
||||||
|
|
||||||
|
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||||
|
|
||||||
|
const api = vi.hoisted(() => ({
|
||||||
|
state: null as TenantLegacy | null,
|
||||||
|
set: vi.fn(async () => {}),
|
||||||
|
}));
|
||||||
|
vi.mock("@/lib/admin/adminLegacyProtocols", async (original) => ({
|
||||||
|
...(await original<typeof import("@/lib/admin/adminLegacyProtocols")>()),
|
||||||
|
fetchTenantLegacy: vi.fn(async () => api.state),
|
||||||
|
setTenantLegacy: api.set,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { TenantLegacyProtocols } = await import("../TenantLegacyProtocols");
|
||||||
|
|
||||||
|
const signIn = (permissions: string[]) =>
|
||||||
|
useSession.setState({ session: { capabilities: {}, accounts: {}, primaryAccounts: {}, username: "[email protected]", ihasmail: { permissions } } as unknown as JmapSession });
|
||||||
|
const button = (host: HTMLElement, label: string) => [...host.querySelectorAll("button")].find((b) => b.textContent?.trim() === label);
|
||||||
|
const type = async (el: HTMLInputElement, value: string) => {
|
||||||
|
await act(async () => {
|
||||||
|
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!.call(el, value);
|
||||||
|
el.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INBUXA legacy-protocols at tenant scope: nobody turns it off by accident
|
||||||
|
* (the statement and a typed phrase first), and turning it back on is one
|
||||||
|
* click.
|
||||||
|
*/
|
||||||
|
describe("a tenant's legacy mail protocols switch", () => {
|
||||||
|
let host: HTMLDivElement;
|
||||||
|
let root: Root;
|
||||||
|
const render = async () => {
|
||||||
|
await act(async () => {
|
||||||
|
root.render(<TenantLegacyProtocols tenantId="t1" tenantName="Acme Corp" />);
|
||||||
|
});
|
||||||
|
await act(async () => {});
|
||||||
|
};
|
||||||
|
beforeEach(() => {
|
||||||
|
host = document.createElement("div");
|
||||||
|
document.body.appendChild(host);
|
||||||
|
root = createRoot(host);
|
||||||
|
api.set.mockClear();
|
||||||
|
});
|
||||||
|
afterEach(async () => {
|
||||||
|
await act(async () => root.unmount());
|
||||||
|
host.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isn't there on a server without the switch", async () => {
|
||||||
|
api.state = null;
|
||||||
|
signIn(["sysDomainGet", "sysDomainUpdate"]);
|
||||||
|
await render();
|
||||||
|
expect(host.textContent).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows who would notice and what it means, then asks for the exact phrase", async () => {
|
||||||
|
api.state = { off: false, recent: [{ accountId: "a", name: "[email protected]", protocol: "imap", lastUsedAt: Date.now() - 2 * 86400_000 }] };
|
||||||
|
signIn(["sysDomainGet", "sysDomainUpdate"]);
|
||||||
|
await render();
|
||||||
|
await act(async () => button(host, "Turn off legacy protocols…")!.click());
|
||||||
|
|
||||||
|
expect(host.textContent).toContain("1 account used a legacy mail app in the last 30 days.");
|
||||||
|
expect(host.textContent).toContain("[email protected]: IMAP");
|
||||||
|
expect(host.textContent).toContain("turned off for everyone in Acme Corp");
|
||||||
|
// A tenant's switch closes no port, so the statement names none.
|
||||||
|
expect(host.textContent).not.toContain("firewall");
|
||||||
|
|
||||||
|
const confirm = button(host, "Turn off legacy protocols")!;
|
||||||
|
const box = host.querySelector<HTMLInputElement>("#admin-legacy-confirm")!;
|
||||||
|
await type(box, "Turn off legacy mail");
|
||||||
|
expect(confirm.disabled).toBe(true);
|
||||||
|
await type(box, "turn off legacy mail");
|
||||||
|
expect(confirm.disabled).toBe(false);
|
||||||
|
await act(async () => confirm.click());
|
||||||
|
expect(api.set).toHaveBeenCalledWith("t1", true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("turns it back on with one click", async () => {
|
||||||
|
api.state = { off: true, recent: [] };
|
||||||
|
signIn(["sysDomainGet", "sysDomainUpdate"]);
|
||||||
|
await render();
|
||||||
|
await act(async () => button(host, "Turn legacy protocols back on")!.click());
|
||||||
|
expect(api.set).toHaveBeenCalledWith("t1", false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows the state but no switch to someone who can't change domains", async () => {
|
||||||
|
api.state = { off: true, recent: null };
|
||||||
|
signIn(["sysDomainGet"]);
|
||||||
|
await render();
|
||||||
|
expect(host.textContent).toContain("Off for Acme Corp");
|
||||||
|
expect(host.querySelector("button")).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user