Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add log viewer #6

Open
wants to merge 1 commit into
base: ws_nodejs
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions belaUI.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ wss.on('connection', function connection(conn) {
console.log(`Error parsing client message: ${err.message}`);
}
});

conn.on('close', function close() {
removeLogListener(conn);
});
});


Expand Down Expand Up @@ -1912,6 +1916,134 @@ function tryAuth(conn, msg) {
}


/* Log viewer */
let logListeners = [];
const LOGSECRETREGEX = /<(\w+)>/gm;
const LOGSECRET = "****";

function spawnLogStream() {
const args = ['-o', 'json', '--all', '-fu', 'belaUI'];
const journalctl = spawn('journalctl', args);

journalctl.stdout.on('data', (chunk) => {
if (logListeners.length == 0) {
journalctl.stdin.pause();
journalctl.kill();

return;
}

processLogStream(chunk.toString(), (msg) => {
let { MESSAGE, _COMM, __REALTIME_TIMESTAMP } = msg;

for (c of logListeners) {
let json = {
comm: _COMM,
timestamp: +__REALTIME_TIMESTAMP,
message: hideSensitiveInfo(MESSAGE.replace(LOGSECRETREGEX, LOGSECRET)),
};

if (json.message.includes("keepalive")) continue;

if (!c.logHideSensitive) {
const matches = [...MESSAGE.matchAll(LOGSECRETREGEX)];

if (matches.length > 0) {
matches.map((m) => (MESSAGE = MESSAGE.replace(m[0], m[1])));
}

json.message = MESSAGE;
}

c.send(buildMsg('logStream', json))
};
});
});
}

function hideSensitiveInfo(sentence) {
sentence = hideRegex(sentence, /"auth":{"\w+":"(.*?)"/gm)
sentence = hideWord(sentence, config.srt_streamid)
sentence = hideWord(sentence, config.srtla_addr)
sentence = hideWord(sentence, config.password_hash)

return sentence;
}

function hideWord(s, word) {
if (s.includes(word)) {
return s.replaceAll(word, LOGSECRET);
}

return s;
}

function hideRegex(s, regex) {
const matches = [...s.matchAll(regex)];

if (matches.length > 0) {
matches.map((m) => (s = s.replace(m[1], LOGSECRET)));
}

return s;
}

let partialJson = '';
let prevPartial = false;
function processLogStream(msg, cb) {
if (prevPartial) {
msg = `${partialJson}${msg}`;
prevPartial = false;
partialJson = '';
}

for (const chunk of msg.trim().split('\n')) {
try {
cb(JSON.parse(chunk));
} catch (error) {
partialJson += chunk;
prevPartial = true;
}
}
}

function subscribeLogStream(conn, { show, hideSensitive }) {
if (!show) {
removeLogListener(conn);
return;
}

if (logListeners.length == 0) spawnLogStream();

if (!logListeners.includes(conn)) {
conn.logHideSensitive = hideSensitive;
logListeners.push(conn);
}
}

function removeLogListener(conn) {
logListeners = logListeners.filter((l) => l != conn);
}

function downloadLog(conn, { hideSensitive }) {
const args = ['-u', 'belaUI', '-b'];
const journalctl = spawn('journalctl', args);

journalctl.stdout.on('data', (chunk) => {
if (hideSensitive) {
const chunkString = hideSensitiveInfo(chunk.toString().replace(LOGSECRETREGEX, LOGSECRET));
chunk = Buffer.from(chunkString);
}

conn.send(buildMsg('logDownload', { chunk }));
});

journalctl.on('close', () => {
conn.send(buildMsg('logDownload', { completed: true }));
});
}


function handleMessage(conn, msg, isRemote = false) {
console.log(msg);

Expand Down Expand Up @@ -1974,6 +2106,12 @@ function handleMessage(conn, msg, isRemote = false) {
delete conn.isAuthed;
delete conn.authToken;

break;
case 'subscribeLogStream':
subscribeLogStream(conn, msg[type]);
break;
case 'downloadLog':
downloadLog(conn, msg[type]);
break;
}
}
Expand Down
34 changes: 34 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,14 @@ <h5 class="modal-title">Connect to a new network</h5>

<div id="wifi"></div>

<div id="logTable" class="card mb-4 d-none">
<div id="logs" class="card-body" style="height: 530px; overflow: auto">
</div>
<button type="button" class="btn btn-block btn-danger btn-netact logStream">
Hide current log
</button>
</div>

<div id="settings">
<div class="card mb-2">
<div class="card-header bg-success text-center" type="button"
Expand Down Expand Up @@ -337,6 +345,32 @@ <h5 class="modal-title">Connect to a new network</h5>
</div> <!-- .collapse -->
</div> <!-- .card -->

<div class="card mb-2">
<div class="card-header bg-success text-center" type="button" data-toggle="collapse" data-target="#collapseFour">
<button class="btn btn-link text-white" type="button" data-toggle="collapse" data-target="#collapseFour"
aria-expanded="false" aria-controls="collapseFour">
Debug
</button>
</div> <!-- card-header -->

<div class="collapse" id="collapseFour">
<div class="card-body">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" value="" id="logHideSensitive" checked>
<label class="form-check-label" for="logHideSensitive">
Hide sensitive data
</label>
</div>
<button type="button" id="logDownload" class="btn btn-block btn-warning btn-netact">
Download log
</button>
<button type="button" class="btn btn-block btn-warning btn-netact logStream">
Show current log
</button>
</div> <!-- .card-body -->
</div> <!-- .collapse -->
</div> <!-- .card -->

</div> <!-- #settings -->
</div> <!-- #main -->
</div> <!-- .container -->
Expand Down
91 changes: 91 additions & 0 deletions public/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ function tryConnect() {
hideError();
tryTokenAuth();
updateNetact(true);

if (logStream) sendLogStreamRequest();
});
}

Expand Down Expand Up @@ -768,6 +770,57 @@ function handleWifiResult(msg) {
}


/* Logs */
const MAX_LOGS = 100;
const logs = document.getElementById('logs');

function handleLogstream(log) {
const { comm, timestamp, message } = log;
const date = new Date(timestamp / 1000);

const html = `<div class="d-flex flex-column card mb-3 p-3">
<div class="d-flex justify-content-between mb-1 font-weight-bold">
<p class="mb-0">${comm}</p>
<p class="mb-0">${date.toLocaleString()}</p>
</div>
<div>
<p class="mb-0"">${message}</p>
</div>
</div>`;

if (logs.childElementCount == MAX_LOGS) logs.lastElementChild.remove();

logs.insertAdjacentHTML('afterbegin', html);
}

let chunkBlobs = [];
function handleLogDownload(log) {
if (log.completed) {
console.log('Download completed');
finalBlob = new Blob(chunkBlobs, { type: 'text/plain' });
saveToFile(finalBlob, `BELABOX_${new Date().toJSON()}.txt`);
chunkBlobs = [];

return;
}

console.log('Downloading log');

const chunk = new Uint8Array(log.chunk.data);
const chunkBlob = new Blob([chunk], { type: 'text/plain' });
chunkBlobs.push(chunkBlob);
}

function saveToFile(blob, name) {
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = name;
a.click();
a.remove();
URL.revokeObjectURL(a.href);
}


/* Error messages */
function showError(message) {
$("#errorMsg>span").text(message);
Expand Down Expand Up @@ -893,6 +946,12 @@ function handleMessage(msg) {
case 'wifi':
handleWifiResult(msg[type]);
break;
case 'logStream':
handleLogstream(msg[type]);
break;
case 'logDownload':
handleLogDownload(msg[type]);
break;
case 'error':
showError(msg[type].msg);
break;
Expand Down Expand Up @@ -1201,3 +1260,35 @@ $('input.click-copy').click(function(ev) {
}, 3000);
}
});

let logStream = false;
const logStreamEle = document.querySelectorAll('.logStream');
const hideSensitive = document.getElementById('logHideSensitive');

logStreamEle.forEach(l => l.addEventListener('click', toggleLog));

function toggleLog() {
logStream = !logStream;
const hideShow = logStream ? 'Hide' : 'Show';
logs.innerHTML = '';

sendLogStreamRequest();

const logTable = document.getElementById('logTable');
logTable.classList.toggle('d-none');

logStreamEle.forEach((i) => (i.innerText = `${hideShow} current log`));
}

function sendLogStreamRequest() {
ws.send(JSON.stringify({
subscribeLogStream: {
show: logStream,
hideSensitive: hideSensitive.checked
}
}));
}

document.getElementById('logDownload').addEventListener('click', () => {
ws.send(JSON.stringify({ downloadLog: { hideSensitive: hideSensitive.checked } }));
});