diff --git a/.codeclimate.yml b/.codeclimate.yml
index 261f7f0..b43f449 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -1,16 +1,16 @@
engines:
eslint:
enabled: true
- channel: "eslint-8"
+ channel: 'eslint-8'
config:
- config: ".eslintrc.yaml"
+ config: '.eslintrc.yaml'
ratings:
paths:
- - "**.js"
+ - '**.js'
exclude_patterns:
- - "html/jquery.tipsy.js"
+ - 'html/jquery.tipsy.js'
checks:
return-statements:
diff --git a/.eslintrc.yaml b/.eslintrc.yaml
index 0fcbe84..7c9f547 100644
--- a/.eslintrc.yaml
+++ b/.eslintrc.yaml
@@ -6,7 +6,7 @@ env:
browser: true
es2022: true
-extends: "@haraka"
+extends: '@haraka'
rules:
- no-unused-vars: ["error", { args: none }]
+ no-unused-vars: ['error', { args: none }]
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 7e97bed..8314a66 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -7,7 +7,7 @@ on:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- - cron: "18 7 * * 4"
+ - cron: '18 7 * * 4'
jobs:
codeql:
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..8ded5e0
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,2 @@
+singleQuote: true
+semi: false
diff --git a/README.md b/README.md
index 529a220..f33e4b1 100644
--- a/README.md
+++ b/README.md
@@ -19,8 +19,7 @@ Enjoy the blinky lights.
## Tips
-- Hover your mouse pointer or tap (with touch devices) on table data to see more
- details.
+- Hover your mouse pointer or tap (with touch devices) on table data to see more details.
- Copy that connection UUID at left and use it to grep your logs for even more.
- Edit the files in watch/html and play with the appearance. If you make it
better, post a screen shot somewhere and create an Issue or PR.
diff --git a/html/client.js b/html/client.js
index 155ac43..ca8a0bc 100644
--- a/html/client.js
+++ b/html/client.js
@@ -1,36 +1,36 @@
-"use strict";
-
-let ws;
-let connect_cols;
-let helo_cols;
-let mail_from_cols;
-let rcpt_to_cols;
-let data_cols;
-let total_cols;
-let cxn_cols;
-let txn_cols;
-
-const connect_plugins = ["geoip", "asn", "p0f", "dns-list", "access", "fcrdns"];
-const helo_plugins = ["helo.checks", "tls", "auth", "relay", "spf"];
-const mail_from_plugins = ["spf", "mail_from.is_resolvable", "known-senders"];
+'use strict'
+
+let ws
+let connect_cols
+let helo_cols
+let mail_from_cols
+let rcpt_to_cols
+let data_cols
+let total_cols
+let cxn_cols
+let txn_cols
+
+const connect_plugins = ['geoip', 'asn', 'p0f', 'dns-list', 'access', 'fcrdns']
+const helo_plugins = ['helo.checks', 'tls', 'auth', 'relay', 'spf']
+const mail_from_plugins = ['spf', 'mail_from.is_resolvable', 'known-senders']
const rcpt_to_plugins = [
- "queue/smtp_forward",
- "rcpt_to.in_host_list",
- "qmail-deliverable",
-];
+ 'queue/smtp_forward',
+ 'rcpt_to.in_host_list',
+ 'qmail-deliverable',
+]
const data_plugins = [
- "early_talker",
- "bounce",
- "headers",
- "karma",
- "spamassassin",
- "rspamd",
- "clamd",
- "uribl",
- "limit",
- "dkim",
- "attachment",
-];
+ 'early_talker',
+ 'bounce',
+ 'headers',
+ 'karma',
+ 'spamassassin',
+ 'rspamd',
+ 'clamd',
+ 'uribl',
+ 'limit',
+ 'dkim',
+ 'attachment',
+]
// 'seen' plugins are ones we've seen data reported for. When data from a new
// plugin arrives, it gets added to one of the sections above and the table is
// redrawn.
@@ -39,28 +39,28 @@ const seen_plugins = connect_plugins.concat(
mail_from_plugins,
rcpt_to_plugins,
data_plugins,
-);
+)
const ignore_seen = [
- "local_port",
- "remote_host",
- "helo",
- "mail_from",
- "rcpt_to",
- "queue",
-];
+ 'local_port',
+ 'remote_host',
+ 'helo',
+ 'mail_from',
+ 'rcpt_to',
+ 'queue',
+]
-let rows_showing = 0;
+let rows_showing = 0
function newRowConnectRow1(data, uuid, txnId) {
- const host = data.remote_host || { title: "", newval: "" };
- const port = data.local_port ? data.local_port.newval || "25" : "25";
+ const host = data.remote_host || { title: '', newval: '' }
+ const port = data.local_port ? data.local_port.newval || '25' : '25'
if (txnId > 1) {
return [
`
`,
`${txnId} | `,
` | `,
- ];
+ ]
}
return [
@@ -70,359 +70,359 @@ function newRowConnectRow1(data, uuid, txnId) {
`${host.newval} | `,
`${port} | `,
` | `,
- ];
+ ]
}
function newRowConnectRow2(data, uuid, txnId) {
- if (txnId > 1) return "";
+ if (txnId > 1) return ''
- const res = [];
+ const res = []
connect_plugins.forEach((plugin) => {
- let nv = shorten_pi(plugin);
- let newc = "";
- let tit = "";
+ let nv = shorten_pi(plugin)
+ let newc = ''
+ let tit = ''
if (data[plugin]) {
// not always updated
- if (data[plugin].classy) newc = data[plugin].classy;
- if (data[plugin].newval) nv = data[plugin].newval;
- if (data[plugin].title) tit = data[plugin].title;
+ if (data[plugin].classy) newc = data[plugin].classy
+ if (data[plugin].newval) nv = data[plugin].newval
+ if (data[plugin].title) tit = data[plugin].title
}
res.push(
`${nv} | `,
- );
- });
- return res.join("");
+ )
+ })
+ return res.join('')
}
function newRowHelo(data, uuid, txnId) {
- if (txnId > 1) return "";
+ if (txnId > 1) return ''
- const cols = [];
+ const cols = []
helo_plugins.forEach((plugin) => {
- cols.push(`${shorten_pi(plugin)} | `);
- });
- return cols.join("\n");
+ cols.push(`${shorten_pi(plugin)} | `)
+ })
+ return cols.join('\n')
}
function newRow(data, uuid) {
- const txnId = uuid.split("_").pop();
- const rowResult = newRowConnectRow1(data, uuid, txnId);
+ const txnId = uuid.split('_').pop()
+ const rowResult = newRowConnectRow1(data, uuid, txnId)
rowResult.push(
` | `,
` | `,
- );
+ )
data_plugins.slice(0, data_cols).forEach((plugin) => {
- rowResult.push(`${shorten_pi(plugin)} | `);
- });
+ rowResult.push(`${shorten_pi(plugin)} | `)
+ })
rowResult.push(
' |
',
``,
- );
+ )
- rowResult.push(newRowConnectRow2(data, uuid, txnId));
- rowResult.push(newRowHelo(data, uuid, txnId));
+ rowResult.push(newRowConnectRow2(data, uuid, txnId))
+ rowResult.push(newRowHelo(data, uuid, txnId))
// transaction data
mail_from_plugins.forEach((plugin) => {
- rowResult.push(`${shorten_pi(plugin)} | `);
- });
+ rowResult.push(`${shorten_pi(plugin)} | `)
+ })
rcpt_to_plugins.forEach((plugin) => {
- rowResult.push(`${shorten_pi(plugin)} | `);
- });
+ rowResult.push(`${shorten_pi(plugin)} | `)
+ })
data_plugins.slice(data_cols, data_plugins.length).forEach((plugin) => {
- rowResult.push(`${shorten_pi(plugin)} | `);
- });
- rowResult.push("
");
+ rowResult.push(`${shorten_pi(plugin)} | `)
+ })
+ rowResult.push('')
if (txnId > 1) {
- const prevUuid = `${uuid.split("_").slice(0, 2).join("_")}_${txnId - 1}`;
- const lastRow = $(`#connections > tbody > tr.${prevUuid}`).last();
+ const prevUuid = `${uuid.split('_').slice(0, 2).join('_')}_${txnId - 1}`
+ const lastRow = $(`#connections > tbody > tr.${prevUuid}`).last()
if (lastRow) {
lastRow
.hide()
- .after($(rowResult.join("\n")))
- .fadeIn("slow");
+ .after($(rowResult.join('\n')))
+ .fadeIn('slow')
}
} else {
- $(rowResult.join("\n"))
+ $(rowResult.join('\n'))
.hide()
- .prependTo("table#connections > tbody")
- .fadeIn(800);
+ .prependTo('table#connections > tbody')
+ .fadeIn(800)
}
- connect_plugins.concat(["remote_host", "local_port"]).forEach((plugin) => {
- $(`table#connections > tbody > tr.${uuid}> td.${css_safe(plugin)}`).tipsy();
- });
+ connect_plugins.concat(['remote_host', 'local_port']).forEach((plugin) => {
+ $(`table#connections > tbody > tr.${uuid}> td.${css_safe(plugin)}`).tipsy()
+ })
}
function updateRow(row_data, selector) {
// each bit of data in the WSS sent object represents a TD in the table
for (const td_name in row_data) {
- const td = row_data[td_name];
- if (typeof td !== "object") continue;
+ const td = row_data[td_name]
+ if (typeof td !== 'object') continue
- const td_name_css = css_safe(td_name);
- let td_sel = `${selector} > td.${td_name_css}`;
+ const td_name_css = css_safe(td_name)
+ let td_sel = `${selector} > td.${td_name_css}`
- if (td_name === "spf") {
- if (td.scope === "helo") {
- td_sel = `${td_sel}:first`;
+ if (td_name === 'spf') {
+ if (td.scope === 'helo') {
+ td_sel = `${td_sel}:first`
} else {
- td_sel = `${td_sel}:last`;
+ td_sel = `${td_sel}:last`
}
}
- update_seen(td_name);
+ update_seen(td_name)
// $('#messages').append(`, ${td_name}: `);
if (td.classy) {
$(td_sel)
- .attr("class", td_name_css) // reset class
+ .attr('class', td_name_css) // reset class
.addClass(td.classy)
- .tipsy();
+ .tipsy()
}
if (td.title) {
$(td_sel)
- .attr("title", `${$(td_sel).attr("title") || ""} ${td.title}`)
- .tipsy();
+ .attr('title', `${$(td_sel).attr('title') || ''} ${td.title}`)
+ .tipsy()
}
- if (td.newval) $(td_sel).html(td.newval).tipsy();
+ if (td.newval) $(td_sel).html(td.newval).tipsy()
}
- $(`${selector} > td`).tipsy();
+ $(`${selector} > td`).tipsy()
}
function httpGetJSON(theUrl) {
- let xmlHttp = null;
- xmlHttp = new XMLHttpRequest();
- xmlHttp.open("GET", theUrl, false);
- xmlHttp.send(null);
- return JSON.parse(xmlHttp.responseText);
+ let xmlHttp = null
+ xmlHttp = new XMLHttpRequest()
+ xmlHttp.open('GET', theUrl, false)
+ xmlHttp.send(null)
+ return JSON.parse(xmlHttp.responseText)
}
function ws_connect() {
if (!window.location.origin) {
- window.location.origin = `${window.location.protocol}//${window.location.hostname}`;
+ window.location.origin = `${window.location.protocol}//${window.location.hostname}`
if (window.location.port)
- window.location.origin += `:${window.location.port}`;
+ window.location.origin += `:${window.location.port}`
}
- const config = httpGetJSON(`${window.location.origin}/watch/wss_conf`);
+ const config = httpGetJSON(`${window.location.origin}/watch/wss_conf`)
if (!config.wss_url) {
- config.wss_url = `wss://${window.location.hostname}`;
- if (window.location.port) config.wss_url += `:${window.location.port}`;
+ config.wss_url = `wss://${window.location.hostname}`
+ if (window.location.port) config.wss_url += `:${window.location.port}`
}
- ws = new WebSocket(config.wss_url);
+ ws = new WebSocket(config.wss_url)
ws.onopen = function () {
// ws.send('something'); // send a message to the server
// $('#messages').append("connected ");
- $("span#connect_state").removeClass().addClass("green");
+ $('span#connect_state').removeClass().addClass('green')
if (config.sampling) {
- $("#messages").append("sampling");
+ $('#messages').append('sampling')
}
- };
+ }
ws.onerror = function (err) {
- $("#messages").append(`${err}, ${err.message}`);
- };
+ $('#messages').append(`${err}, ${err.message}`)
+ }
ws.onclose = function () {
// $('#messages').append('closed ');
- $("span#connect_state").removeClass().addClass("red");
- reconnect();
- };
+ $('span#connect_state').removeClass().addClass('red')
+ reconnect()
+ }
- let last_insert = 0;
+ let last_insert = 0
// let sampled_out = 0;
ws.onmessage = function (event, flags) {
// flags.binary will be set if a binary data is received
// flags.masked will be set if the data was masked
- const data = JSON.parse(event.data);
+ const data = JSON.parse(event.data)
if (data.msg) {
- $("#messages").append(`${data.msg} `);
- return;
+ $('#messages').append(`${data.msg} `)
+ return
}
if (data.watchers) {
- $("span#watchers").html(data.watchers);
- return;
+ $('span#watchers').html(data.watchers)
+ return
}
if (data.uuid === undefined) {
- $("#messages").append(" ERROR, no uuid: ");
- return;
+ $('#messages').append(' ERROR, no uuid: ')
+ return
}
- const css_valid_uuid = get_css_safe_uuid(data.uuid);
- const selector = `table#connections > tbody > tr.${css_valid_uuid}`;
+ const css_valid_uuid = get_css_safe_uuid(data.uuid)
+ const selector = `table#connections > tbody > tr.${css_valid_uuid}`
if ($(selector).length) {
// if the row exists
- updateRow(data, selector);
- return;
+ updateRow(data, selector)
+ return
}
// row doesn't exist (yet)
- let now;
+ let now
if (config.sampling) {
- now = new Date().getTime();
+ now = new Date().getTime()
if (now - last_insert < 1000) {
// sampled_out++;
// $('#messages').append("so:" + sampled_out);
- return;
+ return
}
}
// time to send a new row
- newRow(data, css_valid_uuid);
- prune_table();
- last_insert = now;
- };
+ newRow(data, css_valid_uuid)
+ prune_table()
+ last_insert = now
+ }
}
function reconnect() {
setTimeout(function () {
- ws_connect();
- }, 3 * 1000);
+ ws_connect()
+ }, 3 * 1000)
}
function update_seen(plugin) {
- if (seen_plugins.indexOf(plugin) !== -1) return;
- if (ignore_seen.indexOf(plugin) !== -1) return;
+ if (seen_plugins.indexOf(plugin) !== -1) return
+ if (ignore_seen.indexOf(plugin) !== -1) return
- seen_plugins.push(plugin);
+ seen_plugins.push(plugin)
- let bits = plugin.split(".");
+ let bits = plugin.split('.')
if (bits.length === 2) {
switch (
bits[0] // phase prefix
) {
- case "connect":
- connect_plugins.push(plugin);
- break;
- case "helo":
- helo_plugins.push(plugin);
- break;
- case "mail_from":
- mail_from_plugins.push(plugin);
- break;
- case "rcpt_to":
- rcpt_to_plugins.push(plugin);
- break;
- case "data":
- data_plugins.push(plugin);
- break;
+ case 'connect':
+ connect_plugins.push(plugin)
+ break
+ case 'helo':
+ helo_plugins.push(plugin)
+ break
+ case 'mail_from':
+ mail_from_plugins.push(plugin)
+ break
+ case 'rcpt_to':
+ rcpt_to_plugins.push(plugin)
+ break
+ case 'data':
+ data_plugins.push(plugin)
+ break
}
- $("#messages").append(`, refresh(${plugin}) `);
- return reset_table();
+ $('#messages').append(`, refresh(${plugin}) `)
+ return reset_table()
}
- bits = plugin.split("/");
+ bits = plugin.split('/')
if (bits.length === 2) {
switch (bits[0]) {
- case "auth": // gets coalesced under the 'HELO auth' box
- return;
+ case 'auth': // gets coalesced under the 'HELO auth' box
+ return
}
}
- $("#messages").append(`, uncategorized(${plugin}) `);
- data_plugins.push(plugin);
- return reset_table();
+ $('#messages').append(`, uncategorized(${plugin}) `)
+ data_plugins.push(plugin)
+ return reset_table()
}
function prune_table() {
- rows_showing++;
- const max = 200;
- if (rows_showing < max) return;
+ rows_showing++
+ const max = 200
+ if (rows_showing < max) return
$(`table#connections > tbody > tr:gt(${max * 3})`).fadeOut(2000, () => {
- $(this).remove();
- });
- rows_showing = $("table#connections > tbody > tr").length;
+ $(this).remove()
+ })
+ rows_showing = $('table#connections > tbody > tr').length
}
function reset_table() {
// after results for a 'new' plugin that we've never seen arrives, remove
// the old rows so the table formatting isn't b0rked
- $("table#connections > tbody > tr").fadeOut(5000, () => {
- $(this).remove();
- });
- countPhaseCols();
- display_th();
+ $('table#connections > tbody > tr').fadeOut(5000, () => {
+ $(this).remove()
+ })
+ countPhaseCols()
+ display_th()
}
function display_th() {
- $("table#connections > thead > tr#labels")
+ $('table#connections > thead > tr#labels')
.html(
[
- "ID | ",
+ 'ID | ',
`CONNECT | `,
`HELO | `,
`MAIL FROM | `,
`RCPT TO | `,
`DATA | `,
'QUEUE | ',
- ].join("\n\t"),
+ ].join('\n\t'),
)
- .tipsy();
- $("table#connections > thead > tr#labels > th").tipsy();
- $("table#connections > tfoot > tr#helptext").html(
+ .tipsy()
+ $('table#connections > thead > tr#labels > th').tipsy()
+ $('table#connections > tfoot > tr#helptext').html(
`For a good time: nc ${window.location.hostname} 587 | `,
- );
+ )
}
function countPhaseCols() {
- connect_cols = connect_plugins.length;
- helo_cols = helo_plugins.length;
- mail_from_cols = mail_from_plugins.length;
- rcpt_to_cols = rcpt_to_plugins.length;
- data_cols = Math.ceil(data_plugins.length / 2);
- cxn_cols = connect_cols + helo_cols;
- txn_cols = mail_from_cols + rcpt_to_cols + data_cols;
- total_cols = cxn_cols + txn_cols + 3;
+ connect_cols = connect_plugins.length
+ helo_cols = helo_plugins.length
+ mail_from_cols = mail_from_plugins.length
+ rcpt_to_cols = rcpt_to_plugins.length
+ data_cols = Math.ceil(data_plugins.length / 2)
+ cxn_cols = connect_cols + helo_cols
+ txn_cols = mail_from_cols + rcpt_to_cols + data_cols
+ total_cols = cxn_cols + txn_cols + 3
}
function css_safe(str) {
- return str.replace(/([^0-9a-zA-Z\-_])/g, "_");
+ return str.replace(/([^0-9a-zA-Z\-_])/g, '_')
// http://www.w3.org/TR/CSS21/syndata.html#characters
// identifiers can contain only [a-zA-Z0-9] plus - and _
}
function shorten_pi(name) {
const trims = {
- spamassassin: "spam",
- early_talker: "early",
- "rcpt_to.qmail_deliverable": "qmd",
- "qmail-deliverable": "qmd",
- "rcpt_to.in_host_list": "host_list",
- "mail_from.is_resolvable": "dns",
- "known-senders": "known",
- "queue/smtp_forward": "forward",
- smtp_forward: "forward",
- attachment: "attach",
- };
-
- if (trims[name]) return trims[name];
-
- const parts = name.split(".");
+ spamassassin: 'spam',
+ early_talker: 'early',
+ 'rcpt_to.qmail_deliverable': 'qmd',
+ 'qmail-deliverable': 'qmd',
+ 'rcpt_to.in_host_list': 'host_list',
+ 'mail_from.is_resolvable': 'dns',
+ 'known-senders': 'known',
+ 'queue/smtp_forward': 'forward',
+ smtp_forward: 'forward',
+ attachment: 'attach',
+ }
+
+ if (trims[name]) return trims[name]
+
+ const parts = name.split('.')
switch (parts[0]) {
- case "helo":
- case "connect":
- case "mail_from":
- case "rcpt_to":
- case "data":
- case "queue":
- return parts.slice(1).join(".");
+ case 'helo':
+ case 'connect':
+ case 'mail_from':
+ case 'rcpt_to':
+ case 'data':
+ case 'queue':
+ return parts.slice(1).join('.')
}
- return name;
+ return name
}
function get_css_safe_uuid(uuid) {
@@ -431,12 +431,12 @@ function get_css_safe_uuid(uuid) {
// CAF2B05E-5382-4E65-A51E-7DEE6EF31F80.1 // bits.length=2
// CAF2B05E-5382-4E65-A51E-7DEE6EF31F80.2
- const bits = uuid.split(".");
+ const bits = uuid.split('.')
if (bits.length === 1) {
- bits[1] = 1;
+ bits[1] = 1
}
- return `aa_${bits[0].replace(/[_-]/g, "")}_${bits[1]}`;
+ return `aa_${bits[0].replace(/[_-]/g, '')}_${bits[1]}`
}
-countPhaseCols();
+countPhaseCols()
diff --git a/html/index.html b/html/index.html
index 44d72a7..9ad131a 100644
--- a/html/index.html
+++ b/html/index.html
@@ -47,14 +47,14 @@
@@ -132,29 +132,29 @@