import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatIconModule } from '@angular/material/icon'; import { ApiService } from '../../core/api.service'; import { MailStateService } from '../../core/mail-state.service'; import { Mailbox, errorText, formatBytes } from '../../core/models'; import { ToastService } from '../../ui/toast.service'; import { AuthService } from '../../core/auth.service'; /** Settings → Mailboxes (administrators): create no-reply@, monitor@, support@ …, new password, switch off. */ @Component({ selector: 'app-mailboxes-section', imports: [FormsModule, MatIconModule], changeDetection: ChangeDetectionStrategy.OnPush, // ячейка сетки настроек — сам элемент компонента: на две колонки host: { class: 'block xl:col-span-2' }, template: ` group MAILBOXES Address @ @if (domains().length > 1) { @for (d of domains(); track d) { {{ d }} } } @else { {{ domain() }} } Name Password (empty — generate) {{ showPw() ? 'visibility_off' : 'visibility' }} add CREATE shared mailbox (support@, billing@): no password of its own — members open it from their mailbox, read/unread is common @if (error()) { {{ error() }} } @if (secret(); as s) { Password of {{ s.email }} — shown once, copy it now {{ s.password }} content_copy close } @for (m of mailboxes(); track m.id) { {{ m.email }} {{ m.displayName }} @if (m.shared) { SHARED · {{ m.members.length }} } @if (!m.shared && m.members.length) { +{{ m.members.length }} } @if (m.system) { SYSTEM } @if (m.trap) { TRAP } {{ size(m.usedBytes) }} @if (m.email !== me()) { mark_email_read OPEN } group {{ m.trap ? 'move_to_inbox' : 'outbox' }} @if (!m.shared && !m.admin) { dns } @if (!m.shared) { key } @if (m.email !== me()) { {{ m.active ? 'toggle_on' : 'toggle_off' }} delete } @if (membersOf() === m.id) { Who may open {{ m.email }} from their own mailbox: @for (o of mailboxes(); track o.id) { @if (o.id !== m.id && !o.shared) { {{ o.email }} } } save SAVE CANCEL } } ` }) export class MailboxesSection { private api = inject(ApiService); private state = inject(MailStateService); private toasts = inject(ToastService); private auth = inject(AuthService); readonly mailboxes = signal([]); readonly secret = signal<{ email: string; password: string } | null>(null); readonly error = signal(''); readonly busy = signal(false); readonly me = computed(() => this.state.profile()?.email ?? ''); readonly showPw = signal(false); readonly local = signal(''); // Домены сервера (/api/config); пока не пришли — домен своего ящика readonly domains = signal([]); readonly domain = signal(''); name = ''; password = ''; shared = false; /** mailbox whose members are being edited */ readonly membersOf = signal(null); readonly picked = signal>(new Set()); constructor() { this.domain.set(this.me().split('@')[1] ?? ''); this.api.config().subscribe({ next: c => { this.domains.set(c.domains); if (!c.domains.includes(this.domain())) { this.domain.set(c.domains[0] ?? ''); } }, error: () => {} }); this.load(); } size(bytes: number): string { return formatBytes(bytes); } load(): void { this.api.mailboxes().subscribe({ next: list => this.mailboxes.set(list), error: e => this.error.set(errorText(e)) }); } create(): void { const local = this.local().replace(/@.*$/, ''); if (!local) { this.error.set('Enter an address'); return; } const email = local + '@' + this.domain(); this.error.set(''); this.busy.set(true); this.api.createMailbox(email, this.name.trim(), this.password, this.shared).subscribe({ next: created => { this.busy.set(false); this.local.set(''); this.name = this.password = ''; this.shared = false; if (created.password) { this.secret.set({ email: created.mailbox.email, password: created.password }); } this.toasts.ok('Mailbox ' + created.mailbox.email + ' created'); this.load(); }, error: e => { this.busy.set(false); this.error.set(errorText(e)); } }); } newPassword(m: Mailbox): void { if (!confirm('New password for ' + m.email + '? The old one stops working at once.')) return; this.api.newMailboxPassword(m.id).subscribe({ next: created => created.password && this.secret.set({ email: m.email, password: created.password }), error: e => this.toasts.error(errorText(e)) }); } /** Administrator reads this mailbox: its session; the banner on top leads back. Full reload — fresh folders. */ open(m: Mailbox): void { this.api.openMailbox(m.id).subscribe({ next: session => { this.auth.openAs(session); location.assign('/'); }, error: e => this.toasts.error(errorText(e)) }); } /** Trap on: what the mailbox sends is kept in its own INBOX (staging@). Off: it sends for real again. */ system(m: Mailbox): void { const on = !m.system; if (!confirm(on ? `${m.email} becomes a system mailbox: no web login, the built-in administrators open it. Programs keep its password.` : `${m.email} becomes an ordinary mailbox again.`)) return; this.api.setMailboxSystem(m.id, on).subscribe({ next: () => { this.toasts.ok(m.email + (on ? ' is a system mailbox' : ' is ordinary')); this.load(); }, error: e => this.toasts.error(errorText(e)) }); } trap(m: Mailbox): void { if (m.trap && !confirm('Switch the trap off? ' + m.email + ' will send mail to real people again.')) return; this.api.setMailboxTrap(m.id, !m.trap).subscribe({ next: () => { this.toasts.ok('Trap ' + (m.trap ? 'off' : 'on') + ' for ' + m.email); this.load(); }, error: e => this.toasts.error(errorText(e)) }); } editMembers(m: Mailbox): void { if (this.membersOf() === m.id) { this.membersOf.set(null); return; } this.picked.set(new Set(m.members)); this.membersOf.set(m.id); } pick(email: string): void { const next = new Set(this.picked()); if (next.has(email)) { next.delete(email); } else { next.add(email); } this.picked.set(next); } saveMembers(m: Mailbox): void { this.api.setMailboxMembers(m.id, [...this.picked()]).subscribe({ next: () => { this.toasts.ok('Members of ' + m.email + ' saved'); this.membersOf.set(null); this.load(); }, error: e => this.toasts.error(errorText(e)) }); } toggle(m: Mailbox): void { this.api.setMailboxActive(m.id, !m.active).subscribe({ next: () => this.load(), error: e => this.toasts.error(errorText(e)) }); } remove(m: Mailbox): void { if (!confirm('Delete ' + m.email + ' with ALL its mail? This can not be undone.')) return; this.api.deleteMailbox(m.id).subscribe({ next: () => { this.toasts.ok(m.email + ' deleted'); this.load(); }, error: e => this.toasts.error(errorText(e)) }); } copy(text: string): void { navigator.clipboard?.writeText(text).then(() => this.toasts.ok('Copied'), () => this.toasts.error('Copy it by hand')); } }
{{ error() }}
{{ s.password }}