fix(email): match IMAP card to SMTP styling + auto-detect mailbox folders
- IncomingMailConfigCard rebuilt to mirror the outgoing SMTP card: Card padding=md, icon inputs (Server/User/Lock), password eye toggle, stacked full-width fields, full-width primary Save button - Folder is now a dropdown auto-populated by a 'Detect' button instead of a free-text path: backend emailIntakeService.listFolders() lists IMAP mailboxes (POST /admin/email/incoming-config/folders, accepts current form creds, masked password falls back to stored); UI auto-selects the inbox (special-use) folder - en/de strings added
This commit is contained in:
@@ -171,6 +171,29 @@ router.post('/incoming-config', [
|
||||
});
|
||||
|
||||
// Received-emails log (the IMAP poller's audit trail) — "Received emails" tab.
|
||||
// List IMAP folders so the UI can offer a dropdown (auto-detect) instead of a
|
||||
// free-text path. Accepts optional creds in the body to detect before saving;
|
||||
// falls back to the stored config (and stored password when masked).
|
||||
router.post('/incoming-config/folders', adminAuth, requirePermission('email.view'), async (req, res) => {
|
||||
try {
|
||||
const { imap_host, imap_port, imap_secure, imap_user, imap_pass } = req.body || {};
|
||||
if (imap_host) {
|
||||
const { isPrivateIP } = require('../utils/networkValidation');
|
||||
if (isPrivateIP(imap_host)) {
|
||||
return res.status(400).json({ error: 'IMAP host cannot point to a private or internal network address' });
|
||||
}
|
||||
}
|
||||
const emailIntakeService = require('../services/emailIntakeService');
|
||||
const folders = await emailIntakeService.listFolders(
|
||||
imap_host ? { host: imap_host, port: imap_port, secure: imap_secure, user: imap_user, pass: imap_pass } : undefined
|
||||
);
|
||||
res.json({ folders });
|
||||
} catch (error) {
|
||||
console.error('IMAP folder detection error:', error);
|
||||
res.status(502).json({ error: 'Could not connect to the mailbox. Check host, port and credentials.' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/received', adminAuth, requirePermission('email.view'), async (req, res) => {
|
||||
try {
|
||||
const page = Math.max(1, parseInt(req.query.page, 10) || 1);
|
||||
|
||||
@@ -48,6 +48,41 @@ async function saveAttachment(att) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* List the mailbox folders on the IMAP server so the UI can offer a
|
||||
* dropdown instead of a free-text path. Uses the saved config; an
|
||||
* `override` ({ host, port, secure, user, pass }) lets the admin detect
|
||||
* folders BEFORE saving. A masked/blank override password falls back to
|
||||
* the stored one. Returns [{ path, name, specialUse }] (specialUse like
|
||||
* '\\Inbox' lets the caller auto-select the inbox).
|
||||
*/
|
||||
async function listFolders(override) {
|
||||
let cfg;
|
||||
if (override && override.host && override.user) {
|
||||
cfg = {
|
||||
host: override.host,
|
||||
port: override.port || 993,
|
||||
secure: override.secure !== false && override.secure !== 0,
|
||||
auth: { user: override.user, pass: override.pass || '' },
|
||||
};
|
||||
if (!cfg.auth.pass || cfg.auth.pass === '********') {
|
||||
const stored = await getImapConfig();
|
||||
cfg.auth.pass = stored?.auth?.pass || '';
|
||||
}
|
||||
} else {
|
||||
cfg = await getImapConfig();
|
||||
}
|
||||
if (!cfg) return [];
|
||||
const client = new ImapFlow({ host: cfg.host, port: cfg.port, secure: cfg.secure, auth: cfg.auth, logger: false });
|
||||
await client.connect();
|
||||
try {
|
||||
const list = await client.list();
|
||||
return (list || []).map((m) => ({ path: m.path, name: m.name, specialUse: m.specialUse || null }));
|
||||
} finally {
|
||||
await client.logout().catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
/** Poll the mailbox once. Safe to call repeatedly; self-skips when busy/off. */
|
||||
async function pollOnce() {
|
||||
if (polling) return { skipped: 'busy' };
|
||||
@@ -120,4 +155,4 @@ function startIncomingMailPoller() {
|
||||
logger.info?.('Incoming-mail poller started (every 60s when enabled)');
|
||||
}
|
||||
|
||||
module.exports = { pollOnce, startIncomingMailPoller, _internal: { getImapConfig, isEnabled, saveAttachment } };
|
||||
module.exports = { pollOnce, startIncomingMailPoller, listFolders, _internal: { getImapConfig, isEnabled, saveAttachment } };
|
||||
|
||||
Reference in New Issue
Block a user