From 51a003e078470fdce5bb526353a91cc40af19973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Tue, 2 Jun 2026 14:49:50 +0300 Subject: [PATCH] =?UTF-8?q?v0.5.10:=20/export=20=E2=80=94=20attachment=20d?= =?UTF-8?q?efault,=20=3Fview=3D1=20inline,=20=3Ffilename=3Dcustom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/api/routes/entries.js | 4 +++- src/routes/export.js | 15 +++++++++++---- ui/routes/export.js | 13 +++++++++++-- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 60f7130..106847e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipwhitelist", - "version": "0.5.9", + "version": "0.5.10", "description": "IP WhiteList microservice for cloud provider", "main": "server.js", "scripts": { diff --git a/src/api/routes/entries.js b/src/api/routes/entries.js index 20210cf..66271b2 100644 --- a/src/api/routes/entries.js +++ b/src/api/routes/entries.js @@ -95,7 +95,9 @@ function createEntriesRouter({ q }) { const cidrs = await q.getExportCIDRs(companyId); const aggregated = aggregateCIDRs(cidrs); res.setHeader('Content-Type', 'text/plain; charset=utf-8'); - res.setHeader('Content-Disposition', 'inline; filename="whitelist.txt"'); + const fname = req.query.filename || 'white-list.txt'; + const disp = req.query.view === '1' ? 'inline' : 'attachment'; + res.setHeader('Content-Disposition', `${disp}; filename="${fname}"`); res.send(aggregated.join('\n') + (aggregated.length ? '\n' : '')); } catch (e) { res.status(500).json({ error: e.message }); diff --git a/src/routes/export.js b/src/routes/export.js index 5292ee8..dcc68b7 100644 --- a/src/routes/export.js +++ b/src/routes/export.js @@ -39,8 +39,12 @@ function createRouter({ q, exportLimiter, aggregateCIDRs }) { if (!req.user.isAdmin) { // Обычный пользователь: только его компания. - // getOrCreateCompany — атомарная UPSERT, безопасна для параллельных вызовов. - const company = await q.getOrCreateCompany(req.user.clientId, req.user.companyName); + // ?client_id= для мульти-компании + const requestedId = (req.query.client_id || '').trim(); + const effectiveClientId = (requestedId && req.user.allClientIds && req.user.allClientIds.includes(requestedId)) + ? requestedId + : req.user.clientId; + const company = await q.getOrCreateCompany(effectiveClientId, effectiveClientId); companyId = company.id; } else if (req.query.company) { // Администратор с необязательным фильтром по компании. @@ -56,9 +60,12 @@ function createRouter({ q, exportLimiter, aggregateCIDRs }) { const aggregated = aggregateCIDRs(cidrs); // Content-Type: text/plain — чтобы браузер не пытался разбирать как HTML. - // Content-Disposition: inline — открывается в браузере, а не скачивается. + // ?view=1 → inline (просмотр в браузере), иначе → attachment (скачивание) + // ?filename=X → своё имя файла, по умолчанию white-list.txt res.setHeader('Content-Type', 'text/plain; charset=utf-8'); - res.setHeader('Content-Disposition', 'inline; filename="whitelist.txt"'); + const fname = req.query.filename || 'white-list.txt'; + const disp = req.query.view === '1' ? 'inline' : 'attachment'; + res.setHeader('Content-Disposition', `${disp}; filename="${fname}"`); // Один CIDR на строку. Если список пустой — возвращаем пустую строку (не ошибку). res.send(aggregated.join('\n') + (aggregated.length ? '\n' : '')); diff --git a/ui/routes/export.js b/ui/routes/export.js index bfb9556..3ac8892 100644 --- a/ui/routes/export.js +++ b/ui/routes/export.js @@ -14,11 +14,20 @@ function createRouter() { router.get('/export', async (req, res) => { const token = api.token(req); - const companyQuery = req.query.company ? '?company=' + parseInt(req.query.company, 10) : ''; + // Прокидываем query-параметры в API + const params = new URLSearchParams(); + if (req.query.company) params.set('company', req.query.company); + if (req.query.client_id) params.set('client_id', req.query.client_id); + const qs = params.toString(); + const companyQuery = qs ? '?' + qs : ''; try { const r = await api.get('/api/v1/entries/export' + companyQuery, token); res.set('Content-Type', 'text/plain; charset=utf-8'); - res.set('Content-Disposition', 'inline; filename="whitelist.txt"'); + // ?view=1 → inline (просмотр), иначе → attachment (скачивание) + // ?filename=X → своё имя, по умолчанию white-list.txt + const fname = req.query.filename || 'white-list.txt'; + const disp = req.query.view === '1' ? 'inline' : 'attachment'; + res.set('Content-Disposition', disp + '; filename="' + fname + '"'); res.send(r.data); } catch (e) { res.status(500).send('Ошибка экспорта: ' + e.message);