1659 lines
56 KiB
HTML
1659 lines
56 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<script>
|
|
var savedTheme = localStorage.getItem('audit_theme');
|
|
if (savedTheme === 'dark' || (!savedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
|
document.documentElement.setAttribute('data-theme', 'dark');
|
|
} else {
|
|
document.documentElement.setAttribute('data-theme', 'light');
|
|
}
|
|
</script>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<link rel="icon" type="image/png" href="{{ url_for('icon', path='/icon.png') }}">
|
|
<title>Công cụ Kiểm tra Hardening - VNPT-MEDIA</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
background: var(--bg-main);
|
|
min-height: 100vh;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: flex-start;
|
|
padding: 20px;
|
|
position: relative;
|
|
overflow-x: hidden;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
body::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: -50%;
|
|
left: -50%;
|
|
width: 200%;
|
|
height: 200%;
|
|
background:
|
|
linear-gradient(135deg, transparent 0%, transparent 48%, rgba(33, 150, 243, 0.05) 48%, rgba(33, 150, 243, 0.05) 52%, transparent 52%, transparent 100%),
|
|
linear-gradient(45deg, transparent 0%, transparent 48%, rgba(33, 150, 243, 0.08) 48%, rgba(33, 150, 243, 0.08) 52%, transparent 52%, transparent 100%),
|
|
linear-gradient(135deg, transparent 0%, transparent 48%, rgba(33, 150, 243, 0.03) 48%, rgba(33, 150, 243, 0.03) 52%, transparent 52%, transparent 100%);
|
|
background-size: 300px 300px, 400px 400px, 500px 500px;
|
|
background-position: 0 0, 100px 100px, 200px 0;
|
|
z-index: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
body::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
width: 0;
|
|
height: 0;
|
|
border-style: solid;
|
|
border-width: 0 400px 400px 0;
|
|
border-color: transparent rgba(33, 150, 243, 0.03) transparent transparent;
|
|
z-index: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.container {
|
|
background: var(--card-bg);
|
|
border-radius: 20px;
|
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
|
padding: 40px;
|
|
max-width: 800px;
|
|
width: 100%;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.logo {
|
|
text-align: center;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.logo-text {
|
|
font-size: 42px;
|
|
font-weight: bold;
|
|
color: var(--primary-color);
|
|
letter-spacing: 6px;
|
|
}
|
|
|
|
.logo-icon {
|
|
display: inline-block;
|
|
width: 50px;
|
|
height: 50px;
|
|
background: #1976d2;
|
|
border-radius: 50% 0 50% 50%;
|
|
position: relative;
|
|
margin-right: 10px;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.logo-icon::after {
|
|
content: '';
|
|
position: absolute;
|
|
width: 25px;
|
|
height: 25px;
|
|
background: var(--card-bg);
|
|
border-radius: 50%;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
}
|
|
|
|
h1 {
|
|
color: var(--text-main);
|
|
text-align: center;
|
|
margin-bottom: 10px;
|
|
font-size: 24px;
|
|
}
|
|
|
|
.subtitle {
|
|
text-align: center;
|
|
color: var(--text-muted);
|
|
margin-bottom: 30px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.alert {
|
|
padding: 15px;
|
|
border-radius: 10px;
|
|
margin-bottom: 20px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.alert-success {
|
|
background-color: var(--success-bg);
|
|
color: #155724;
|
|
border: 1px solid #c3e6cb;
|
|
}
|
|
|
|
.alert-error {
|
|
background-color: var(--danger-bg);
|
|
color: var(--danger-color);
|
|
border: 1px solid #f5c6cb;
|
|
}
|
|
|
|
.alert-warning {
|
|
background-color: var(--warning-bg);
|
|
color: #856404;
|
|
border: 1px solid #ffeaa7;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 25px;
|
|
}
|
|
|
|
label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
color: var(--text-main);
|
|
font-weight: 600;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.required {
|
|
color: var(--danger-color);
|
|
}
|
|
|
|
input[type="file"],
|
|
select {
|
|
width: 100%;
|
|
padding: 12px;
|
|
border: 2px solid #667eea;
|
|
border-radius: 10px;
|
|
background: var(--table-header-bg);
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
font-size: 14px;
|
|
}
|
|
|
|
input[type="file"] {
|
|
border-style: dashed;
|
|
}
|
|
|
|
input[type="file"]:hover,
|
|
select:hover {
|
|
border-color: #764ba2;
|
|
background: #e9ecef;
|
|
}
|
|
|
|
select:focus {
|
|
outline: none;
|
|
border-color: #764ba2;
|
|
box-shadow: 0 0 0 3px rgba(118, 75, 162, 0.1);
|
|
}
|
|
|
|
.hint {
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
margin-top: 5px;
|
|
font-style: italic;
|
|
}
|
|
|
|
.auto-detect-info {
|
|
margin-top: 10px;
|
|
padding: 10px 15px;
|
|
border-radius: 8px;
|
|
background-color: var(--info-bg);
|
|
border-left: 4px solid #2196f3;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.auto-detect-info.success {
|
|
background-color: var(--success-bg);
|
|
border-left-color: var(--success-color);
|
|
}
|
|
|
|
.auto-detect-info.warning {
|
|
background-color: var(--warning-bg);
|
|
border-left-color: var(--warning-color);
|
|
}
|
|
|
|
.auto-detect-info.error {
|
|
background-color: var(--danger-bg);
|
|
border-left-color: var(--danger-color);
|
|
}
|
|
|
|
.auto-detect-info span:first-child {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.btn-submit {
|
|
width: 100%;
|
|
padding: 15px;
|
|
background: #1976d2;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 10px;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: background 0.3s ease;
|
|
text-transform: uppercase;
|
|
letter-spacing: 1px;
|
|
}
|
|
|
|
.btn-submit:hover {
|
|
background: #1565c0;
|
|
}
|
|
|
|
.btn-submit:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.info-box {
|
|
background-color: var(--success-bg);
|
|
border-left: 4px solid #4caf50;
|
|
padding: 15px;
|
|
border-radius: 5px;
|
|
margin-bottom: 25px;
|
|
}
|
|
|
|
.info-box h3 {
|
|
color: var(--success-color);
|
|
font-size: 16px;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.info-box ol {
|
|
margin-left: 20px;
|
|
color: var(--text-main);
|
|
font-size: 14px;
|
|
line-height: 1.8;
|
|
}
|
|
|
|
.footer {
|
|
text-align: center;
|
|
margin-top: 30px;
|
|
padding-top: 20px;
|
|
border-top: 1px solid #eee;
|
|
color: var(--text-muted);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.link {
|
|
color: var(--primary-color);
|
|
text-decoration: none;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.link:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.user-info {
|
|
position: absolute;
|
|
top: 20px;
|
|
right: 20px;
|
|
background: var(--card-bg);
|
|
padding: 10px 20px;
|
|
border-radius: 25px;
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
font-size: 14px;
|
|
color: var(--text-main);
|
|
}
|
|
|
|
.user-info strong {
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
/* User Status Bar Styles */
|
|
.user-status-bar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 20px;
|
|
margin-bottom: 20px;
|
|
border-radius: 10px;
|
|
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
|
|
border: 1px solid #a5d6a7;
|
|
}
|
|
|
|
.user-status-bar.guest {
|
|
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
|
|
border: 1px solid #ffcc80;
|
|
}
|
|
|
|
.user-status-logged-in {
|
|
color: var(--success-color);
|
|
font-size: 14px;
|
|
}
|
|
|
|
.user-status-logged-in strong {
|
|
color: var(--success-color);
|
|
}
|
|
|
|
.user-role {
|
|
color: var(--success-color);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.user-status-guest {
|
|
color: var(--warning-color);
|
|
font-size: 14px;
|
|
}
|
|
|
|
.btn-logout {
|
|
background: #ef5350;
|
|
color: white;
|
|
padding: 8px 16px;
|
|
border-radius: 20px;
|
|
text-decoration: none;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.btn-logout:hover {
|
|
background: #e53935;
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 2px 8px rgba(229, 57, 53, 0.3);
|
|
}
|
|
|
|
.user-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
align-items: center;
|
|
}
|
|
|
|
.btn-admin {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
padding: 8px 16px;
|
|
border-radius: 20px;
|
|
text-decoration: none;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.btn-admin:hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.4);
|
|
}
|
|
|
|
.btn-login-small {
|
|
background: #1976d2;
|
|
color: white;
|
|
padding: 8px 16px;
|
|
border-radius: 20px;
|
|
text-decoration: none;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.btn-login-small:hover {
|
|
background: #1565c0;
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 2px 8px rgba(21, 101, 192, 0.3);
|
|
}
|
|
|
|
.link-disabled {
|
|
color: var(--text-muted);
|
|
cursor: not-allowed;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Loading Screen Styles */
|
|
.loading-overlay {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: linear-gradient(135deg, #1976d2 0%, #1565c0 50%, #0d47a1 100%);
|
|
background-image:
|
|
linear-gradient(135deg, rgba(255, 255, 255, 0.05) 25%, transparent 25%),
|
|
linear-gradient(225deg, rgba(255, 255, 255, 0.05) 25%, transparent 25%),
|
|
linear-gradient(45deg, rgba(255, 255, 255, 0.05) 25%, transparent 25%),
|
|
linear-gradient(315deg, rgba(255, 255, 255, 0.05) 25%, transparent 25%),
|
|
linear-gradient(135deg, #1976d2 0%, #1565c0 50%, #0d47a1 100%);
|
|
background-size: 100px 100px, 100px 100px, 100px 100px, 100px 100px, 100% 100%;
|
|
background-position: 0 0, 50px 0, 50px -50px, 0px 50px, 0 0;
|
|
z-index: 9999;
|
|
justify-content: center;
|
|
align-items: center;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.loading-overlay.active {
|
|
display: flex;
|
|
}
|
|
|
|
.loading-content {
|
|
text-align: center;
|
|
color: white;
|
|
}
|
|
|
|
.loading-spinner {
|
|
width: 80px;
|
|
height: 80px;
|
|
margin: 0 auto 30px;
|
|
position: relative;
|
|
}
|
|
|
|
.loading-spinner::before,
|
|
.loading-spinner::after {
|
|
content: '';
|
|
position: absolute;
|
|
border-radius: 50%;
|
|
border: 4px solid transparent;
|
|
border-top-color: white;
|
|
animation: spin 1.5s linear infinite;
|
|
}
|
|
|
|
.loading-spinner::before {
|
|
width: 80px;
|
|
height: 80px;
|
|
top: 0;
|
|
left: 0;
|
|
}
|
|
|
|
.loading-spinner::after {
|
|
width: 60px;
|
|
height: 60px;
|
|
top: 10px;
|
|
left: 10px;
|
|
border-top-color: rgba(255, 255, 255, 0.5);
|
|
animation-duration: 1s;
|
|
animation-direction: reverse;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% {
|
|
transform: rotate(0deg);
|
|
}
|
|
|
|
100% {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
.loading-text {
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
margin-bottom: 15px;
|
|
animation: pulse 2s ease-in-out infinite;
|
|
}
|
|
|
|
.loading-subtext {
|
|
font-size: 16px;
|
|
opacity: 0.9;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
|
|
|
|
@keyframes pulse {
|
|
|
|
0%,
|
|
100% {
|
|
opacity: 1;
|
|
}
|
|
|
|
50% {
|
|
opacity: 0.7;
|
|
}
|
|
}
|
|
|
|
@keyframes bounce {
|
|
|
|
0%,
|
|
100% {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
50% {
|
|
transform: translateY(-5px);
|
|
}
|
|
}
|
|
|
|
.loading-progress {
|
|
width: 300px;
|
|
height: 6px;
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border-radius: 3px;
|
|
margin: 20px auto;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.loading-progress-bar {
|
|
height: 100%;
|
|
background: var(--card-bg);
|
|
border-radius: 3px;
|
|
animation: progress 3s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes progress {
|
|
0% {
|
|
width: 0%;
|
|
}
|
|
|
|
50% {
|
|
width: 70%;
|
|
}
|
|
|
|
100% {
|
|
width: 100%;
|
|
}
|
|
}
|
|
|
|
/* Responsive Styles */
|
|
@media screen and (max-width: 768px) {
|
|
body {
|
|
padding: 10px;
|
|
}
|
|
|
|
.container {
|
|
padding: 25px;
|
|
border-radius: 15px;
|
|
}
|
|
|
|
.logo-text {
|
|
font-size: 32px;
|
|
letter-spacing: 4px;
|
|
}
|
|
|
|
.logo-icon {
|
|
width: 40px;
|
|
height: 40px;
|
|
}
|
|
|
|
.logo-icon::after {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 20px;
|
|
}
|
|
|
|
.info-box ol {
|
|
font-size: 13px;
|
|
}
|
|
|
|
.loading-text {
|
|
font-size: 20px;
|
|
}
|
|
|
|
.loading-subtext {
|
|
font-size: 14px;
|
|
}
|
|
|
|
.loading-progress {
|
|
width: 250px;
|
|
}
|
|
}
|
|
|
|
@media screen and (max-width: 480px) {
|
|
body {
|
|
padding: 5px;
|
|
}
|
|
|
|
.container {
|
|
padding: 15px;
|
|
border-radius: 10px;
|
|
}
|
|
|
|
.logo-text {
|
|
font-size: 28px;
|
|
letter-spacing: 3px;
|
|
}
|
|
|
|
.logo-icon {
|
|
width: 35px;
|
|
height: 35px;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 16px;
|
|
}
|
|
|
|
.subtitle {
|
|
font-size: 12px;
|
|
}
|
|
|
|
label {
|
|
font-size: 13px;
|
|
}
|
|
|
|
input[type="file"],
|
|
select {
|
|
font-size: 13px;
|
|
padding: 10px;
|
|
}
|
|
|
|
.hint {
|
|
font-size: 11px;
|
|
}
|
|
|
|
.btn-submit {
|
|
font-size: 14px;
|
|
padding: 12px;
|
|
}
|
|
|
|
.info-box {
|
|
padding: 12px;
|
|
}
|
|
|
|
.info-box h3 {
|
|
font-size: 14px;
|
|
}
|
|
|
|
.info-box ol {
|
|
font-size: 12px;
|
|
margin-left: 15px;
|
|
}
|
|
|
|
.footer {
|
|
font-size: 10px;
|
|
}
|
|
|
|
.footer .link {
|
|
font-size: 11px;
|
|
}
|
|
|
|
.loading-spinner {
|
|
width: 60px;
|
|
height: 60px;
|
|
}
|
|
|
|
.loading-spinner::before {
|
|
width: 60px;
|
|
height: 60px;
|
|
}
|
|
|
|
.loading-spinner::after {
|
|
width: 45px;
|
|
height: 45px;
|
|
top: 7.5px;
|
|
left: 7.5px;
|
|
}
|
|
|
|
.loading-text {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.loading-subtext {
|
|
font-size: 12px;
|
|
}
|
|
|
|
.loading-progress {
|
|
width: 200px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
{% include 'header.html' %}
|
|
|
|
<!-- OS Detection Overlay (Trang Trung Gian 1) -->
|
|
|
|
<div class="loading-overlay" id="osDetectionOverlay"
|
|
style="background: linear-gradient(135deg, #1f1c2c 0%, #928DAB 100%); z-index: 10001;">
|
|
<div class="loading-content" id="osDetectionContent">
|
|
<!-- Spinner / Scan Animation -->
|
|
<div class="os-scan-container" id="osScanContainer">
|
|
<div class="scanner-ring"></div>
|
|
<div class="loading-text" style="margin-top: 30px;">Đang phân tích Hệ Điều Hành...</div>
|
|
<div class="loading-subtext">Hệ thống đang quét nội dung file để tự động xác định OS</div>
|
|
</div>
|
|
|
|
<!-- Result / Fallback container (hidden by default) -->
|
|
<div class="os-result-container" id="osResultContainer"
|
|
style="display: none; background: rgba(255, 255, 255, 0.15); backdrop-filter: blur(15px); padding: 35px; border-radius: 20px; box-shadow: 0 15px 35px rgba(0,0,0,0.2); border: 1px solid rgba(255, 255, 255, 0.3); max-width: 500px; margin: 0 auto; width: 90%;">
|
|
<div id="osResultIcon" style="font-size: 50px; margin-bottom: 20px; animation: bounce 2s infinite;">❓
|
|
</div>
|
|
<div class="loading-text" id="osResultTitle" style="font-size: 22px;">Không thể tự động phân tích OS
|
|
</div>
|
|
<div class="loading-subtext" id="osResultMessage" style="margin-bottom: 25px; font-size: 15px;">Vui lòng
|
|
chọn cấu hình Hệ điều hành thủ công để tiếp tục:</div>
|
|
|
|
<div class="form-group" style="text-align: left; margin-bottom: 25px;">
|
|
<select id="manual_os_select"
|
|
style="width: 100%; padding: 14px; border-radius: 10px; border: 2px solid rgba(255,255,255,0.8); background: rgba(255, 255, 255, 0.95); font-size: 16px; color: var(--text-main); cursor: pointer; transition: all 0.3s ease; outline: none; box-shadow: 0 4px 10px rgba(0,0,0,0.1);">
|
|
<option value="" disabled selected>-- Chọn Hệ Điều Hành --</option>
|
|
{% for key, value in os_config.items() %}
|
|
<option value="{{ key }}">{{ value.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<button type="button" class="btn-submit" id="btnContinueUpload"
|
|
style="background: linear-gradient(135deg, #00b09b 0%, #96c93d 100%); box-shadow: 0 5px 15px rgba(150, 201, 61, 0.4);">
|
|
Xác nhận & Tiếp tục
|
|
</button>
|
|
<button type="button" class="btn-cancel" id="btnCancelUpload"
|
|
style="margin-top: 15px; background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.3); color: white; padding: 12px; width: 100%; border-radius: 10px; cursor: pointer; transition: all 0.3s ease; font-weight: 600; font-size: 15px;">
|
|
Hủy bỏ
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<style>
|
|
.scanner-ring {
|
|
width: 100px;
|
|
height: 100px;
|
|
border-radius: 50%;
|
|
border: 4px solid transparent;
|
|
border-top-color: #00e5ff;
|
|
border-bottom-color: #b700ff;
|
|
margin: 0 auto;
|
|
animation: spin 1.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite;
|
|
position: relative;
|
|
}
|
|
|
|
.scanner-ring::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 10px;
|
|
left: 10px;
|
|
right: 10px;
|
|
bottom: 10px;
|
|
border-radius: 50%;
|
|
border: 4px solid transparent;
|
|
border-left-color: #ff007f;
|
|
border-right-color: #00ff88;
|
|
animation: spin 2s linear infinite reverse;
|
|
}
|
|
|
|
.scanner-ring::after {
|
|
content: 'OS';
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
font-size: 20px;
|
|
font-weight: bold;
|
|
color: white;
|
|
}
|
|
|
|
#btnCancelUpload:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border-color: rgba(255, 255, 255, 0.5);
|
|
}
|
|
|
|
#manual_os_select:hover,
|
|
#manual_os_select:focus {
|
|
border-color: #00e5ff;
|
|
box-shadow: 0 0 15px rgba(0, 229, 255, 0.3);
|
|
}
|
|
</style>
|
|
|
|
<!-- Loading Overlay -->
|
|
<div class="loading-overlay" id="loadingOverlay">
|
|
<div class="loading-content">
|
|
<!-- VNPT Logo -->
|
|
<div style="margin-bottom: 30px;">
|
|
<span class="logo-icon" style="width: 60px; height: 60px; background: var(--card-bg);">
|
|
<span
|
|
style="position: absolute; width: 30px; height: 30px; background: #1976d2; border-radius: 50%; top: 50%; left: 50%; transform: translate(-50%, -50%);"></span>
|
|
</span>
|
|
<span
|
|
style="font-size: 48px; font-weight: bold; color: white; letter-spacing: 8px; vertical-align: middle; margin-left: 10px;">VNPT-MEDIA</span>
|
|
</div>
|
|
|
|
<div class="loading-spinner"></div>
|
|
<div class="loading-text">Đang xử lý file...</div>
|
|
<div class="loading-subtext">Vui lòng đợi trong khi hệ thống giải mã và tạo báo cáo</div>
|
|
|
|
<div style="margin-top: 20px; font-size: 14px; opacity: 0.9;">
|
|
<p>⏳ Quá trình này có thể mất vài phút...</p>
|
|
<p style="margin-top: 10px; font-size: 12px; opacity: 0.7;">Đang giải mã và tạo báo cáo</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Disclaimer Popup -->
|
|
<div class="disclaimer-overlay" id="disclaimerOverlay">
|
|
<div class="disclaimer-popup">
|
|
<div class="disclaimer-header">
|
|
<span class="disclaimer-icon">⚠️</span>
|
|
<h2>Thông báo</h2>
|
|
</div>
|
|
<div class="disclaimer-content">
|
|
<p>Trang web phục vụ mục đích để <strong>kiểm tra tuân thủ nhanh</strong>.</p>
|
|
<p>Một số các tiêu chí có thể chưa được hoàn thiện, đã và đang được cập nhật.</p>
|
|
</div>
|
|
<div class="disclaimer-footer">
|
|
<button class="btn-disclaimer-confirm" onclick="acceptDisclaimer()">
|
|
✓ Tôi đã hiểu
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
/* Disclaimer Popup Styles */
|
|
.disclaimer-overlay {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
backdrop-filter: blur(5px);
|
|
z-index: 10000;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.disclaimer-overlay.active {
|
|
display: flex;
|
|
}
|
|
|
|
.disclaimer-popup {
|
|
background: var(--card-bg);
|
|
border-radius: 16px;
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
max-width: 500px;
|
|
width: 90%;
|
|
overflow: hidden;
|
|
animation: popupSlideIn 0.3s ease-out;
|
|
}
|
|
|
|
@keyframes popupSlideIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-30px) scale(0.95);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0) scale(1);
|
|
}
|
|
}
|
|
|
|
.disclaimer-header {
|
|
background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);
|
|
color: white;
|
|
padding: 20px 25px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.disclaimer-icon {
|
|
font-size: 28px;
|
|
}
|
|
|
|
.disclaimer-header h2 {
|
|
margin: 0;
|
|
font-size: 20px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.disclaimer-content {
|
|
padding: 25px;
|
|
color: var(--text-main);
|
|
line-height: 1.8;
|
|
}
|
|
|
|
.disclaimer-content p {
|
|
margin: 0 0 12px 0;
|
|
font-size: 15px;
|
|
}
|
|
|
|
.disclaimer-content p:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.disclaimer-content strong {
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.disclaimer-footer {
|
|
padding: 15px 25px 25px;
|
|
text-align: center;
|
|
}
|
|
|
|
.btn-disclaimer-confirm {
|
|
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
|
|
color: white;
|
|
border: none;
|
|
padding: 14px 40px;
|
|
border-radius: 8px;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 4px 15px rgba(25, 118, 210, 0.3);
|
|
}
|
|
|
|
.btn-disclaimer-confirm:hover {
|
|
background: linear-gradient(135deg, #1565c0 0%, #0d47a1 100%);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 20px rgba(25, 118, 210, 0.4);
|
|
}
|
|
|
|
@media screen and (max-width: 480px) {
|
|
.disclaimer-popup {
|
|
width: 95%;
|
|
}
|
|
|
|
.disclaimer-header {
|
|
padding: 15px 20px;
|
|
}
|
|
|
|
.disclaimer-header h2 {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.disclaimer-content {
|
|
padding: 20px;
|
|
}
|
|
|
|
.disclaimer-content p {
|
|
font-size: 14px;
|
|
}
|
|
|
|
.btn-disclaimer-confirm {
|
|
padding: 12px 30px;
|
|
font-size: 14px;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<div class="container">
|
|
<!-- Top Navigation Tabs -->
|
|
<div class="top-nav-tabs"
|
|
style="display: flex; justify-content: center; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; background: rgba(25, 118, 210, 0.05); padding: 15px; border-radius: 12px; border: 1px solid rgba(25, 118, 210, 0.1);">
|
|
<a href="{{ url_for('index') }}" class="nav-tab"
|
|
style="padding: 10px 20px; background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%); color: white; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; box-shadow: 0 4px 10px rgba(25, 118, 210, 0.3);">🏠 Trang chủ</a>
|
|
<a href="{{ url_for('my_files') }}" class="nav-tab"
|
|
style="padding: 10px 20px; background: var(--card-bg); color: var(--text-main); text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; border: 1px solid var(--border-color); box-shadow: 0 2px 5px rgba(0,0,0,0.05);">📁 File của tôi</a>
|
|
{% if user_info and user_info.role == 'admin' %}
|
|
<a href="{{ url_for('admin_tools') }}" class="nav-tab"
|
|
style="padding: 10px 20px; background: var(--card-bg); color: var(--text-main); text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 14px; transition: all 0.3s ease; border: 1px solid var(--border-color); box-shadow: 0 2px 5px rgba(0,0,0,0.05);">⚙️ Quản trị</a>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="logo">
|
|
<img src="{{ url_for('icon', path='/icon.png') }}" alt="VNPT-MEDIA Logo"
|
|
style="max-width: 60px; height: auto; vertical-align: middle; margin-right: 10px;">
|
|
<span class="logo-text">VNPT-MEDIA</span>
|
|
</div>
|
|
<h1>🔐 Công cụ Kiểm tra Hardening</h1>
|
|
<p class="subtitle">Giải mã & Tạo Báo cáo Tuân thủ</p>
|
|
|
|
{% if message %}
|
|
<div class="alert alert-{{ message.type }}">
|
|
{{ message.text }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="info-box">
|
|
<h3>📋 Hướng dẫn sử dụng:</h3>
|
|
<ol>
|
|
<li>Tải <strong>Script đánh giá</strong> phù hợp với hệ điều hành (xem tab Công cụ bên dưới)</li>
|
|
<li>Chạy script trên máy chủ cần đánh giá để thu thập dữ liệu</li>
|
|
<li>Chọn <strong>File kết quả đã mã hóa</strong> (.txt.enc) bên dưới - Có thể chọn nhiều file</li>
|
|
<li>Chọn <strong>Hệ điều hành</strong>: để <em>Tự động phát hiện</em> (hệ thống giải mã và đọc nội dung
|
|
file) hoặc chọn thủ công</li>
|
|
<li>Nhấn <strong>Xử lý</strong> để giải mã và tạo báo cáo Excel</li>
|
|
</ol>
|
|
<p style="margin-top: 10px; font-size: 13px; color: var(--text-main);">
|
|
ℹ️ Chế độ <strong>Tự động phát hiện</strong> xác định OS từ nội dung file (chính xác) thay vì tên file.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Download Tools Section -->
|
|
<div class="tools-section">
|
|
<div class="tools-header" onclick="toggleToolsSection()">
|
|
<h3>📥 Công cụ Đánh giá</h3>
|
|
<span id="toolsToggleIcon" class="toggle-icon">▼</span>
|
|
</div>
|
|
<div id="toolsContent" class="tools-content">
|
|
{% if tools_list %}
|
|
<table class="tools-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Hệ điều hành</th>
|
|
<th>Phiên bản</th>
|
|
<th>Cập nhật</th>
|
|
<th>Tải về</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for tool in tools_list %}
|
|
{% if tool.available %}
|
|
<tr>
|
|
<td><span class="os-badge {{ tool.os_key }}">{{ tool.icon }} {{ tool.name }}</span></td>
|
|
<td><code>{{ tool.version }}</code></td>
|
|
<td>{{ tool.updated }}</td>
|
|
<td><a href="{{ url_for('download_tool', version_key=tool.version_key) }}"
|
|
class="btn-download">⬇️ Tải</a></td>
|
|
</tr>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<p style="text-align: center; color: var(--text-muted); padding: 20px;">
|
|
📭 Chưa có công cụ nào được tải lên.
|
|
</p>
|
|
{% endif %}
|
|
<p class="tools-note">
|
|
💡 <strong>Hướng dẫn:</strong> Tải script, chạy trên máy chủ với quyền root/admin, sau đó upload
|
|
file kết quả (.txt.enc) ở form bên trên.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Documentation Guidelines Section -->
|
|
<div class="tools-section">
|
|
<div class="tools-header" onclick="toggleDocsSection()">
|
|
<h3>📚 Tài liệu Cấu hình & Hướng dẫn</h3>
|
|
<span id="docsToggleIcon" class="toggle-icon">▼</span>
|
|
</div>
|
|
<div id="docsContent" class="tools-content">
|
|
{% if docs_list %}
|
|
<table class="tools-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Tên tài liệu</th>
|
|
<th>Phiên bản</th>
|
|
<th>Kích thước</th>
|
|
<th>Ngày đăng</th>
|
|
<th>Tải về</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for doc in docs_list %}
|
|
{% if doc.available %}
|
|
<tr>
|
|
<td>
|
|
<strong>📄 {{ doc.name }}</strong>
|
|
<span style="font-size: 12px; color: var(--text-muted); margin-left: 6px;">({{ doc.file }})</span>
|
|
</td>
|
|
<td><code>{{ doc.version }}</code></td>
|
|
<td>{{ doc.size }}</td>
|
|
<td>{{ doc.updated }}</td>
|
|
<td style="white-space: nowrap;">
|
|
<a href="{{ url_for('download_doc', doc_key=doc.doc_key) }}" class="btn-download" style="white-space: nowrap;">⬇️ Tải</a>
|
|
</td>
|
|
</tr>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<p style="text-align: center; color: var(--text-muted); padding: 25px;">
|
|
📭 Chưa có tài liệu hướng dẫn cấu hình nào được tải lên.
|
|
</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
/* Tools Section Styles */
|
|
.tools-section {
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 12px;
|
|
margin-bottom: 25px;
|
|
overflow: hidden;
|
|
box-shadow: var(--shadow);
|
|
}
|
|
|
|
.tools-header {
|
|
background: var(--table-header-bg);
|
|
padding: 15px 20px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
transition: background 0.3s ease;
|
|
}
|
|
|
|
.tools-header:hover {
|
|
background: var(--hover-bg);
|
|
}
|
|
|
|
.tools-header h3 {
|
|
margin: 0;
|
|
color: var(--primary-color);
|
|
font-size: 16px;
|
|
}
|
|
|
|
.toggle-icon {
|
|
color: var(--primary-color);
|
|
font-size: 14px;
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.toggle-icon.collapsed {
|
|
transform: rotate(-90deg);
|
|
}
|
|
|
|
.tools-content {
|
|
padding: 20px;
|
|
display: block;
|
|
}
|
|
|
|
.tools-content.collapsed {
|
|
display: none;
|
|
}
|
|
|
|
.tools-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.tools-table th,
|
|
.tools-table td {
|
|
padding: 12px 15px;
|
|
text-align: left;
|
|
border-bottom: 1px solid var(--border-color);
|
|
}
|
|
|
|
.tools-table th {
|
|
background: var(--bg-main);
|
|
font-weight: 600;
|
|
color: var(--text-main);
|
|
}
|
|
|
|
.tools-table tr:hover {
|
|
background: var(--table-row-hover);
|
|
}
|
|
|
|
.os-badge {
|
|
padding: 4px 10px;
|
|
border-radius: 15px;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.os-badge.ubuntu {
|
|
background-color: var(--success-bg);
|
|
color: var(--success-color);
|
|
}
|
|
|
|
.os-badge.centos {
|
|
background-color: var(--danger-bg);
|
|
color: var(--danger-color);
|
|
}
|
|
|
|
.os-badge.rhel {
|
|
background-color: var(--danger-bg);
|
|
color: var(--danger-color);
|
|
}
|
|
|
|
.os-badge.oracle {
|
|
background-color: var(--warning-bg);
|
|
color: var(--warning-color);
|
|
}
|
|
|
|
.os-badge.windows {
|
|
background-color: var(--info-bg);
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.tools-table code {
|
|
background-color: var(--success-bg);
|
|
padding: 2px 8px;
|
|
border-radius: 4px;
|
|
color: var(--success-color);
|
|
font-family: 'Consolas', monospace;
|
|
}
|
|
|
|
.btn-download {
|
|
background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
|
|
color: white;
|
|
padding: 6px 14px;
|
|
border-radius: 6px;
|
|
text-decoration: none;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
transition: all 0.3s ease;
|
|
display: inline-block;
|
|
}
|
|
|
|
.btn-download:hover {
|
|
background: linear-gradient(135deg, #1565c0 0%, #0d47a1 100%);
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 3px 10px rgba(21, 101, 192, 0.3);
|
|
}
|
|
|
|
.btn-download.disabled {
|
|
background: #9e9e9e;
|
|
cursor: not-allowed;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.btn-download.disabled:hover {
|
|
transform: none;
|
|
box-shadow: none;
|
|
background: #9e9e9e;
|
|
}
|
|
|
|
.tools-note {
|
|
margin: 15px 0 0 0;
|
|
padding: 12px 15px;
|
|
background-color: var(--warning-bg);
|
|
border-left: 4px solid #ffc107;
|
|
border-radius: 4px;
|
|
font-size: 13px;
|
|
color: var(--warning-color);
|
|
}
|
|
|
|
@media screen and (max-width: 600px) {
|
|
|
|
.tools-table th,
|
|
.tools-table td {
|
|
padding: 8px 10px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.os-badge {
|
|
font-size: 11px;
|
|
padding: 3px 8px;
|
|
}
|
|
|
|
.btn-download {
|
|
padding: 5px 10px;
|
|
font-size: 11px;
|
|
}
|
|
}
|
|
</style>
|
|
<!-- Upload Form -->
|
|
<form id="uploadForm" action="upload" method="post" enctype="multipart/form-data">
|
|
<!-- File selection -->
|
|
<div class="form-group">
|
|
<label for="encrypted_files">
|
|
1. File đã mã hóa <span class="required">*</span>
|
|
</label>
|
|
<input type="file" id="encrypted_files" name="encrypted_files" accept=".enc,.txt" multiple required>
|
|
<p class="hint">Chọn một hoặc nhiều file <code>.txt.enc</code> để giải mã và tạo báo cáo.</p>
|
|
|
|
<!-- Auto-detect status display -->
|
|
<div id="autoDetectInfo" class="auto-detect-info" style="display: none;">
|
|
<span id="autoDetectIcon">🔍</span>
|
|
<span id="autoDetectMessage">Đang chờ chọn file...</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- OS selection - hidden by default, shown if auto-detect fails -->
|
|
<div class="form-group" id="osSelectionGroup" style="display: none;">
|
|
<label for="os_type">
|
|
2. Hệ điều hành
|
|
</label>
|
|
<select id="os_type" name="os_type">
|
|
<option value="auto" selected>🔍 Tự động phát hiện (khuyến nghị)</option>
|
|
{% for key, value in os_config.items() %}
|
|
<option value="{{ key }}">{{ value.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<p class="hint" id="osTypeHint">
|
|
Để <strong>Tự động</strong>: hệ thống giải mã file và đọc nội dung để xác định OS (chính xác nhất).
|
|
Chọn thủ công nếu muốn override.
|
|
</p>
|
|
</div>
|
|
|
|
<button type="button" class="btn-submit" id="submitBtn" onclick="handleUploadClick()">
|
|
⚡ Xử lý File
|
|
</button>
|
|
</form>
|
|
|
|
<div class="footer"
|
|
style="text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; color: var(--text-muted); font-size: 13px;">
|
|
<p>Công cụ Kiểm tra Hardening v2.3 - VNPT-MEDIA</p>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// ROOT_PATH: tiền tố URL từ backend (ví dụ: '/audit' khi deploy Docker,
|
|
// hoặc '' khi chạy local). Dùng để xây dựng URL API đúng trong mọi môi trường.
|
|
const ROOT_PATH = "{{ root_path }}";
|
|
|
|
window.onerror = function (msg, url, line, col, error) {
|
|
alert("JS Error: " + msg + " \nLine: " + line);
|
|
};
|
|
// Tools section toggle
|
|
function toggleToolsSection() {
|
|
const content = document.getElementById('toolsContent');
|
|
const icon = document.getElementById('toolsToggleIcon');
|
|
content.classList.toggle('collapsed');
|
|
icon.classList.toggle('collapsed');
|
|
}
|
|
|
|
// Docs section toggle
|
|
function toggleDocsSection() {
|
|
const content = document.getElementById('docsContent');
|
|
const icon = document.getElementById('docsToggleIcon');
|
|
content.classList.toggle('collapsed');
|
|
icon.classList.toggle('collapsed');
|
|
}
|
|
|
|
// Disclaimer popup handling
|
|
const disclaimerOverlay = document.getElementById('disclaimerOverlay');
|
|
|
|
function showDisclaimer() {
|
|
if (!localStorage.getItem('disclaimerAccepted')) {
|
|
disclaimerOverlay.classList.add('active');
|
|
}
|
|
}
|
|
|
|
function acceptDisclaimer() {
|
|
localStorage.setItem('disclaimerAccepted', 'true');
|
|
disclaimerOverlay.classList.remove('active');
|
|
}
|
|
|
|
window.addEventListener('DOMContentLoaded', function () {
|
|
// Login bắt buộc — user đã đăng nhập, không cần nhập email nữa.
|
|
// Hiển thị disclaimer ngay khi vào trang lần đầu (mỗi phiên trình duyệt)
|
|
showDisclaimer();
|
|
|
|
// Đảm bảo Form luôn bắt đầu ở trạng thái Tự động
|
|
document.getElementById('os_type').value = 'auto';
|
|
});
|
|
|
|
|
|
// ── Email Registration Logic ───────────────────────────────────────────
|
|
const VNPT_EMAIL_RE = /^[a-z0-9._%+\-]+@vnpt\.vn$/i;
|
|
|
|
function showEmailRegOverlay() {
|
|
document.getElementById('emailRegOverlay').style.display = 'flex';
|
|
}
|
|
function hideEmailRegOverlay() {
|
|
document.getElementById('emailRegOverlay').style.display = 'none';
|
|
}
|
|
|
|
async function handleEmailRegSubmit() {
|
|
const input = document.getElementById('emailRegInput');
|
|
const errorDiv = document.getElementById('emailRegError');
|
|
const btn = document.getElementById('emailRegBtn');
|
|
const email = input.value.trim().toLowerCase();
|
|
|
|
// Client-side validation
|
|
if (!VNPT_EMAIL_RE.test(email)) {
|
|
errorDiv.textContent = '⚠️ Email không hợp lệ. Vui lòng nhập email có đuôi @vnpt.vn';
|
|
errorDiv.style.display = 'block';
|
|
input.style.borderColor = '#e53935';
|
|
input.focus();
|
|
return;
|
|
}
|
|
|
|
// Reset UI
|
|
errorDiv.style.display = 'none';
|
|
input.style.borderColor = '#90caf9';
|
|
btn.disabled = true;
|
|
btn.textContent = 'Đang xử lý...';
|
|
|
|
try {
|
|
const fd = new FormData();
|
|
fd.append('email', email);
|
|
const resp = await fetch(ROOT_PATH + '/api/register_email', {
|
|
method: 'POST',
|
|
body: fd
|
|
});
|
|
const data = await resp.json();
|
|
|
|
if (data.success) {
|
|
localStorage.setItem('vnpt_user_email', email);
|
|
hideEmailRegOverlay();
|
|
// Hiển thị disclaimer sau khi xác nhận email
|
|
showDisclaimer();
|
|
} else {
|
|
errorDiv.textContent = data.message || 'Lỗi không xác định. Vui lòng thử lại.';
|
|
errorDiv.style.display = 'block';
|
|
input.style.borderColor = '#e53935';
|
|
}
|
|
} catch (err) {
|
|
errorDiv.textContent = 'Lỗi kết nối. Vui lòng thử lại sau.';
|
|
errorDiv.style.display = 'block';
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.textContent = 'Xác nhận & Tiếp tục';
|
|
}
|
|
}
|
|
|
|
async function restoreEmailToSession(email) {
|
|
// Đưa email từ localStorage lên session backend, không hỏi lại user
|
|
try {
|
|
const fd = new FormData();
|
|
fd.append('email', email);
|
|
const resp = await fetch(ROOT_PATH + '/api/register_email', {
|
|
method: 'POST',
|
|
body: fd
|
|
});
|
|
const data = await resp.json();
|
|
if (!data.success) {
|
|
// Email trong localStorage không hợp lệ → xóa và hỏi lại
|
|
localStorage.removeItem('vnpt_user_email');
|
|
showEmailRegOverlay();
|
|
return;
|
|
}
|
|
} catch (_) {
|
|
// Lỗi mạng → tiếp tục bình thường, không block user
|
|
}
|
|
showDisclaimer();
|
|
}
|
|
|
|
// ── Upload form logic ──────────────────────────────────────────────────
|
|
const form = document.getElementById('uploadForm');
|
|
const loadingOverlay = document.getElementById('loadingOverlay');
|
|
const osSelect = document.getElementById('os_type');
|
|
const fileInput = document.getElementById('encrypted_files');
|
|
const autoDetectInfo = document.getElementById('autoDetectInfo');
|
|
const autoDetectIcon = document.getElementById('autoDetectIcon');
|
|
const autoDetectMessage = document.getElementById('autoDetectMessage');
|
|
|
|
// OS name display map (mirrors OS_CONFIG)
|
|
const osNames = {
|
|
'centos': 'CentOS',
|
|
'rhel': 'RHEL (Red Hat Enterprise Linux)',
|
|
'ubuntu': 'Ubuntu',
|
|
'oracle': 'Oracle Linux',
|
|
'windows': 'Windows'
|
|
};
|
|
|
|
// Client-side OS patterns for guessing from filename (hint only)
|
|
const osPatterns = {
|
|
'centos': ['centos', 'centos_linux'],
|
|
'rhel': ['rhel', 'red_hat', 'red-hat', 'redhat'],
|
|
'ubuntu': ['ubuntu'],
|
|
'oracle': ['oracle', 'oracle_linux', 'ol_'],
|
|
'windows': ['windows', 'win_server', 'winserver']
|
|
};
|
|
|
|
function detectOSFromFilename(filename) {
|
|
if (!filename) return null;
|
|
const lower = filename.toLowerCase();
|
|
for (const [key, patterns] of Object.entries(osPatterns)) {
|
|
for (const p of patterns) {
|
|
if (lower.includes(p)) return key;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function updateAutoDetectInfo(status, message) {
|
|
autoDetectInfo.style.display = 'flex';
|
|
const iconMap = { success: '✅', warning: '⚠️', error: '❌', info: '🔍' };
|
|
const classMap = { success: 'success', warning: 'warning', error: 'error', info: '' };
|
|
autoDetectIcon.textContent = iconMap[status] || '🔍';
|
|
autoDetectMessage.textContent = message;
|
|
autoDetectInfo.className = 'auto-detect-info ' + (classMap[status] || '');
|
|
}
|
|
|
|
// Removed auto OS detect on file change to prevent overlap
|
|
|
|
// Update hint text when user manually changes OS
|
|
osSelect.addEventListener('change', function () {
|
|
const hintEl = document.getElementById('osTypeHint');
|
|
if (this.value === 'auto') {
|
|
hintEl.innerHTML = 'Để <strong>Tự động</strong>: hệ thống giải mã file và đọc nội dung để xác định OS (chính xác nhất). Chọn thủ công nếu muốn override.';
|
|
} else {
|
|
hintEl.innerHTML = `<strong>Đã chọn thủ công: ${osNames[this.value] || this.value}.</strong> Hệ thống sẽ dùng cấu hình này khi xử lý.`;
|
|
}
|
|
});
|
|
|
|
// OS Detection Overlay Logic
|
|
const osOverlay = document.getElementById('osDetectionOverlay');
|
|
const scanContainer = document.getElementById('osScanContainer');
|
|
const resultContainer = document.getElementById('osResultContainer');
|
|
const defaultScanHtml = scanContainer.innerHTML; // Cache default HTML
|
|
|
|
async function handleUploadClick() {
|
|
const files = fileInput.files;
|
|
if (files.length === 0) {
|
|
alert("Vui lòng chọn ít nhất một file để Xử lý.");
|
|
return;
|
|
}
|
|
|
|
// Nếu người dùng đã chọn thủ công một OS trên form chính (không phải auto)
|
|
if (osSelect.value !== 'auto') {
|
|
// Trang trung gian 2 luôn (Xử lý ngay)
|
|
startGeneratingReport();
|
|
return;
|
|
}
|
|
|
|
// Trang Trung Gian 1: Detect OS
|
|
scanContainer.innerHTML = defaultScanHtml; // Reset vể trạng thái đang scan
|
|
|
|
// Xoá dòng mô tả chi tiết theo yêu cầu
|
|
const subtextEl = scanContainer.querySelector('.loading-subtext');
|
|
if (subtextEl) {
|
|
subtextEl.remove();
|
|
}
|
|
|
|
scanContainer.style.display = 'block';
|
|
resultContainer.style.display = 'none';
|
|
osOverlay.classList.add('active');
|
|
|
|
try {
|
|
let allDetected = true;
|
|
let detectedResults = [];
|
|
|
|
for (let i = 0; i < files.length; i++) {
|
|
const file = files[i];
|
|
|
|
// Cập nhật giao diện: Đang xử lý file nào
|
|
const textEl = scanContainer.querySelector('.loading-text');
|
|
if (textEl) {
|
|
textEl.innerHTML = `Đang phân tích file (${i + 1}/${files.length}):<br><span style="font-size: 16px; color: #00e5ff; font-weight: normal; margin-top: 10px; display: inline-block;">${file.name}</span>`;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append('files', file);
|
|
|
|
// Gửi từng file lên API
|
|
// Dùng ROOT_PATH để URL đúng trong môi trường Docker (/audit/api/...)
|
|
const response = await fetch(ROOT_PATH + '/api/detect_os_from_content', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.detected_os) {
|
|
detectedResults.push({ name: file.name, os: data.os_name, os_key: data.detected_os });
|
|
} else {
|
|
// Nếu có bất kì file nào thất bại thì ngắt và chuyển sang chế độ chọn thủ công
|
|
allDetected = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (allDetected) {
|
|
// Thành công: Đã tìm thấy OS cho tất cả các file
|
|
let resultHtml = `
|
|
<div style="font-size: 60px; margin-bottom: 15px; animation: bounce 1s;">✅</div>
|
|
<div class="loading-text" style="font-size: 24px; color: #00ff88;">Hoàn tất phân tích!</div>
|
|
<div style="max-height: 150px; overflow-y: auto; margin-top: 15px; text-align: left; border-radius: 10px; background: rgba(0,0,0,0.3); padding: 15px;">
|
|
`;
|
|
for (let r of detectedResults) {
|
|
resultHtml += `<div style="font-size: 14px; margin-bottom: 8px; color: white;">📄 ${r.name} <br> ➔ <strong style="color: #00e5ff;">${r.os}</strong></div>`;
|
|
}
|
|
resultHtml += `</div>`;
|
|
|
|
scanContainer.innerHTML = resultHtml;
|
|
|
|
// Kiểm tra xem tất cả file có cùng OS không
|
|
const uniqueOsKeys = [...new Set(detectedResults.map(r => r.os_key))];
|
|
if (uniqueOsKeys.length === 1) {
|
|
// Tất cả file cùng OS → gửi OS key cụ thể xuống backend
|
|
// để backend không cần detect lại (chính xác hơn vì đã phân tích content)
|
|
osSelect.value = uniqueOsKeys[0];
|
|
console.log('[OS-DETECT] All files same OS, setting os_type=' + uniqueOsKeys[0]);
|
|
} else {
|
|
// Mixed OS → gửi 'auto' để backend xử lý từng file
|
|
osSelect.value = 'auto';
|
|
console.log('[OS-DETECT] Mixed OS detected, keeping auto mode');
|
|
}
|
|
|
|
// Delay 2s để user kịp nhìn thấy danh sách kết quả trước khi sang Trang 2
|
|
setTimeout(() => {
|
|
osOverlay.classList.remove('active');
|
|
startGeneratingReport();
|
|
}, 2000);
|
|
} else {
|
|
// Thất bại: Không xác định được ít nhất 1 file
|
|
scanContainer.style.display = 'none';
|
|
resultContainer.style.display = 'block';
|
|
}
|
|
} catch (error) {
|
|
console.error("Lỗi khi detect OS:", error);
|
|
// Fallback to manual selection on error
|
|
scanContainer.style.display = 'none';
|
|
resultContainer.style.display = 'block';
|
|
}
|
|
}
|
|
|
|
// Hàm bắt đầu xử lý (Trang Trung Gian 2)
|
|
function startGeneratingReport() {
|
|
loadingOverlay.classList.add('active');
|
|
|
|
// Sử dụng setTimeout để đảm bảo trình duyệt (Chrome/Firefox)
|
|
// kịp render giao diện Trang Trung Gian 2 ra màn hình
|
|
// trước khi luồng tải bị khoá cứng bởi lệnh 'form.submit()'
|
|
setTimeout(() => {
|
|
form.submit();
|
|
}, 100);
|
|
}
|
|
|
|
// Xử lý khi user bấm Xác nhận & Tiếp tục trong màn hình chọn thủ công
|
|
document.getElementById('btnContinueUpload').addEventListener('click', function () {
|
|
const manualOs = document.getElementById('manual_os_select').value;
|
|
if (!manualOs) {
|
|
alert("Vui lòng chọn một Hệ Điều Hành trong danh sách!");
|
|
return;
|
|
}
|
|
|
|
// Cập nhật thẻ select bị ẩn trong form chính
|
|
osSelect.value = manualOs;
|
|
|
|
// Ẩn lớp phủ trang 1 và gọi trang 2
|
|
osOverlay.classList.remove('active');
|
|
startGeneratingReport();
|
|
});
|
|
|
|
// Xử lý khi user bấm Hủy
|
|
document.getElementById('btnCancelUpload').addEventListener('click', function () {
|
|
osOverlay.classList.remove('active');
|
|
// User huỷ upload, form không submit.
|
|
});
|
|
</script>
|
|
</body>
|
|
|
|
</html> |