mirror of
https://github.com/boku7/Loki
synced 2026-06-06 15:24:27 +00:00
425 lines
12 KiB
HTML
425 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Loki Dashboard</title>
|
|
<style>
|
|
|
|
body {
|
|
background-image: url('./assets/images/background.png');
|
|
background-size: contain; /* Fit the image within the element */
|
|
background-repeat: no-repeat; /* Prevent repeating */
|
|
background-position: center; /* Center the image */
|
|
height: 100vh; /* Full viewport height */
|
|
background-color: #000000;
|
|
color: #e0e0e0;
|
|
font-family: 'Arial', sans-serif;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: flex-start;
|
|
height: 100vh;
|
|
margin: 0;
|
|
overflow: auto; /* Allow both vertical and horizontal scrolling */
|
|
}
|
|
|
|
/* Dark mode scrollbar for the body */
|
|
body::-webkit-scrollbar {
|
|
width: 12px;
|
|
}
|
|
|
|
body::-webkit-scrollbar-track {
|
|
background: #0a0a0a;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
body::-webkit-scrollbar-thumb {
|
|
background: #333333;
|
|
border-radius: 6px;
|
|
border: 2px solid #0a0a0a;
|
|
}
|
|
|
|
body::-webkit-scrollbar-thumb:hover {
|
|
background: #555555;
|
|
}
|
|
|
|
table.vampire-matrix {
|
|
width: 100%; /* Make the table consume the entire window width */
|
|
min-width: 1200px; /* Minimum width to ensure all columns are readable */
|
|
border-collapse: collapse;
|
|
background-color: #1c1c1c;
|
|
box-shadow: 0 0 10px rgba(131, 131, 131, 0.5);
|
|
max-height: none; /* Allow table to grow beyond viewport */
|
|
}
|
|
|
|
table.vampire-matrix th,
|
|
table.vampire-matrix td {
|
|
border: 1px solid #333;
|
|
padding: 10px 15px;
|
|
text-align: left;
|
|
vertical-align: middle;
|
|
word-wrap: break-word; /* Ensure content wraps within the cell */
|
|
}
|
|
|
|
table.vampire-matrix thead th {
|
|
background-color: #333;
|
|
color: #fff;
|
|
font-size: 1.1em;
|
|
}
|
|
|
|
table.vampire-matrix tbody tr {
|
|
transition: background-color 0.2s ease, border-left-color 0.2s ease;
|
|
cursor: pointer;
|
|
border-left: 3px solid transparent;
|
|
}
|
|
|
|
table.vampire-matrix tbody tr:hover,
|
|
table.vampire-matrix tbody tr.row-hovered {
|
|
background-color: #2a2a2a !important;
|
|
border-left-color: #06883e !important;
|
|
}
|
|
|
|
table.vampire-matrix tbody tr:active {
|
|
background-color: #333333 !important;
|
|
}
|
|
|
|
/* Hide the Container column (2nd column) */
|
|
table.vampire-matrix th:nth-child(2),
|
|
table.vampire-matrix td:nth-child(2) {
|
|
display: none;
|
|
}
|
|
|
|
table.vampire-matrix tbody tr:nth-child(even) {
|
|
background-color: #232323;
|
|
}
|
|
|
|
table.vampire-matrix tbody tr:nth-child(odd) {
|
|
background-color: #1a1a1a;
|
|
}
|
|
|
|
table.vampire-matrix td {
|
|
color: #fff; /* Change text color to white */
|
|
}
|
|
|
|
table.vampire-matrix th {
|
|
border-bottom: 2px solid #444;
|
|
}
|
|
|
|
table.vampire-matrix tbody td:first-child {
|
|
border-left: 2px solid #444;
|
|
}
|
|
|
|
table.vampire-matrix tbody td:last-child {
|
|
border-right: 2px solid #444;
|
|
}
|
|
|
|
tr {
|
|
height: 20px;
|
|
max-height: 20px; /* Set your desired max-height here */
|
|
overflow: hidden;
|
|
}
|
|
tr > td {
|
|
max-height: inherit; /* Apply max height to each cell */
|
|
overflow: hidden; /* Ensure content overflow is hidden in each cell */
|
|
}
|
|
|
|
/* Dark mode context menu styling */
|
|
.context-menu {
|
|
position: fixed;
|
|
background-color: #2d2d2d;
|
|
border: 1px solid #555;
|
|
border-radius: 4px;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6);
|
|
padding: 4px 0;
|
|
min-width: 150px;
|
|
z-index: 9999;
|
|
font-family: 'Arial', sans-serif;
|
|
font-size: 14px;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
.context-menu-item {
|
|
padding: 8px 16px;
|
|
cursor: pointer;
|
|
transition: background-color 0.2s ease;
|
|
border: none;
|
|
background: none;
|
|
width: 100%;
|
|
text-align: left;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
.context-menu-item:hover {
|
|
background-color: #404040;
|
|
}
|
|
|
|
.context-menu-item:active {
|
|
background-color: #4a4a4a;
|
|
}
|
|
|
|
.context-menu-separator {
|
|
height: 1px;
|
|
background-color: #555;
|
|
margin: 4px 0;
|
|
}
|
|
|
|
/* Override Electron's native context menu when possible */
|
|
::-webkit-context-menu {
|
|
background-color: #2d2d2d !important;
|
|
border: 1px solid #555 !important;
|
|
border-radius: 4px !important;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6) !important;
|
|
}
|
|
|
|
::-webkit-context-menu-item {
|
|
background-color: #2d2d2d !important;
|
|
color: #e0e0e0 !important;
|
|
padding: 8px 16px !important;
|
|
}
|
|
|
|
::-webkit-context-menu-item:hover {
|
|
background-color: #404040 !important;
|
|
}
|
|
|
|
/* Additional dark scrollbar for any dropdowns */
|
|
*::-webkit-scrollbar {
|
|
width: 8px;
|
|
}
|
|
|
|
*::-webkit-scrollbar-track {
|
|
background: #1a1a1a;
|
|
}
|
|
|
|
*::-webkit-scrollbar-thumb {
|
|
background: #555;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
*::-webkit-scrollbar-thumb:hover {
|
|
background: #666;
|
|
}
|
|
|
|
.logo-watermark {
|
|
position: fixed;
|
|
bottom: 20px;
|
|
right: 20px;
|
|
width: auto;
|
|
height: 60px;
|
|
opacity: 0.6;
|
|
z-index: 1000;
|
|
pointer-events: auto;
|
|
cursor: pointer;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.logo-watermark:hover {
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.repo-watermark {
|
|
position: fixed;
|
|
bottom: 20px;
|
|
left: 20px;
|
|
width: auto;
|
|
height: 60px;
|
|
opacity: 0.6;
|
|
z-index: 1000;
|
|
pointer-events: auto;
|
|
cursor: pointer;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.repo-watermark:hover {
|
|
opacity: 0.8;
|
|
}
|
|
|
|
/* Hide logos when window height is too small */
|
|
@media (max-height: 120px) {
|
|
.logo-watermark,
|
|
.repo-watermark {
|
|
display: none !important;
|
|
}
|
|
}
|
|
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<table id="containerTable" class="vampire-matrix" height="20px">
|
|
<thead>
|
|
<tr>
|
|
<th data-column="agentid">Agent ID <span class="arrow" id="agentidArrow"></span></th>
|
|
<th data-column="container">Container <span class="arrow" id="containerArrow"></span></th>
|
|
<th data-column="hostname">Hostname <span class="arrow" id="hostnameArrow"></span></th>
|
|
<th data-column="username">Username <span class="arrow" id="usernameArrow"></span></th>
|
|
<th data-column="process">Process <span class="arrow" id="processArrow"></span></th>
|
|
<th data-column="pid">PID <span class="arrow" id="pidArrow"></span></th>
|
|
<th data-column="ip">IP <span class="arrow" id="ipArrow"></span></th>
|
|
<th data-column="arch">Arch <span class="arrow" id="archArrow"></span></th>
|
|
<th data-column="platform">Platform <span class="arrow" id="platformArrow"></span></th>
|
|
<th data-column="mode">Mode <span class="arrow" id="modeArrow"></span></th>
|
|
<th data-column="checkin">Checkin <span class="arrow" id="checkinArrow"></span></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<!-- Container rows will be added here by renderer.js -->
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Logo watermark -->
|
|
<img src="./assets/images/logo1.png" alt="Loki Logo" class="logo-watermark" onclick="openTwitterProfile()">
|
|
|
|
<!-- Repo watermark -->
|
|
<img src="./assets/images/repo.png" alt="Repo Logo" class="repo-watermark" onclick="openGitHubRepo()">
|
|
|
|
<script src="dashboard.js"></script>
|
|
<script>
|
|
const { shell } = require('electron');
|
|
|
|
// Function to open GitHub repository
|
|
function openGitHubRepo() {
|
|
shell.openExternal('https://github.com/boku7/loki');
|
|
}
|
|
|
|
// Function to open Twitter/X profile
|
|
function openTwitterProfile() {
|
|
shell.openExternal('https://x.com/0xBoku');
|
|
}
|
|
|
|
// Function to apply background image to dashboard
|
|
function applyBackgroundImage(settings) {
|
|
if (settings && settings.backgroundImage) {
|
|
console.log('Applying background image:', settings.backgroundImage);
|
|
document.body.style.backgroundImage = `url('${settings.backgroundImage}')`;
|
|
} else {
|
|
console.log('Clearing background image');
|
|
document.body.style.backgroundImage = "url('./assets/images/background.png')";
|
|
}
|
|
}
|
|
|
|
// Handle font settings from agent settings window
|
|
ipcRenderer.on('apply-font-settings', (event, settings) => {
|
|
console.log('=== RECEIVED APPLY-FONT-SETTINGS IPC IN DASHBOARD ===');
|
|
console.log('Settings:', settings);
|
|
applyBackgroundImage(settings);
|
|
});
|
|
|
|
// Logo visibility management based on window height and width
|
|
function manageLogoVisibility() {
|
|
const logoWatermark = document.querySelector('.logo-watermark');
|
|
const repoWatermark = document.querySelector('.repo-watermark');
|
|
const minHeightNeeded = 140; // 60px logo + 40px padding + some buffer
|
|
|
|
function checkLogoVisibility() {
|
|
const windowHeight = window.innerHeight;
|
|
const windowWidth = window.innerWidth;
|
|
|
|
// Hide both logos if window height is too small
|
|
const shouldHideForHeight = windowHeight < minHeightNeeded;
|
|
|
|
let shouldHideRepoForWidth = false;
|
|
|
|
// Calculate actual overlap positions if both logos exist
|
|
if (logoWatermark && repoWatermark && !shouldHideForHeight) {
|
|
// Ensure logos are visible first to get accurate measurements
|
|
logoWatermark.style.display = 'block';
|
|
repoWatermark.style.display = 'block';
|
|
|
|
// Get actual logo dimensions and positions
|
|
const logoRect = logoWatermark.getBoundingClientRect();
|
|
const repoRect = repoWatermark.getBoundingClientRect();
|
|
|
|
// Calculate positions:
|
|
// Left logo: positioned at left: 20px, so its right edge is at (20px + width)
|
|
// Right logo: positioned at right: 20px, so its left edge is at (windowWidth - 20px - width)
|
|
const repoRightEdge = 20 + repoRect.width;
|
|
const logoLeftEdge = windowWidth - 20 - logoRect.width;
|
|
|
|
// Hide repo logo if its right edge would touch or overlap the right logo's left edge
|
|
shouldHideRepoForWidth = repoRightEdge >= logoLeftEdge;
|
|
}
|
|
|
|
if (logoWatermark) {
|
|
logoWatermark.style.display = shouldHideForHeight ? 'none' : 'block';
|
|
}
|
|
if (repoWatermark) {
|
|
// Hide repo logo if either height is too small OR width would cause overlap
|
|
repoWatermark.style.display = (shouldHideForHeight || shouldHideRepoForWidth) ? 'none' : 'block';
|
|
}
|
|
}
|
|
|
|
// Check on load and resize
|
|
checkLogoVisibility();
|
|
window.addEventListener('resize', checkLogoVisibility);
|
|
}
|
|
|
|
// Persistent row hover management
|
|
function setupPersistentHover() {
|
|
const table = document.getElementById('containerTable');
|
|
if (!table) return;
|
|
|
|
const tbody = table.querySelector('tbody');
|
|
|
|
// Add event listeners for persistent hover
|
|
tbody.addEventListener('mouseenter', (e) => {
|
|
if (e.target.tagName === 'TD') {
|
|
const row = e.target.closest('tr');
|
|
if (row && row.parentElement === tbody) {
|
|
// Remove hover class from all other rows first
|
|
tbody.querySelectorAll('.row-hovered').forEach(otherRow => {
|
|
if (otherRow !== row) {
|
|
otherRow.classList.remove('row-hovered');
|
|
}
|
|
});
|
|
// Add hover class to current row
|
|
row.classList.add('row-hovered');
|
|
}
|
|
}
|
|
}, true);
|
|
|
|
tbody.addEventListener('mouseleave', (e) => {
|
|
if (e.target.tagName === 'TD') {
|
|
const row = e.target.closest('tr');
|
|
if (row && row.parentElement === tbody) {
|
|
// Check if mouse is still within the table area
|
|
const rect = table.getBoundingClientRect();
|
|
const mouseX = e.clientX;
|
|
const mouseY = e.clientY;
|
|
|
|
if (mouseX < rect.left || mouseX > rect.right ||
|
|
mouseY < rect.top || mouseY > rect.bottom) {
|
|
row.classList.remove('row-hovered');
|
|
}
|
|
}
|
|
}
|
|
}, true);
|
|
|
|
// Clean up hover states when mouse leaves table entirely
|
|
table.addEventListener('mouseleave', () => {
|
|
tbody.querySelectorAll('.row-hovered').forEach(row => {
|
|
row.classList.remove('row-hovered');
|
|
});
|
|
});
|
|
}
|
|
|
|
// Load saved settings on page load
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
// Setup persistent hover system
|
|
setupPersistentHover();
|
|
|
|
// Setup logo visibility management
|
|
manageLogoVisibility();
|
|
|
|
// Load settings from customize.js file
|
|
ipcRenderer.invoke('get-customize-settings').then(settings => {
|
|
if (settings) {
|
|
console.log('Loading customize settings for dashboard:', settings);
|
|
applyBackgroundImage(settings);
|
|
}
|
|
}).catch(error => {
|
|
console.error('Error loading customize settings in dashboard:', error);
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|