This commit is contained in:
usmannasir
2025-06-02 16:11:00 +05:00
parent db6014858e
commit 8d1aada377
4 changed files with 406 additions and 333 deletions

View File

@@ -875,6 +875,27 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
$scope.totalDBs = 0;
$scope.totalEmails = 0;
// SSH Logs
$scope.sshLogs = [];
$scope.loadingSSHLogs = true;
$scope.errorSSHLogs = '';
function fetchSSHLogs() {
$scope.loadingSSHLogs = true;
$http.get('/base/getRecentSSHLogs').then(function (response) {
$scope.loadingSSHLogs = false;
if (response.data && response.data.logs) {
$scope.sshLogs = response.data.logs;
} else {
$scope.sshLogs = [];
}
}, function (err) {
$scope.loadingSSHLogs = false;
$scope.errorSSHLogs = 'Failed to load SSH logs.';
});
}
fetchSSHLogs();
setInterval(fetchSSHLogs, 10000);
// SSH Logins
$scope.sshLogins = [];
$scope.loadingSSHLogins = true;

View File

@@ -6,10 +6,6 @@
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<!--
Note: We have updated colors to match the CyberPanel theme and fixed the system resource cards
to display properly with horizontal layout. All existing JS/selectors are preserved.
-->
<div
class="container"
style="
@@ -308,158 +304,44 @@
</div>
{% if admin %}
<!--
===============================
System Stat Cards (CPU / RAM / Disk) - Fixed to match image design
===============================
-->
<div ng-controller="homePageStatus" style="margin-bottom: 40px;">
<div
style="
display: flex;
justify-content: center;
gap: 20px;
align-items: stretch;
flex-wrap: wrap;
"
>
<div style="display: flex; justify-content: center; gap: 30px; align-items: stretch; flex-wrap: wrap;" class="system-stats-row">
<!-- CPU Usage -->
<div
class="system-stat-card"
style="
background: #fff;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 30px 40px;
min-width: 200px;
text-align: center;
border: 1px solid #e9ecef;
transition: all 0.3s ease;
"
>
<div
style="
font-size: 1rem;
color: #e53935;
font-weight: 600;
margin-bottom: 15px;
text-transform: uppercase;
letter-spacing: 1px;
"
>
{% trans "CPU Usage" %}
</div>
<div
id="redcircle"
class="c100 red cpu"
style="margin: 0 auto;"
>
<span
style="
font-weight: 700;
font-size: 1.2rem;
color: #333;
"
>
{$ cpuUsage $}%
</span>
<div class="slice">
<div class="bar" style="border-color: #e53935;"></div>
<div class="fill" style="border-color: #e53935;"></div>
<div style="background: #fff; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); padding: 30px 40px; flex: 1 1 0; min-width: 0; text-align: center; border: 1px solid #e9ecef;">
<div style="font-size: 1rem; color: #e53935; font-weight: 600; margin-bottom: 15px; text-transform: uppercase; letter-spacing: 1px;">{% trans "CPU Usage" %}</div>
<div style="display: flex; justify-content: center;">
<div id="redcircle" class="c100 red p{$ cpuUsage $}">
<span style="font-weight: 700; font-size: 1.2rem; color: #333;">{$ cpuUsage $}%</span>
<div class="slice">
<div class="bar"></div>
<div class="fill"></div>
</div>
</div>
</div>
</div>
<!-- RAM Usage -->
<div
class="system-stat-card"
style="
background: #fff;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 30px 40px;
min-width: 200px;
text-align: center;
border: 1px solid #e9ecef;
transition: all 0.3s ease;
"
>
<div
style="
font-size: 1rem;
color: #43a047;
font-weight: 600;
margin-bottom: 15px;
text-transform: uppercase;
letter-spacing: 1px;
"
>
{% trans "Ram Usage" %}
</div>
<div
id="greencircle"
class="c100 p0 green ram"
style="margin: 0 auto;"
>
<span
style="
font-weight: 700;
font-size: 1.2rem;
color: #333;
"
>
{$ ramUsage $}%
</span>
<div class="slice">
<div class="bar" style="border-color: #43a047;"></div>
<div class="fill" style="border-color: #43a047;"></div>
<div style="background: #fff; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); padding: 30px 40px; flex: 1 1 0; min-width: 0; text-align: center; border: 1px solid #e9ecef;">
<div style="font-size: 1rem; color: #43a047; font-weight: 600; margin-bottom: 15px; text-transform: uppercase; letter-spacing: 1px;">{% trans "Ram Usage" %}</div>
<div style="display: flex; justify-content: center;">
<div id="greencircle" class="c100 p{$ ramUsage $} green">
<span style="font-weight: 700; font-size: 1.2rem; color: #333;">{$ ramUsage $}%</span>
<div class="slice">
<div class="bar"></div>
<div class="fill"></div>
</div>
</div>
</div>
</div>
<!-- Disk Usage -->
<div
class="system-stat-card"
style="
background: #fff;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 30px 40px;
min-width: 200px;
text-align: center;
border: 1px solid #e9ecef;
transition: all 0.3s ease;
"
>
<div
style="
font-size: 1rem;
color: #d81b60;
font-weight: 600;
margin-bottom: 15px;
text-transform: uppercase;
letter-spacing: 1px;
"
>
{% trans "Disk Usage '/'" %}
</div>
<div
id="pinkcircle"
class="c100 pink disk"
style="margin: 0 auto;"
>
<span
style="
font-weight: 700;
font-size: 1.2rem;
color: #333;
"
>
{$ diskUsage $}%
</span>
<div class="slice">
<div class="bar" style="border-color: #d81b60;"></div>
<div class="fill" style="border-color: #d81b60;"></div>
<div style="background: #fff; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); padding: 30px 40px; flex: 1 1 0; min-width: 0; text-align: center; border: 1px solid #e9ecef;">
<div style="font-size: 1rem; color: #d81b60; font-weight: 600; margin-bottom: 15px; text-transform: uppercase; letter-spacing: 1px;">{% trans "Disk Usage '/'" %}</div>
<div style="display: flex; justify-content: center;">
<div id="pinkcircle" class="c100 pink p{$ diskUsage $}">
<span style="font-weight: 700; font-size: 1.2rem; color: #333;">{$ diskUsage $}%</span>
<div class="slice">
<div class="bar"></div>
<div class="fill"></div>
</div>
</div>
</div>
</div>
@@ -604,180 +486,66 @@
</div>
</div>
<!--
=========================================
Recent SSH Logins Widget (Table)
=========================================
-->
<div style="margin-bottom: 40px;">
<div
style="
background: #fff;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 25px 30px;
max-width: 900px;
margin: 0 auto;
border: 1px solid #e9ecef;
"
>
<div
style="
display: flex;
align-items: center;
margin-bottom: 18px;
"
>
<span
style="
font-size: 1.25rem;
font-weight: 700;
color: #333;
letter-spacing: 0.5px;
"
>
Recent SSH Logins
</span>
</div>
<!-- Loading State -->
<div
ng-if="loadingSSHLogins"
style="text-align: center; padding: 20px; color: #888;"
>
<i class="fa fa-spinner fa-spin"></i> Loading recent SSH logins...
</div>
<!-- No Logins Found -->
<div
ng-if="!loadingSSHLogins && sshLogins.length === 0"
style="text-align: center; color: #888; padding: 20px;"
>
No recent SSH logins found.
</div>
<!-- Table of Logins -->
<div
ng-if="!loadingSSHLogins && sshLogins.length > 0"
style="overflow-x: auto;"
>
<table
style="
width: 100%;
border-collapse: collapse;
min-width: 600px;
"
>
<!-- SSH Logins and Logs Row (side by side) -->
{% if admin %}
<div style="display: flex; gap: 30px; justify-content: center; align-items: flex-start; flex-wrap: wrap; margin-bottom: 40px;">
<!-- Recent SSH Logins Widget -->
<div style="flex: 1 1 420px; min-width: 320px; max-width: 600px; background: #fff; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); padding: 25px 30px; border: 1px solid #e9ecef;">
<div style="font-size: 1.25rem; font-weight: 700; color: #333; letter-spacing: 0.5px; margin-bottom: 18px;">Recent SSH Logins</div>
<div ng-if="loadingSSHLogins" style="text-align: center; padding: 20px; color: #888;">Loading recent SSH logins...</div>
<div ng-if="!loadingSSHLogins && sshLogins.length === 0" style="text-align: center; color: #888; padding: 20px;">No recent SSH logins found.</div>
<div ng-if="!loadingSSHLogins && sshLogins.length > 0" style="overflow-x: auto;">
<table style="width: 100%; border-collapse: collapse; min-width: 320px;">
<thead>
<tr
style="
background: #4c5fad;
color: #fff;
"
>
<th
style="
padding: 12px 15px;
border-radius: 8px 0 0 8px;
text-align: left;
font-weight: 600;
"
>
User
</th>
<th
style="
padding: 12px 15px;
text-align: left;
font-weight: 600;
"
>
IP
</th>
<th
style="
padding: 12px 15px;
text-align: left;
font-weight: 600;
"
>
Country
</th>
<th
style="
padding: 12px 15px;
text-align: left;
font-weight: 600;
"
>
Date/Time
</th>
<th
style="
padding: 12px 15px;
border-radius: 0 8px 8px 0;
text-align: left;
font-weight: 600;
"
>
Session
</th>
<tr style="background: #4c5fad; color: #fff;">
<th style="padding: 12px 15px; border-radius: 8px 0 0 8px; text-align: left; font-weight: 600;">User</th>
<th style="padding: 12px 15px; text-align: left; font-weight: 600;">IP</th>
<th style="padding: 12px 15px; text-align: left; font-weight: 600;">Country</th>
<th style="padding: 12px 15px; text-align: left; font-weight: 600;">Date/Time</th>
<th style="padding: 12px 15px; border-radius: 0 8px 8px 0; text-align: left; font-weight: 600;">Session</th>
</tr>
</thead>
<tbody>
<tr
ng-repeat="login in sshLogins"
class="ssh-login-row"
style="
background: #f8f9fa;
border-bottom: 1px solid #e9ecef;
transition: background 0.2s;
"
>
<td
style="
padding: 10px 15px;
font-weight: 600;
color: #333;
"
>
{$ login.user $}
</td>
<td style="padding: 10px 15px; color: #555;">
{$ login.ip $}
</td>
<tr ng-repeat="login in sshLogins" class="ssh-login-row" style="background: #f8f9fa; border-bottom: 1px solid #e9ecef; transition: background 0.2s;">
<td style="padding: 10px 15px; font-weight: 600; color: #333;">{$ login.user $}</td>
<td style="padding: 10px 15px; color: #555;">{$ login.ip $}</td>
<td style="padding: 10px 15px;">
<span ng-if="login.flag">
<img
ng-src="{$ login.flag $}"
alt="{$ login.country $}"
style="
height: 18px;
margin-right: 6px;
vertical-align: middle;
border-radius: 2px;
"
/>
</span>
<span ng-if="!login.flag && login.country">
{$ login.country $}
</span>
<span ng-if="!login.flag && !login.country">
-
</span>
</td>
<td style="padding: 10px 15px; color: #555;">
{$ login.date $}
</td>
<td style="padding: 10px 15px; color: #888;">
{$ login.session $}
<span ng-if="login.flag"><img ng-src="{$ login.flag $}" alt="{$ login.country $}" style="height: 18px; margin-right: 6px; vertical-align: middle; border-radius: 2px;" /></span>
<span ng-if="!login.flag && login.country">{$ login.country $}</span>
<span ng-if="!login.flag && !login.country">-</span>
</td>
<td style="padding: 10px 15px; color: #555;">{$ login.date $}</td>
<td style="padding: 10px 15px; color: #888;">{$ login.session $}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Recent SSH Logs Widget -->
<div style="flex: 1 1 420px; min-width: 320px; max-width: 600px; background: #fff; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); padding: 25px 30px; border: 1px solid #e9ecef;">
<div style="font-size: 1.25rem; font-weight: 700; color: #333; letter-spacing: 0.5px; margin-bottom: 18px;">Recent SSH Logs</div>
<div ng-if="loadingSSHLogs" style="text-align: center; padding: 20px; color: #888;">Loading recent SSH logs...</div>
<div ng-if="errorSSHLogs" style="text-align: center; color: #d81b60; padding: 20px;">{$ errorSSHLogs $}</div>
<div ng-if="!loadingSSHLogs && sshLogs.length === 0 && !errorSSHLogs" style="text-align: center; color: #888; padding: 20px;">No recent SSH logs found.</div>
<div ng-if="!loadingSSHLogs && sshLogs.length > 0" style="overflow-x: auto; max-height: 350px;">
<table style="width: 100%; border-collapse: collapse; min-width: 320px;">
<thead>
<tr style="background: #4c5fad; color: #fff;">
<th style="padding: 12px 15px; border-radius: 8px 0 0 8px; text-align: left; font-weight: 600;">Timestamp</th>
<th style="padding: 12px 15px; border-radius: 0 8px 8px 0; text-align: left; font-weight: 600;">Message</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="log in sshLogs" style="background: #f8f9fa; border-bottom: 1px solid #e9ecef; transition: background 0.2s;">
<td style="padding: 10px 15px; color: #555; white-space: nowrap;">{$ log.timestamp $}</td>
<td style="padding: 10px 15px; color: #333;">{$ log.message $}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
</div>
<!-- End Dashboard Stats Section -->
@@ -831,54 +599,294 @@
color: #4c5fad !important;
}
/* CSS for circular progress bars - ensuring they display correctly */
/* CSS for circular progress bars */
.c100 {
position: relative;
width: 100px;
height: 100px;
font-size: 100px;
width: 1em;
height: 1em;
border-radius: 50%;
background: #f0f0f0;
margin: 0 auto;
background-color: #f3f3f3;
display: inline-block;
}
.c100 *,
.c100 *:before,
.c100 *:after {
box-sizing: content-box;
}
.c100 > span {
position: absolute;
width: 100%;
z-index: 1;
left: 0;
top: 0;
line-height: 1em;
font-size: 0.2em;
color: #333;
display: block;
text-align: center;
white-space: nowrap;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.c100:after {
position: absolute;
top: 0.08em;
left: 0.08em;
display: block;
content: " ";
border-radius: 50%;
background-color: #fff;
width: 0.84em;
height: 0.84em;
}
.c100 .slice {
position: absolute;
width: 100px;
height: 100px;
clip: rect(0px, 100px, 100px, 50px);
width: 1em;
height: 1em;
clip: rect(0em, 1em, 1em, 0.5em);
}
.c100.p50 .slice {
clip: rect(auto, auto, auto, auto);
}
.c100 .bar {
position: absolute;
border: 8px solid transparent;
width: 84px;
height: 84px;
border: 0.08em solid #307bbb;
width: 0.84em;
height: 0.84em;
clip: rect(0em, 0.5em, 1em, 0em);
border-radius: 50%;
clip: rect(0px, 50px, 100px, 0px);
transform: rotate(0deg);
}
.c100 .fill {
position: absolute;
border: 8px solid transparent;
width: 84px;
height: 84px;
border: 0.08em solid #307bbb;
width: 0.84em;
height: 0.84em;
clip: rect(0em, 0.5em, 1em, 0em);
border-radius: 50%;
clip: rect(0px, 50px, 100px, 0px);
transform: rotate(180deg);
}
.c100 span {
position: absolute;
width: 100px;
z-index: 1;
left: 0;
top: 0;
line-height: 100px;
text-align: center;
font-size: 20px;
color: #333;
/* Generate rotation classes for percentages 0-49 */
.c100.p0 .bar { transform: rotate(0deg); }
.c100.p1 .bar { transform: rotate(3.6deg); }
.c100.p2 .bar { transform: rotate(7.2deg); }
.c100.p3 .bar { transform: rotate(10.8deg); }
.c100.p4 .bar { transform: rotate(14.4deg); }
.c100.p5 .bar { transform: rotate(18deg); }
.c100.p6 .bar { transform: rotate(21.6deg); }
.c100.p7 .bar { transform: rotate(25.2deg); }
.c100.p8 .bar { transform: rotate(28.8deg); }
.c100.p9 .bar { transform: rotate(32.4deg); }
.c100.p10 .bar { transform: rotate(36deg); }
.c100.p11 .bar { transform: rotate(39.6deg); }
.c100.p12 .bar { transform: rotate(43.2deg); }
.c100.p13 .bar { transform: rotate(46.8deg); }
.c100.p14 .bar { transform: rotate(50.4deg); }
.c100.p15 .bar { transform: rotate(54deg); }
.c100.p16 .bar { transform: rotate(57.6deg); }
.c100.p17 .bar { transform: rotate(61.2deg); }
.c100.p18 .bar { transform: rotate(64.8deg); }
.c100.p19 .bar { transform: rotate(68.4deg); }
.c100.p20 .bar { transform: rotate(72deg); }
.c100.p21 .bar { transform: rotate(75.6deg); }
.c100.p22 .bar { transform: rotate(79.2deg); }
.c100.p23 .bar { transform: rotate(82.8deg); }
.c100.p24 .bar { transform: rotate(86.4deg); }
.c100.p25 .bar { transform: rotate(90deg); }
.c100.p26 .bar { transform: rotate(93.6deg); }
.c100.p27 .bar { transform: rotate(97.2deg); }
.c100.p28 .bar { transform: rotate(100.8deg); }
.c100.p29 .bar { transform: rotate(104.4deg); }
.c100.p30 .bar { transform: rotate(108deg); }
.c100.p31 .bar { transform: rotate(111.6deg); }
.c100.p32 .bar { transform: rotate(115.2deg); }
.c100.p33 .bar { transform: rotate(118.8deg); }
.c100.p34 .bar { transform: rotate(122.4deg); }
.c100.p35 .bar { transform: rotate(126deg); }
.c100.p36 .bar { transform: rotate(129.6deg); }
.c100.p37 .bar { transform: rotate(133.2deg); }
.c100.p38 .bar { transform: rotate(136.8deg); }
.c100.p39 .bar { transform: rotate(140.4deg); }
.c100.p40 .bar { transform: rotate(144deg); }
.c100.p41 .bar { transform: rotate(147.6deg); }
.c100.p42 .bar { transform: rotate(151.2deg); }
.c100.p43 .bar { transform: rotate(154.8deg); }
.c100.p44 .bar { transform: rotate(158.4deg); }
.c100.p45 .bar { transform: rotate(162deg); }
.c100.p46 .bar { transform: rotate(165.6deg); }
.c100.p47 .bar { transform: rotate(169.2deg); }
.c100.p48 .bar { transform: rotate(172.8deg); }
.c100.p49 .bar { transform: rotate(176.4deg); }
.c100.p50 .bar { transform: rotate(180deg); }
/* For values > 50%, show the fill */
.c100.p51 .slice,
.c100.p52 .slice,
.c100.p53 .slice,
.c100.p54 .slice,
.c100.p55 .slice,
.c100.p56 .slice,
.c100.p57 .slice,
.c100.p58 .slice,
.c100.p59 .slice,
.c100.p60 .slice,
.c100.p61 .slice,
.c100.p62 .slice,
.c100.p63 .slice,
.c100.p64 .slice,
.c100.p65 .slice,
.c100.p66 .slice,
.c100.p67 .slice,
.c100.p68 .slice,
.c100.p69 .slice,
.c100.p70 .slice,
.c100.p71 .slice,
.c100.p72 .slice,
.c100.p73 .slice,
.c100.p74 .slice,
.c100.p75 .slice,
.c100.p76 .slice,
.c100.p77 .slice,
.c100.p78 .slice,
.c100.p79 .slice,
.c100.p80 .slice,
.c100.p81 .slice,
.c100.p82 .slice,
.c100.p83 .slice,
.c100.p84 .slice,
.c100.p85 .slice,
.c100.p86 .slice,
.c100.p87 .slice,
.c100.p88 .slice,
.c100.p89 .slice,
.c100.p90 .slice,
.c100.p91 .slice,
.c100.p92 .slice,
.c100.p93 .slice,
.c100.p94 .slice,
.c100.p95 .slice,
.c100.p96 .slice,
.c100.p97 .slice,
.c100.p98 .slice,
.c100.p99 .slice,
.c100.p100 .slice {
clip: rect(auto, auto, auto, auto);
}
.c100.p51 .bar { transform: rotate(180deg); }
.c100.p52 .bar { transform: rotate(180deg); }
.c100.p53 .bar { transform: rotate(180deg); }
.c100.p54 .bar { transform: rotate(180deg); }
.c100.p55 .bar { transform: rotate(180deg); }
.c100.p56 .bar { transform: rotate(180deg); }
.c100.p57 .bar { transform: rotate(180deg); }
.c100.p58 .bar { transform: rotate(180deg); }
.c100.p59 .bar { transform: rotate(180deg); }
.c100.p60 .bar { transform: rotate(180deg); }
.c100.p61 .bar { transform: rotate(180deg); }
.c100.p62 .bar { transform: rotate(180deg); }
.c100.p63 .bar { transform: rotate(180deg); }
.c100.p64 .bar { transform: rotate(180deg); }
.c100.p65 .bar { transform: rotate(180deg); }
.c100.p66 .bar { transform: rotate(180deg); }
.c100.p67 .bar { transform: rotate(180deg); }
.c100.p68 .bar { transform: rotate(180deg); }
.c100.p69 .bar { transform: rotate(180deg); }
.c100.p70 .bar { transform: rotate(180deg); }
.c100.p71 .bar { transform: rotate(180deg); }
.c100.p72 .bar { transform: rotate(180deg); }
.c100.p73 .bar { transform: rotate(180deg); }
.c100.p74 .bar { transform: rotate(180deg); }
.c100.p75 .bar { transform: rotate(180deg); }
.c100.p76 .bar { transform: rotate(180deg); }
.c100.p77 .bar { transform: rotate(180deg); }
.c100.p78 .bar { transform: rotate(180deg); }
.c100.p79 .bar { transform: rotate(180deg); }
.c100.p80 .bar { transform: rotate(180deg); }
.c100.p81 .bar { transform: rotate(180deg); }
.c100.p82 .bar { transform: rotate(180deg); }
.c100.p83 .bar { transform: rotate(180deg); }
.c100.p84 .bar { transform: rotate(180deg); }
.c100.p85 .bar { transform: rotate(180deg); }
.c100.p86 .bar { transform: rotate(180deg); }
.c100.p87 .bar { transform: rotate(180deg); }
.c100.p88 .bar { transform: rotate(180deg); }
.c100.p89 .bar { transform: rotate(180deg); }
.c100.p90 .bar { transform: rotate(180deg); }
.c100.p91 .bar { transform: rotate(180deg); }
.c100.p92 .bar { transform: rotate(180deg); }
.c100.p93 .bar { transform: rotate(180deg); }
.c100.p94 .bar { transform: rotate(180deg); }
.c100.p95 .bar { transform: rotate(180deg); }
.c100.p96 .bar { transform: rotate(180deg); }
.c100.p97 .bar { transform: rotate(180deg); }
.c100.p98 .bar { transform: rotate(180deg); }
.c100.p99 .bar { transform: rotate(180deg); }
.c100.p100 .bar { transform: rotate(180deg); }
.c100.p51 .fill { transform: rotate(183.6deg); }
.c100.p52 .fill { transform: rotate(187.2deg); }
.c100.p53 .fill { transform: rotate(190.8deg); }
.c100.p54 .fill { transform: rotate(194.4deg); }
.c100.p55 .fill { transform: rotate(198deg); }
.c100.p56 .fill { transform: rotate(201.6deg); }
.c100.p57 .fill { transform: rotate(205.2deg); }
.c100.p58 .fill { transform: rotate(208.8deg); }
.c100.p59 .fill { transform: rotate(212.4deg); }
.c100.p60 .fill { transform: rotate(216deg); }
.c100.p61 .fill { transform: rotate(219.6deg); }
.c100.p62 .fill { transform: rotate(223.2deg); }
.c100.p63 .fill { transform: rotate(226.8deg); }
.c100.p64 .fill { transform: rotate(230.4deg); }
.c100.p65 .fill { transform: rotate(234deg); }
.c100.p66 .fill { transform: rotate(237.6deg); }
.c100.p67 .fill { transform: rotate(241.2deg); }
.c100.p68 .fill { transform: rotate(244.8deg); }
.c100.p69 .fill { transform: rotate(248.4deg); }
.c100.p70 .fill { transform: rotate(252deg); }
.c100.p71 .fill { transform: rotate(255.6deg); }
.c100.p72 .fill { transform: rotate(259.2deg); }
.c100.p73 .fill { transform: rotate(262.8deg); }
.c100.p74 .fill { transform: rotate(266.4deg); }
.c100.p75 .fill { transform: rotate(270deg); }
.c100.p76 .fill { transform: rotate(273.6deg); }
.c100.p77 .fill { transform: rotate(277.2deg); }
.c100.p78 .fill { transform: rotate(280.8deg); }
.c100.p79 .fill { transform: rotate(284.4deg); }
.c100.p80 .fill { transform: rotate(288deg); }
.c100.p81 .fill { transform: rotate(291.6deg); }
.c100.p82 .fill { transform: rotate(295.2deg); }
.c100.p83 .fill { transform: rotate(298.8deg); }
.c100.p84 .fill { transform: rotate(302.4deg); }
.c100.p85 .fill { transform: rotate(306deg); }
.c100.p86 .fill { transform: rotate(309.6deg); }
.c100.p87 .fill { transform: rotate(313.2deg); }
.c100.p88 .fill { transform: rotate(316.8deg); }
.c100.p89 .fill { transform: rotate(320.4deg); }
.c100.p90 .fill { transform: rotate(324deg); }
.c100.p91 .fill { transform: rotate(327.6deg); }
.c100.p92 .fill { transform: rotate(331.2deg); }
.c100.p93 .fill { transform: rotate(334.8deg); }
.c100.p94 .fill { transform: rotate(338.4deg); }
.c100.p95 .fill { transform: rotate(342deg); }
.c100.p96 .fill { transform: rotate(345.6deg); }
.c100.p97 .fill { transform: rotate(349.2deg); }
.c100.p98 .fill { transform: rotate(352.8deg); }
.c100.p99 .fill { transform: rotate(356.4deg); }
.c100.p100 .fill { transform: rotate(360deg); }
/* Color variants for progress circles */
.c100.red .bar, .c100.red .fill {
border-color: #e53935;
@@ -896,6 +904,12 @@
canvas {
transition: none !important;
}
@media (min-width: 900px) {
.system-stats-row {
flex-wrap: nowrap !important;
}
}
</style>
<!-- Chart.js (unchanged) -->

View File

@@ -20,4 +20,5 @@ urlpatterns = [
re_path(r'^getDiskIOStats$', views.getDiskIOStats, name='getDiskIOStats'),
re_path(r'^getCPULoadGraph$', views.getCPULoadGraph, name='getCPULoadGraph'),
re_path(r'^getRecentSSHLogins$', views.getRecentSSHLogins, name='getRecentSSHLogins'),
re_path(r'^getRecentSSHLogs$', views.getRecentSSHLogs, name='getRecentSSHLogs'),
]

View File

@@ -588,3 +588,40 @@ def getRecentSSHLogins(request):
return HttpResponse(json.dumps({'logins': logins}), content_type='application/json')
except Exception as e:
return HttpResponse(json.dumps({'error': str(e)}), content_type='application/json', status=500)
@csrf_exempt
@require_GET
def getRecentSSHLogs(request):
try:
user_id = request.session.get('userID')
if not user_id:
return HttpResponse(json.dumps({'error': 'Not logged in'}), content_type='application/json', status=403)
currentACL = ACLManager.loadedACL(user_id)
if not currentACL.get('admin', 0):
return HttpResponse(json.dumps({'error': 'Admin only'}), content_type='application/json', status=403)
from plogical.processUtilities import ProcessUtilities
distro = ProcessUtilities.decideDistro()
if distro in [ProcessUtilities.ubuntu, ProcessUtilities.ubuntu20]:
log_path = '/var/log/auth.log'
else:
log_path = '/var/log/secure'
try:
output = ProcessUtilities.outputExecutioner(f'tail -n 100 {log_path}')
except Exception as e:
return HttpResponse(json.dumps({'error': f'Failed to read log: {str(e)}'}), content_type='application/json', status=500)
lines = output.split('\n')
logs = []
for line in lines:
if 'sshd' in line:
# Try to split into timestamp and message
parts = line.split()
if len(parts) > 4:
timestamp = ' '.join(parts[:3])
message = ' '.join(parts[4:])
else:
timestamp = ''
message = line
logs.append({'timestamp': timestamp, 'message': message, 'raw': line})
return HttpResponse(json.dumps({'logs': logs}), content_type='application/json')
except Exception as e:
return HttpResponse(json.dumps({'error': str(e)}), content_type='application/json', status=500)