510 lines
22 KiB
Plaintext
510 lines
22 KiB
Plaintext
<%- await include('parts/header.ejs', locals) %>
|
|
|
|
|
|
<style>
|
|
.pre-log-content {
|
|
height: calc(100vh - 270px); /* -251px */
|
|
margin-bottom: 0;
|
|
font-size: 100%;
|
|
/* word-break: break-all; */
|
|
}
|
|
|
|
.nui-height {
|
|
height: calc(100vh - 170px);
|
|
}
|
|
|
|
.card {
|
|
height: 100%;
|
|
margin-bottom: 0 !important;
|
|
}
|
|
|
|
.e-src{
|
|
color: var(--dark);
|
|
font-weight: 900 !important;
|
|
padding: .15rem;
|
|
margin-left: .25rem;
|
|
margin-right: .25rem;
|
|
}
|
|
.e-src-player{
|
|
color: var(--primary);
|
|
font-weight: 900 !important;
|
|
cursor: pointer;
|
|
}
|
|
.e-src-player:hover{
|
|
/* color: var(--warning); */
|
|
color: var(--light);
|
|
background-color: var(--dark);
|
|
}
|
|
|
|
.filter-controls-container {
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.filter-show-toggle {
|
|
display: none;
|
|
}
|
|
|
|
@media only screen and (max-width: 1200px) {
|
|
.card-containers {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.filter-show-toggle {
|
|
display: flex;
|
|
}
|
|
|
|
.section-title {
|
|
display: flex;
|
|
flex-basis: 0 ;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
}
|
|
|
|
@media (min-width: 1250px) and (max-width: 1450px) {
|
|
.override-bootstrap {
|
|
flex: 0 0 auto !important;
|
|
}
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
<div class="row">
|
|
<div class="col-lg-3 col-md-12 card-containers" style="min-width: 235px;">
|
|
<div class="card" >
|
|
<div class="card-body text-center">
|
|
<div id="cardstatus">
|
|
<h4>Mode: <span class="text-success" id="modeLabel">LIVE</span></h4>
|
|
<p>
|
|
<strong>From:</strong> <span id="histLogStart">--</span> <br>
|
|
<strong>To:</strong> <span id="histLogEnd">--</span> <br>
|
|
</p>
|
|
<button type="button" class="btn btn-outline-dark btn-sm mb-2" id="viewOlderBtn">
|
|
< View Older
|
|
</button>
|
|
<button type="button" class="btn btn-outline-dark btn-sm mb-2" id="viewNewerBtn">
|
|
View Newer >
|
|
</button>
|
|
<br>
|
|
<button type="button" id="clearConsole" class="btn btn-outline-dark btn-sm mb-2">
|
|
Clear Console
|
|
</button>
|
|
<!-- TODO: add modal -->
|
|
<button type="button" id="showLogsModalBtn" class="btn btn-outline-dark btn-sm mb-2 d-none">
|
|
Download Log
|
|
</button>
|
|
</div>
|
|
|
|
<hr class="border-primary">
|
|
<div class='section-title'>
|
|
<button
|
|
style='margin-left: 10px;'
|
|
class='btn btn-outline-info filter-show-toggle rounded'
|
|
data-toggle='collapse'
|
|
href="#filter-control-container"
|
|
role='button'
|
|
aria-expanded='false'
|
|
aria-controls='collapseControls'>
|
|
Show Log Filters
|
|
</button>
|
|
</div>
|
|
<div class='filter-controls-container' id='filter-control-container'>
|
|
<h4>Logger Filters</h4>
|
|
<div class="form-group row mb-1">
|
|
<label class="col-sm-8 col-form-label">Player Joins</label>
|
|
<div class="col-sm-4">
|
|
<label class="c-switch c-switch-sm c-switch-label c-switch-pill c-switch-success fix-pill-form">
|
|
<input class="c-switch-input e-filter-sw" type="checkbox" checked data-e-type="PlayerJoin">
|
|
<span class="c-switch-slider" data-checked="On" data-unchecked="Off"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="form-group row mb-1">
|
|
<label class="col-sm-8 col-form-label">Player Leaves</label>
|
|
<div class="col-sm-4">
|
|
<label class="c-switch c-switch-sm c-switch-label c-switch-pill c-switch-success fix-pill-form">
|
|
<input class="c-switch-input e-filter-sw" type="checkbox" checked data-e-type="playerDropped">
|
|
<span class="c-switch-slider" data-checked="On" data-unchecked="Off"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="form-group row mb-1">
|
|
<label class="col-sm-8 col-form-label">Chat Messages</label>
|
|
<div class="col-sm-4">
|
|
<label class="c-switch c-switch-sm c-switch-label c-switch-pill c-switch-success fix-pill-form">
|
|
<input class="c-switch-input e-filter-sw" type="checkbox" checked data-e-type="ChatMessage">
|
|
<span class="c-switch-slider" data-checked="On" data-unchecked="Off"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="form-group row mb-1">
|
|
<label class="col-sm-8 col-form-label">Player Death</label>
|
|
<div class="col-sm-4">
|
|
<label class="c-switch c-switch-sm c-switch-label c-switch-pill c-switch-success fix-pill-form">
|
|
<input class="c-switch-input e-filter-sw" type="checkbox" checked data-e-type="DeathNotice">
|
|
<span class="c-switch-slider" data-checked="On" data-unchecked="Off"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="form-group row mb-1">
|
|
<label class="col-sm-8 col-form-label">Menu Actions</label>
|
|
<div class="col-sm-4">
|
|
<label class="c-switch c-switch-sm c-switch-label c-switch-pill c-switch-success fix-pill-form">
|
|
<input class="c-switch-input e-filter-sw" type="checkbox" checked data-e-type="MenuEvent">
|
|
<span class="c-switch-slider" data-checked="On" data-unchecked="Off"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="form-group row mb-1">
|
|
<label class="col-sm-8 col-form-label">Explosions</label>
|
|
<div class="col-sm-4">
|
|
<label class="c-switch c-switch-sm c-switch-label c-switch-pill c-switch-success fix-pill-form">
|
|
<input class="c-switch-input e-filter-sw" type="checkbox" checked data-e-type="explosionEvent">
|
|
<span class="c-switch-slider" data-checked="On" data-unchecked="Off"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="form-group row mb-1">
|
|
<label class="col-sm-8 col-form-label">Commands</label>
|
|
<div class="col-sm-4">
|
|
<label class="c-switch c-switch-sm c-switch-label c-switch-pill c-switch-success fix-pill-form">
|
|
<input class="c-switch-input e-filter-sw" type="checkbox" checked data-e-type="CommandExecuted">
|
|
<span class="c-switch-slider" data-checked="On" data-unchecked="Off"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="form-group row mb-1">
|
|
<label class="col-sm-8 col-form-label">System Events</label>
|
|
<div class="col-sm-4">
|
|
<label class="c-switch c-switch-sm c-switch-label c-switch-pill c-switch-success fix-pill-form">
|
|
<input class="c-switch-input e-filter-sw" type="checkbox" checked data-e-type="System">
|
|
<span class="c-switch-slider" data-checked="On" data-unchecked="Off"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- <div class="form-group row mb-1">
|
|
<label class="col-sm-8 col-form-label">Other Events</label>
|
|
<div class="col-sm-4">
|
|
<label class="c-switch c-switch-sm c-switch-label c-switch-pill c-switch-success fix-pill-form">
|
|
<input class="c-switch-input e-filter-sw" type="checkbox" checked data-e-type="other">
|
|
<span class="c-switch-slider" data-checked="On" data-unchecked="Off"></span>
|
|
</label>
|
|
</div>
|
|
</div> -->
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-9 col-md-12 card-containers override-bootstrap">
|
|
<div class="card">
|
|
<div class="card-body p-3" style="position: relative">
|
|
<pre id="logContainer" class="thin-scroll pre-log-content <%= isWebInterface ? '' : 'nui-height' %>"></pre>
|
|
<div id="autoScrollDiv">
|
|
<a id="autoScrollBtn" class="d-none" href="#"><span></span><span></span><span></span></a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<%- await include('parts/footer.ejs', locals) %>
|
|
|
|
|
|
<script>
|
|
(function () {
|
|
//============================================== Moved from main.js
|
|
function showPlayerByMutexNetid(mutexNetid) {
|
|
const [mutex, netid] = mutexNetid.split(/[_#]/, 2);
|
|
return window.parent.postMessage({ type: 'openPlayerModal', ref: { mutex, netid } });
|
|
}
|
|
|
|
const getSocket = (rooms) => {
|
|
const socketOpts = {
|
|
transports: ['polling'],
|
|
upgrade: false,
|
|
query: { rooms }
|
|
};
|
|
|
|
const socket = isWebInterface
|
|
? io({ ...socketOpts, path: '/socket.io' })
|
|
: io('monitor', { ...socketOpts, path: '/WebPipe/socket.io' });
|
|
|
|
socket.on('logout', () => {
|
|
console.log('Received logout command from websocket.');
|
|
window.parent.postMessage({ type: 'logoutNotice' });
|
|
});
|
|
|
|
return socket;
|
|
}
|
|
|
|
//============================================== Preparing variables
|
|
const autoScrollBtn = document.getElementById("autoScrollBtn");
|
|
const logContainer = document.getElementById("logContainer");
|
|
const modeLabel = document.getElementById("modeLabel");
|
|
const histLogStart = document.getElementById("histLogStart");
|
|
const histLogEnd = document.getElementById("histLogEnd");
|
|
const viewOlderBtn = document.getElementById("viewOlderBtn");
|
|
const viewNewerBtn = document.getElementById("viewNewerBtn");
|
|
const MAX_HISTORY_SIZE = 500;
|
|
const frameTimeOptions = {weekday: 'long', hour: '2-digit', minute: '2-digit'};
|
|
const eventTimeOptions = {hour: '2-digit', minute: '2-digit', second: '2-digit'};
|
|
let logStartTs, logEndTs;
|
|
let eventFilters = {};
|
|
let autoScroll = true;
|
|
const pageSocket = getSocket('serverlog');
|
|
const logContainerSpinner = `<div style="height: 90%;" class="d-flex"><div style="margin: auto;">${SPINNER_HTML}</div></div>`;
|
|
|
|
|
|
//============================================== Responsivity stuff
|
|
const mql = window.matchMedia('(max-width: 992px)');
|
|
const targetEl = document.getElementById('filter-control-container');
|
|
if (mql.matches) {
|
|
targetEl.classList.add('collapse');
|
|
}
|
|
|
|
mql.addEventListener("change", (e) => {
|
|
if (e.matches && !targetEl.classList.contains('collapse')) {
|
|
return targetEl.classList.add('collapse');
|
|
}
|
|
|
|
if (targetEl.classList.contains('collapse')) targetEl.classList.remove('collapse');
|
|
})
|
|
|
|
//============================================== Socket Stuff
|
|
pageSocket.on('error', (error) => {
|
|
console.log('Page Socket.IO', error)
|
|
});
|
|
pageSocket.on('connect', () => {
|
|
console.log("Page Socket.IO Connected.");
|
|
logContainer.innerHTML = '';
|
|
});
|
|
pageSocket.on('disconnect', (message) => {
|
|
console.log("Page Socket.IO Disonnected:", message);
|
|
});
|
|
pageSocket.on('logData', processLog);
|
|
|
|
//============================================== Mode selection
|
|
function goLive() {
|
|
logContainer.innerHTML = logContainerSpinner;
|
|
modeLabel.classList.remove('text-warning');
|
|
modeLabel.classList.add('text-success');
|
|
modeLabel.textContent = 'LIVE';
|
|
viewOlderBtn.disabled = false;
|
|
viewNewerBtn.disabled = true;
|
|
if (!pageSocket.connected) pageSocket.connect();
|
|
}
|
|
|
|
function goHistory(direction) {
|
|
let ref;
|
|
if(direction === 'older'){
|
|
ref = logStartTs;
|
|
}else if(direction === 'newer'){
|
|
ref = logEndTs;
|
|
}else{
|
|
throw new Error(`unknown direction`);
|
|
}
|
|
pageSocket.disconnect();
|
|
logContainer.innerHTML = logContainerSpinner;
|
|
modeLabel.classList.add('text-warning');
|
|
modeLabel.classList.remove('text-success');
|
|
modeLabel.textContent = 'LOG';
|
|
viewOlderBtn.disabled = false;
|
|
viewNewerBtn.disabled = false;
|
|
|
|
//Get log from API
|
|
txAdminAPI({
|
|
type: 'GET',
|
|
url: `/serverLog/partial?dir=${direction}&ref=${ref}`,
|
|
timeout: REQ_TIMEOUT_MEDIUM,
|
|
success: function (data) {
|
|
logContainer.innerHTML = '';
|
|
if(!Array.isArray(data.log)){
|
|
logContainer.innerHTML = 'Failed to load log. Please refresh the page and try again.';
|
|
return;
|
|
}
|
|
//FIXME: reorganize this mess
|
|
if(data.boundry){
|
|
if(direction === 'older'){
|
|
if(!data.log.length){
|
|
logContainer.innerHTML = 'No more log entries to show.';
|
|
}else{
|
|
processLog(data.log);
|
|
}
|
|
viewOlderBtn.disabled = true;
|
|
}else{
|
|
goLive();
|
|
}
|
|
return;
|
|
}
|
|
processLog(data.log);
|
|
|
|
// TODO: da até pra depois do processLog() adicionar um <a> pra ir older ou newer
|
|
},
|
|
error: function (xmlhttprequest, textstatus, message) {
|
|
logContainer.innerHTML = 'Failed to load log. Please refresh the page and try again.';
|
|
},
|
|
});
|
|
}
|
|
|
|
// TODO: check if search string then do goLive() or goHistory()
|
|
goLive();
|
|
|
|
|
|
//============================================== Handling filters
|
|
if(typeof window.localStorage.eventFilters !== 'undefined'){
|
|
try {
|
|
const fromStorage = JSON.parse(window.localStorage.eventFilters);
|
|
console.log('Filters from storage:', fromStorage);
|
|
if(typeof fromStorage === 'object' && fromStorage !== null) {
|
|
eventFilters = fromStorage;
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to process window.localStorage.eventFilters');
|
|
}
|
|
}
|
|
|
|
const allSwitches = document.querySelectorAll('.e-filter-sw');
|
|
allSwitches.forEach((sw) => {
|
|
sw.addEventListener('change', eventFiltersChanged);
|
|
if(typeof eventFilters[sw.dataset.eType] === 'undefined'){
|
|
eventFilters[sw.dataset.eType] = true;
|
|
}
|
|
sw.checked = eventFilters[sw.dataset.eType];
|
|
});
|
|
window.localStorage.eventFilters = JSON.stringify(eventFilters);
|
|
|
|
const getShowTypes = () => Object.keys(eventFilters)
|
|
.filter(fn => eventFilters[fn])
|
|
.flatMap(fn => {
|
|
if (fn === 'PlayerJoin') return ['playerJoining', 'playerJoinDenied'];
|
|
if(fn === 'System') return ['LoggerStarted', 'DebugMessage'];
|
|
return fn;
|
|
});
|
|
|
|
function eventFiltersChanged(caller){
|
|
eventFilters[caller.target.dataset.eType] = caller.target.checked;
|
|
window.localStorage.eventFilters = JSON.stringify(eventFilters);
|
|
const showTypes = getShowTypes();
|
|
console.log('showTypes:', showTypes);
|
|
|
|
logContainer.childNodes.forEach(line => {
|
|
if(showTypes.includes(line.dataset.eType)){
|
|
line.classList.remove('d-none');
|
|
}else{
|
|
line.classList.add('d-none');
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
//============================================== AutoScroll Things
|
|
const scrollBottom = () => {
|
|
if (autoScroll) logContainer.scrollTop = logContainer.scrollHeight;
|
|
}
|
|
const autoscrollToggle = (status) => {
|
|
autoScroll = status
|
|
if(autoScroll){
|
|
autoScrollBtn.classList.add('d-none');
|
|
}else{
|
|
autoScrollBtn.classList.remove('d-none');
|
|
}
|
|
scrollBottom();
|
|
}
|
|
logContainer.addEventListener('scroll',function(){
|
|
const scrollTop = logContainer.scrollTop;
|
|
const scrollHeight = logContainer.scrollHeight;
|
|
const offsetHeight = logContainer.offsetHeight;
|
|
const contentHeight = scrollHeight - offsetHeight;
|
|
if (scrollTop < contentHeight) {
|
|
autoscrollToggle(false);
|
|
} else if(scrollTop === contentHeight){
|
|
autoscrollToggle(true);
|
|
}
|
|
}
|
|
)
|
|
autoScrollBtn.addEventListener("click", (event) => {
|
|
event.preventDefault();
|
|
autoscrollToggle(true);
|
|
});
|
|
|
|
|
|
//============================================== Buttons
|
|
document.getElementById("viewOlderBtn").addEventListener("click", function () {
|
|
goHistory('older');
|
|
});
|
|
document.getElementById("viewNewerBtn").addEventListener("click", function () {
|
|
goHistory('newer');
|
|
});
|
|
document.getElementById("clearConsole").addEventListener("click", function () {
|
|
logContainer.innerHTML = "";
|
|
});
|
|
document.getElementById("showLogsModalBtn").addEventListener("click", function () {
|
|
//TODO: add modal stuff
|
|
});
|
|
|
|
|
|
//============================================== Log processor
|
|
function processLog(events) {
|
|
console.log(`Events: ${events.length}`);
|
|
const showTypes = getShowTypes();
|
|
//For every new entry
|
|
for (let i = 0; i < events.length; i++) {
|
|
const event = events[i];
|
|
logEndTs = event.ts;
|
|
|
|
//Line
|
|
const lineElement = document.createElement('div');
|
|
lineElement.dataset.eTs = event.ts;
|
|
lineElement.dataset.eType = event.type;
|
|
if(!showTypes.includes(event.type)){
|
|
lineElement.classList.add('d-none');
|
|
}
|
|
|
|
//Time
|
|
const localeTime = new Date(event.ts).toLocaleTimeString(window.navigator.language, eventTimeOptions);
|
|
const timeNode = document.createElement('span');
|
|
timeNode.textContent = `[${localeTime}]`;
|
|
timeNode.classList.add('text-muted');
|
|
lineElement.appendChild(timeNode);
|
|
|
|
//Source
|
|
const sourceNode = document.createElement('strong');
|
|
sourceNode.classList.add('e-src');
|
|
if(event.src.id){
|
|
const [mutex, netid] = event.src.id.split('#', 2);
|
|
sourceNode.classList.add('e-src-player');
|
|
sourceNode.addEventListener('click', ()=>{ showPlayerByMutexNetid(`${mutex}_${netid}`) });
|
|
sourceNode.textContent = `[${netid}] ${event.src.name}`;
|
|
}else{
|
|
sourceNode.textContent = event.src.name;
|
|
}
|
|
lineElement.appendChild(sourceNode);
|
|
|
|
//Message
|
|
const msgNode = document.createTextNode(event.msg);
|
|
lineElement.appendChild(msgNode);
|
|
|
|
//Appending & capping log
|
|
logContainer.appendChild(lineElement)
|
|
if(logContainer.childNodes.length > MAX_HISTORY_SIZE){
|
|
logContainer.removeChild(logContainer.childNodes[0])
|
|
}
|
|
}
|
|
|
|
//Process times
|
|
logStartTs = parseInt(logContainer.childNodes[0].dataset.eTs);
|
|
const logStartDate = new Date(logStartTs);
|
|
const logEndDate = new Date(logEndTs);
|
|
histLogStart.textContent = logStartDate.toLocaleString(window.navigator.language, frameTimeOptions);
|
|
histLogEnd.textContent = logEndDate.toLocaleString(window.navigator.language, frameTimeOptions);
|
|
|
|
//AutoScroll
|
|
scrollBottom();
|
|
}
|
|
})();
|
|
</script>
|