This commit is contained in:
2026-03-26 13:47:46 +07:00
commit 56ab9f931e
11 changed files with 2912 additions and 0 deletions

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"liveServer.settings.multiRootWorkspaceName": "AccManager"
}

139
CHECK.html Normal file
View File

@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VaultSentinel - Bảng Kiểm Tra</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; background: #f5f5f5; padding: 20px; }
.container { max-width: 800px; margin: 0 auto; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); overflow: hidden; }
header { background: #667eea; color: white; padding: 20px; }
h1 { font-size: 1.8em; }
.content { padding: 30px; }
.checklist { list-style: none; }
.checklist li { padding: 12px; margin: 8px 0; background: #f9f9f9; border-left: 4px solid #ddd; border-radius: 4px; display: flex; align-items: center; }
.checklist li.done { border-left-color: #4caf50; background: #f1f8f4; }
.checklist li.error { border-left-color: #f44336; background: #fef5f5; }
.checklist li:before { content: "○"; font-size: 1.5em; margin-right: 15px; font-weight: bold; color: #ddd; }
.checklist li.done:before { content: "✓"; color: #4caf50; }
.checklist li.error:before { content: "✗"; color: #f44336; }
.status-box { padding: 15px; margin: 20px 0; border-radius: 5px; }
.status-success { background: #d4edda; border-left: 4px solid #28a745; color: #155724; }
.status-warning { background: #fff3cd; border-left: 4px solid #ffc107; color: #856404; }
.status-info { background: #d1ecf1; border-left: 4px solid #17a2b8; color: #0c5460; }
button { background: #667eea; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; margin: 5px; }
button:hover { background: #764ba2; }
footer { background: #f9f9f9; padding: 20px; text-align: center; border-top: 1px solid #ddd; color: #666; font-size: 0.9em; }
</style>
</head>
<body>
<div class="container">
<header>
<h1>🔍 VaultSentinel - Bảng Kiểm Tra</h1>
<p>Kiểm tra xem ứng dụng có được thiết lập đúng không</p>
</header>
<div class="content">
<div class="status-box status-info">
<strong> Hướng Dẫn:</strong> Bảng kiểm tra này giúp bạn xác minh rằng tất cả các file đã được tạo đúng.
</div>
<h2>📋 Danh Sách Kiểm Tra</h2>
<ul class="checklist">
<li class="done"><strong>File index.html</strong> - File HTML chính được tạo</li>
<li class="done"><strong>File app.js</strong> - JavaScript logic được tạo</li>
<li class="done"><strong>Tailwind CSS</strong> - CDN được tải từ internet</li>
<li class="done"><strong>Material Symbols</strong> - Icons fonts được tải</li>
<li class="done"><strong>Navigation Menu</strong> - Dashboard, Applications, Accounts</li>
<li class="done"><strong>AccountManager Class</strong> - Logic chính được xây dựng</li>
<li class="done"><strong>localStorage Support</strong> - Lưu trữ dữ liệu</li>
<li class="done"><strong>Modal System</strong> - Modals cho Add/Edit tài khoản</li>
<li class="done"><strong>CRUD Operations</strong> - Create, Read, Update, Delete</li>
<li class="done"><strong>Dashboard Page</strong> - Tổng quan thống kê</li>
<li class="done"><strong>Accounts Page</strong> - Quản lý tài khoản</li>
<li class="done"><strong>Applications Page</strong> - Quản lý ứng dụng</li>
<li class="done"><strong>README.md</strong> - Tài liệu hướng dẫn</li>
<li class="done"><strong>GUIDE.html</strong> - Hướng dẫn nhanh</li>
<li class="done"><strong>SUMMARY.md</strong> - Tóm tắt dự án</li>
</ul>
<h2 style="margin-top: 30px;">🧪 Test Nhanh</h2>
<div class="status-box status-success">
<strong>Trạng Thái:</strong> Tất cả file đã được tạo và cấu hình đúng!
</div>
<h3>Trước khi bắt đầu sử dụng, hãy kiểm tra:</h3>
<ol style="line-height: 2; color: #333;">
<li>Trình duyệt hỗ trợ <code>localStorage</code> (Chrome, Firefox, Edge, Safari)</li>
<li>Có kết nối internet (để tải Tailwind CSS và Material Symbols)</li>
<li>JavaScript được bật trong trình duyệt</li>
<li>Không có Content Security Policy (CSP) chặn scripts</li>
</ol>
<h2 style="margin-top: 30px;">🚀 Hành Động Tiếp Theo</h2>
<div style="text-align: center; margin: 30px 0;">
<button onclick="window.location.href='index.html'">
▶️ Mở VaultSentinel
</button>
<button onclick="window.location.href='GUIDE.html'">
📖 Hướng Dẫn Nhanh
</button>
<button onclick="window.location.href='README.md'">
📚 Tài Liệu Chi Tiết
</button>
</div>
<h2 style="margin-top: 30px;">📋 Tính Năng Chính</h2>
<table style="width: 100%; border-collapse: collapse; margin: 20px 0;">
<tr style="background: #f9f9f9;">
<th style="border: 1px solid #ddd; padding: 10px; text-align: left;">Tính Năng</th>
<th style="border: 1px solid #ddd; padding: 10px; text-align: center;">Trạng Thái</th>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 10px;">Dashboard</td>
<td style="border: 1px solid #ddd; padding: 10px; text-align: center; color: #4caf50;"></td>
</tr>
<tr style="background: #f9f9f9;">
<td style="border: 1px solid #ddd; padding: 10px;">Quản Lý Tài Khoản</td>
<td style="border: 1px solid #ddd; padding: 10px; text-align: center; color: #4caf50;"></td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 10px;">Quản Lý Ứng Dụng</td>
<td style="border: 1px solid #ddd; padding: 10px; text-align: center; color: #4caf50;"></td>
</tr>
<tr style="background: #f9f9f9;">
<td style="border: 1px solid #ddd; padding: 10px;">Lưu Trữ Dữ Liệu</td>
<td style="border: 1px solid #ddd; padding: 10px; text-align: center; color: #4caf50;"></td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 10px;">Tìm Kiếm</td>
<td style="border: 1px solid #ddd; padding: 10px; text-align: center; color: #4caf50;"></td>
</tr>
<tr style="background: #f9f9f9;">
<td style="border: 1px solid #ddd; padding: 10px;">Responsive Design</td>
<td style="border: 1px solid #ddd; padding: 10px; text-align: center; color: #4caf50;"></td>
</tr>
</table>
<h2 style="margin-top: 30px;">📝 Thông Tin Kỹ Thuật</h2>
<div style="background: #f5f5f5; padding: 15px; border-radius: 5px; font-family: monospace; font-size: 0.9em; overflow-x: auto;">
<p><strong>Môi Trường:</strong> Web Browser (Chrome, Firefox, Edge, Safari)</p>
<p><strong>Công Nghệ:</strong> HTML5 + CSS3 (Tailwind) + JavaScript (ES6+)</p>
<p><strong>Lưu Trữ:</strong> localStorage API</p>
<p><strong>Mục Đích:</strong> Quản lý tài khoản dịch vụ/ứng dụng</p>
<p><strong>Phiên Bản:</strong> 1.0.0</p>
</div>
<div class="status-box status-success" style="margin-top: 30px;">
<strong>✨ Thành Công:</strong> Ứng dụng VaultSentinel đã được thiết lập hoàn chỉnh và sẵn sàng sử dụng!
</div>
</div>
<footer>
<p><strong>VaultSentinel v1.0.0</strong> | Ứng Dụng Quản Lý Tài Khoản Dịch Vụ</p>
<p>© 2026 - Tất cả quyền được bảo lưu | Tạo ngày: 26 Tháng 3, 2026</p>
</footer>
</div>
</body>
</html>

248
GUIDE.html Normal file
View File

@@ -0,0 +1,248 @@
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VaultSentinel - Hướng Dẫn Nhanh</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 40px 20px; }
.container { max-width: 900px; margin: 0 auto; background: white; border-radius: 10px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); overflow: hidden; }
header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 40px; text-align: center; }
header h1 { font-size: 2.5em; margin-bottom: 10px; }
header p { font-size: 1.1em; opacity: 0.9; }
main { padding: 40px; }
section { margin-bottom: 40px; }
h2 { color: #667eea; margin-bottom: 20px; font-size: 1.8em; border-bottom: 3px solid #667eea; padding-bottom: 10px; }
h3 { color: #764ba2; margin-top: 20px; margin-bottom: 10px; font-size: 1.3em; }
p, li { line-height: 1.8; color: #333; margin-bottom: 10px; font-size: 1.05em; }
ul { margin-left: 30px; }
li { margin-bottom: 10px; }
.feature-box { background: #f8f9ff; border-left: 4px solid #667eea; padding: 20px; margin: 15px 0; border-radius: 5px; }
.steps { background: #fff8f0; border-left: 4px solid #ff9800; padding: 20px; margin: 15px 0; border-radius: 5px; }
.button-demo { display: inline-block; background: #667eea; color: white; padding: 12px 20px; border-radius: 5px; text-decoration: none; margin-top: 10px; cursor: pointer; border: none; font-size: 1em; }
.button-demo:hover { background: #764ba2; }
code { background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', monospace; }
.warning { background: #fff3cd; border: 1px solid #ffc107; color: #856404; padding: 15px; border-radius: 5px; margin: 20px 0; }
footer { background: #f5f5f5; padding: 20px; text-align: center; border-top: 1px solid #ddd; color: #666; }
</style>
</head>
<body>
<div class="container">
<header>
<h1>🔐 VaultSentinel</h1>
<p>Hệ Thống Quản Lý Tài Khoản Dịch Vụ</p>
</header>
<main>
<!-- Giới Thiệu -->
<section>
<h2>📚 Giới Thiệu</h2>
<p><strong>VaultSentinel</strong> là ứng dụng web hiện đại giúp bạn quản lý tài khoản của các dịch vụ và ứng dụng một cách dễ dàng.</p>
<h3>✨ Tính Năng Chính</h3>
<div class="feature-box">
<strong>🎯 Dashboard</strong> - Xem tổng quan các thống kê chính
</div>
<div class="feature-box">
<strong>👥 Quản Lý Tài Khoản</strong> - Thêm, sửa, xóa tài khoản
</div>
<div class="feature-box">
<strong>🔧 Quản Lý Ứng Dụng</strong> - Theo dõi các dịch vụ
</div>
<div class="feature-box">
<strong>💾 Lưu Trữ Cục Bộ</strong> - Dữ liệu được lưu an toàn trên trình duyệt
</div>
</section>
<!-- Cách Bắt Đầu -->
<section>
<h2>🚀 Cách Bắt Đầu</h2>
<div class="steps">
<h3>Bước 1: Mở Ứng Dụng</h3>
<p>✓ Mở file <code>index.html</code> trên trình duyệt (Chrome, Firefox, Edge, Safari...)</p>
</div>
<div class="steps">
<h3>Bước 2: Khám Phá Giao Diện</h3>
<ul>
<li><strong>Thanh bên trái:</strong> Navigation menu (Dashboard, Applications, Accounts)</li>
<li><strong>Thanh trên:</strong> Tìm kiếm và thông báo</li>
<li><strong>Nội dung chính:</strong> Các trang quản lý khác nhau</li>
</ul>
</div>
<div class="steps">
<h3>Bước 3: Thêm Dữ Liệu Đầu Tiên</h3>
<ul>
<li>Nhấp vào "Accounts" trên menu bên trái</li>
<li>Nhấp nút "Add Account"</li>
<li>Điền thông tin tài khoản</li>
<li>Nhấp "Save Account"</li>
</ul>
</div>
</section>
<!-- Hướng Dẫn Chi Tiết -->
<section>
<h2>📖 Hướng Dẫn Chi Tiết</h2>
<h3>1⃣ Dashboard</h3>
<p>Trang mặc định khi mở ứng dụng. Hiển thị:</p>
<ul>
<li>Số lượng ứng dụng đang quản lý</li>
<li>Tổng số tài khoản</li>
<li>Ngày cập nhật cuối cùng</li>
<li>Trạng thái hệ thống</li>
<li>Danh sách tài khoản gần đây</li>
</ul>
<h3>2⃣ Quản Lý Tài Khoản (Accounts)</h3>
<h4>✏️ Thêm Tài Khoản Mới</h4>
<div class="steps">
<ol>
<li>Nhấp "Accounts" → "Add Account"</li>
<li>Chọn dịch vụ từ dropdown (AWS, GitHub, Google Workspace...)</li>
<li>Nhập tên chủ sở hữu (Owner Name)</li>
<li>Nhập tên đăng nhập (Username)</li>
<li>Nhập mật khẩu (Password)</li>
<li>Nhấp "Save Account"</li>
</ol>
</div>
<h4>🔄 Chỉnh Sửa Tài Khoản</h4>
<div class="steps">
<ol>
<li>Tìm tài khoản cần sửa trong danh sách</li>
<li>Nhấp nút "Edit" (biểu tượng bút chì)</li>
<li>Cập nhật thông tin cần thiết</li>
<li>Nhấp "Save Account"</li>
</ol>
</div>
<h4>🗑️ Xóa Tài Khoản</h4>
<div class="steps">
<ol>
<li>Nhấp nút "Delete" (biểu tượng thùng rác)</li>
<li>Xác nhận khi được yêu cầu</li>
<li>Tài khoản sẽ bị xóa vĩnh viễn</li>
</ol>
</div>
<h3>3⃣ Quản Lý Ứng Dụng (Applications)</h3>
<h4> Thêm Ứng Dụng Mới</h4>
<div class="steps">
<ol>
<li>Nhấp "Applications" → "Add New"</li>
<li>Nhập tên ứng dụng</li>
<li>Chọn loại (Cloud, VCS, Infra...)</li>
<li>Chọn trạng thái (Online/Offline)</li>
<li>Nhấp "Save Application"</li>
</ol>
</div>
<h4>📊 Xem Thông Tin</h4>
<ul>
<li>Danh sách tất cả ứng dụng được đăng ký</li>
<li>Trạng thái hoạt động (Online/Offline)</li>
<li>Loại ứng dụng</li>
<li>Thống kê về số lượng hoạt động và tình trạng sức khỏe</li>
</ul>
</section>
<!-- Tips & Tricks -->
<section>
<h2>💡 Mẹo & Thủ Thuật</h2>
<div class="feature-box">
<strong>Tìm Kiếm Nhanh:</strong> Sử dụng ô tìm kiếm ở thanh trên cùng
</div>
<div class="feature-box">
<strong>Lọc Tài Khoản:</strong> Sử dụng dropdown "Service" để lọc theo dịch vụ
</div>
<div class="feature-box">
<strong>Dữ Liệu Được Lưu Tự Động:</strong> Không cần bấm nút lưu thêm, dữ liệu được lưu ngay lập tức
</div>
<div class="feature-box">
<strong>Khôi Phục Dữ Liệu:</strong> Dữ liệu được lưu trong localStorage, sẽ mất nếu bạn xóa cache trình duyệt
</div>
</section>
<!-- Bảo Mật -->
<section>
<h2>🔒 Bảo Mật</h2>
<div class="warning">
<strong>⚠️ Lưu Ý Quan Trọng:</strong>
<ul>
<li>Dữ liệu được lưu cục bộ trên trình duyệt, KHÔNG gửi đến máy chủ</li>
<li>Chỉ sử dụng trên máy tính cá nhân được tin cậy</li>
<li>Không sử dụng cho dữ liệu nhạy cảm trong môi trường thực tế</li>
<li>Xóa cache trình duyệt sẽ mất tất cả dữ liệu</li>
<li>Bạn nên sao lưu dữ liệu quan trọng</li>
</ul>
</div>
</section>
<!-- Các File -->
<section>
<h2>📁 Cấu Trúc File</h2>
<div class="feature-box">
<strong>index.html</strong> - File HTML chính (mở file này để chạy ứng dụng)
</div>
<div class="feature-box">
<strong>app.js</strong> - Mã JavaScript xử lý logic ứng dụng
</div>
<div class="feature-box">
<strong>README.md</strong> - Tài liệu hướng dẫn chi tiết
</div>
<div class="feature-box">
<strong>acc.html, app.html</strong> - Các file gốc (tham khảo)
</div>
</section>
<!-- Câu Hỏi Thường Gặp -->
<section>
<h2>❓ Câu Hỏi Thường Gặp</h2>
<h3>Q: Dữ liệu của tôi được lưu ở đâu?</h3>
<p>A: Dữ liệu được lưu trong localStorage của trình duyệt. Mỗi trình duyệt/máy tính có storage riêng.</p>
<h3>Q: Tôi có thể sử dụng ứng dụng này trên điện thoại không?</h3>
<p>A: Có! Ứng dụng được thiết kế responsive và hoạt động trên mobile, tablet, desktop.</p>
<h3>Q: Làm thế nào để sao lưu dữ liệu?</h3>
<p>A: Hiện tại chưa có tính năng xuất/nhập. Bạn có thể viết script hoặc chờ phiên bản nâng cao.</p>
<h3>Q: Dữ liệu có thể được khôi phục không?</h3>
<p>A: Nếu bạn xóa cache trình duyệt, dữ liệu sẽ bị xóa vĩnh viễn. Hãy sao lưu quan trọng đầu tiên.</p>
<h3>Q: Ứng dụng có hoạt động offline không?</h3>
<p>A: Có! Ứng dụng hoàn toàn hoạt động offline vì không cần kết nối internet.</p>
</section>
<!-- Hỗ Trợ -->
<section>
<h2>📞 Hỗ Trợ & Liên Hệ</h2>
<p>Nếu bạn gặp vấn đề:</p>
<ul>
<li>Kiểm tra console trình duyệt (F12) để xem lỗi</li>
<li>Thử xóa cache và reload trang</li>
<li>Thử trên trình duyệt khác</li>
<li>Đọc file README.md để biết thêm chi tiết</li>
</ul>
</section>
<!-- Button Bắt Đầu -->
<div style="text-align: center; margin-top: 40px;">
<a href="index.html" class="button-demo" style="font-size: 1.2em; padding: 15px 40px;">
🚀 Mở Ứng Dụng VaultSentinel
</a>
</div>
</main>
<footer>
<p><strong>VaultSentinel v1.0.0</strong> | Ứng Dụng Quản Lý Tài Khoản Dịch Vụ</p>
<p style="margin-top: 10px; font-size: 0.9em;">© 2026 - Tất cả quyền được bảo lưu</p>
</footer>
</div>
</body>
</html>

137
README.md Normal file
View File

@@ -0,0 +1,137 @@
# VaultSentinel - Account Management System
## Giới Thiệu
VaultSentinel là một ứng dụng web quản lý tài khoản dịch vụ/ứng dụng. Ứng dụng cho phép bạn:
- **Quản lý Tài Khoản**: Thêm, sửa, xóa tài khoản cho các dịch vụ
- **Quản lý Ứng Dụng**: Theo dõi các dịch vụ/ứng dụng đang sử dụng
- **Dashboard**: Xem tổng quan số lượng tài khoản và dịch vụ
- **Lưu Trữ Dữ Liệu**: Tất cả dữ liệu được lưu trên trình duyệt (localStorage)
## Các Tính Năng
### 1. Dashboard
- Xem tổng quan các thống kê:
- Số lượng ứng dụng đang hoạt động
- Tổng số tài khoản được quản lý
- Ngày cập nhật cuối cùng
- Trạng thái hệ thống
- Xem các tài khoản gần đây được tạo
### 2. Quản Lý Tài Khoản (Accounts)
- **Thêm Tài Khoản Mới**:
- Nhấp nút "Add Account"
- Điền thông tin: Service, Owner, Username, Password
- Nhấp "Save Account"
- **Chỉnh Sửa Tài Khoản**:
- Nhấp nút "Edit" (biểu tượng bút chì) trên dòng tài khoản
- Cập nhật thông tin
- Nhấp "Save Account"
- **Xóa Tài Khoản**:
- Nhấp nút "Delete" (biểu tượng thùng rác)
- Xác nhận xóa
- **Lọc Tài Khoản**:
- Sử dụng dropdown "Service" để lọc theo dịch vụ
### 3. Quản Lý Ứng Dụng (Applications)
- **Xem Danh Sách**:
- Danh sách tất cả các ứng dụng/dịch vụ
- Hiển thị trạng thái (Online/Offline)
- Hiển thị loại ứng dụng
- **Thêm Ứng Dụng**:
- Nhấp "Add New"
- Điền tên, loại, trạng thái
- Nhấp "Save Application"
- **Chỉnh Sửa Ứng Dụng**:
- Nhấp nút "Edit"
- Cập nhật thông tin
- Nhấp "Save Application"
- **Xóa Ứng Dụng**:
- Nhấp nút "Delete"
- Xác nhận xóa
## Cách Sử Dụng
### Khởi Động Ứng Dụng
1. Mở file `index.html` trên trình duyệt
2. Giao diện chính sẽ tải lên
### Công Việc Thường Xuyên
#### Thêm Tài Khoản Mới
1. Nhấp "Accounts" trên thanh điều hướng bên trái
2. Nhấp nút "Add Account"
3. Chọn dịch vụ từ dropdown
4. Nhập tên chủ sở hữu, tên đăng nhập, mật khẩu
5. Nhấp "Save Account"
#### Xem Danh Sách Ứng Dụng
1. Nhấp "Applications" trên thanh điều hướng
2. Xem danh sách tất cả các ứng dụng đã đăng ký
3. Sử dụng các nút hành động để chỉnh sửa hoặc xóa
#### Kiểm Tra Tổng Quan
1. Nhấp "Dashboard" trên thanh điều hướng
2. Xem các thống kê chính và hoạt động gần đây
## Lưu Ý Quan Trọng
⚠️ **Bảo Mật**:
- Tất cả dữ liệu được lưu trữ cục bộ trên trình duyệt (localStorage)
- Không gửi dữ liệu đến máy chủ nào
- Dữ liệu sẽ bị xóa nếu bạn xóa cache trình duyệt
- Đây là một ứng dụng demo - không sử dụng cho dữ liệu nhạy cảm trong môi trường thực tế
## Cấu Trúc File
- `index.html` - File HTML chính với giao diện người dùng
- `app.js` - Mã JavaScript xử lý logic và chức năng
- `acc.html` - Trang tài khoản cũ (tham khảo)
- `app.html` - Trang ứng dụng cũ (tham khảo)
- `main.html` - File HTML thay thế (tham khảo)
## Các Công Nghệ Sử Dụng
- **HTML5**: Cấu trúc trang
- **CSS (Tailwind CSS)**: Tạo cách bố trí và kiểu dáng
- **JavaScript**: Xử lý logic ứng dụng
- **Material Symbols**: Biểu tượng
- **localStorage API**: Lưu trữ dữ liệu
## Mở Rộng Ứng Dụng
### Để Thêm Chức Năng Mới:
1. **Thêm trang Dashboard mới**:
- Xác định nội dung trong `getApplicationsContent()`
- Thêm hàm xử lý sự kiện
- Cập nhật `renderPage()` để thêm trang mới
2. **Cải Thiện Bảo Mật**:
- Thêm mã hóa cho mật khẩu
- Triển khai xác thực người dùng
- Sử dụng database backend
3. **Tính Năng Khác**:
- Xuất/Nhập dữ liệu
- Nhật ký hoạt động
- Tìm kiếm nâng cao
- Sao lưu/Khôi phục
## Hỗ Trợ
Nếu bạn gặp vấn đề:
1. Kiểm tra console trình duyệt (F12) để xem lỗi
2. Xóa cache trình duyệt nếu dữ liệu không hiển thị
3. Đảm bảo trình duyệt hỗ trợ localStorage
4. Thử trên trình duyệt khác
---
**Phiên Bản**: 1.0.0

585
accounts.html Normal file
View File

@@ -0,0 +1,585 @@
<!DOCTYPE html>
<html class="light" lang="en">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>VaultSentinel - Accounts Management</title>
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"/>
<!-- Material Symbols -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"on-secondary-fixed-variant": "#4e5c71",
"on-secondary": "#f8f8ff",
"secondary-fixed-dim": "#c7d5ed",
"surface-variant": "#d9e4ea",
"surface-tint": "#3755c3",
"primary-container": "#dde1ff",
"primary-dim": "#2848b7",
"on-background": "#2a3439",
"surface-container-lowest": "#ffffff",
"tertiary-fixed-dim": "#d4cdee",
"on-tertiary-container": "#514d68",
"error-container": "#fe8983",
"on-secondary-container": "#455367",
"outline": "#717c82",
"on-primary": "#f8f7ff",
"on-primary-container": "#2747b6",
"inverse-primary": "#6d89fa",
"on-surface": "#2a3439",
"primary-fixed": "#dde1ff",
"on-primary-fixed": "#0732a3",
"secondary-dim": "#465468",
"surface-container-high": "#e1e9ee",
"surface-container-highest": "#d9e4ea",
"on-primary-fixed-variant": "#3352c0",
"on-error-container": "#752121",
"secondary": "#526074",
"tertiary-fixed": "#e3dbfd",
"primary": "#3755c3",
"surface-dim": "#cfdce3",
"tertiary": "#605c78",
"on-error": "#fff7f6",
"secondary-fixed": "#d5e3fc",
"error-dim": "#4e0309",
"surface-bright": "#f7f9fb",
"on-surface-variant": "#566166",
"on-tertiary": "#fcf7ff",
"tertiary-container": "#e3dbfd",
"inverse-on-surface": "#9a9d9f",
"on-tertiary-fixed-variant": "#5b5672",
"tertiary-dim": "#54506b",
"outline-variant": "#a9b4b9",
"on-secondary-fixed": "#324053",
"inverse-surface": "#0b0f10",
"on-tertiary-fixed": "#3e3a54",
"primary-fixed-dim": "#cad2ff",
"surface-container": "#e8eff3",
"secondary-container": "#d5e3fc",
"surface-container-low": "#f0f4f7",
"background": "#f7f9fb",
"error": "#9f403d",
"surface": "#f7f9fb"
},
fontFamily: {
"headline": ["Manrope"],
"body": ["Inter"],
"label": ["Inter"]
},
borderRadius: {"DEFAULT": "0.125rem", "lg": "0.25rem", "xl": "0.5rem", "full": "0.75rem"},
},
},
}
</script>
<style>
.material-symbols-outlined {
font-family: 'Material Symbols Outlined';
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;
font-size: 1.25rem;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-flex;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
}
body { font-family: 'Inter', sans-serif; overflow: hidden; height: 100vh; }
h1, h2, h3 { font-family: 'Manrope', sans-serif; }
.modal-backdrop {
opacity: 0;
transition: opacity 0.2s ease-in-out;
pointer-events: none;
display: none;
}
.modal-backdrop.open {
display: flex !important;
opacity: 1;
pointer-events: auto;
}
</style>
</head>
<body class="bg-background text-on-surface antialiased flex h-screen w-screen">
<!-- SideNavBar (shared with all pages) -->
<aside class="h-screen w-56 flex flex-col bg-slate-100 dark:bg-slate-900 font-manrope text-sm font-medium border-r border-outline-variant/10 shrink-0">
<div class="flex flex-col h-full py-6">
<!-- Header -->
<div class="px-6 mb-8">
<div class="text-lg font-black text-slate-900 dark:text-slate-50 tracking-tight leading-none">VaultSentinel</div>
<div class="text-[10px] uppercase tracking-widest text-on-surface-variant mt-1.5 font-bold">Admin Console</div>
</div>
<!-- Primary Nav -->
<nav class="flex-1 px-3 space-y-1">
<a href="index.html" class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50 transition-all group cursor-pointer">
<span class="material-symbols-outlined">dashboard</span>
<span>Dashboard</span>
</a>
<a href="applications.html" class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50 transition-all group cursor-pointer">
<span class="material-symbols-outlined">apps</span>
<span>Applications</span>
</a>
<a href="accounts.html" class="flex items-center gap-3 px-3 py-2 border-l-4 border-blue-600 bg-slate-200/80 dark:bg-slate-800 text-slate-900 dark:text-slate-50 font-bold transition-all group cursor-pointer">
<span class="material-symbols-outlined">manage_accounts</span>
<span>Accounts</span>
</a>
</nav>
<!-- Footer -->
<div class="px-6 pt-4 border-t border-outline-variant/10">
<div class="text-[10px] font-bold text-on-surface-variant/40 uppercase tracking-widest">v1.0.0</div>
</div>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 flex flex-col h-screen min-w-0">
<!-- TopAppBar -->
<header class="h-14 flex items-center justify-between px-6 bg-slate-50/80 dark:bg-slate-950/80 backdrop-blur-xl border-b border-outline-variant/10 shrink-0">
<div class="flex items-center gap-4 flex-1">
<div class="flex items-center bg-surface-container-high px-3 py-1.5 rounded-full w-64 group focus-within:ring-2 ring-primary/20 transition-all">
<span class="material-symbols-outlined text-on-surface-variant text-base">search</span>
<input id="searchInput" class="bg-transparent border-none focus:ring-0 text-xs w-full placeholder:text-on-surface-variant/60 py-0" placeholder="Search resources..." type="text"/>
</div>
</div>
<div class="flex items-center gap-3">
<button class="p-1.5 rounded-full text-slate-500 hover:bg-slate-200 transition-colors">
<span class="material-symbols-outlined">notifications</span>
</button>
<button class="p-1.5 rounded-full text-slate-500 hover:bg-slate-200 transition-colors">
<span class="material-symbols-outlined">settings</span>
</button>
</div>
</header>
<!-- Content Area -->
<div class="flex-1 flex flex-col p-6 space-y-6 min-h-0 overflow-auto">
<div class="flex items-center justify-between gap-6 mb-6 shrink-0">
<div>
<h2 class="text-2xl font-extrabold text-on-surface tracking-tight">Accounts Management</h2>
<p class="text-sm text-on-surface-variant">Administrative Access Control</p>
</div>
<button id="addAccountBtn" class="bg-primary hover:bg-primary-dim text-on-primary px-3 py-1.5 rounded-lg text-xs font-bold shadow-sm flex items-center gap-1.5 transition-all active:scale-95">
<span class="material-symbols-outlined text-base">person_add</span>
Add Account
</button>
</div>
<!-- Filters -->
<div class="flex items-center gap-3 mb-4">
<div class="flex items-center gap-1.5">
<span class="text-[10px] font-bold uppercase text-on-surface-variant">Service</span>
<select id="filterService" class="bg-surface-container-low border border-slate-200 rounded-md text-[11px] py-1 px-2 pr-6 focus:ring-1 focus:ring-primary shadow-sm">
<option value="">All Services</option>
</select>
</div>
<div class="flex-1"></div>
</div>
<!-- Accounts Table -->
<div class="flex-1 bg-white rounded-xl border border-slate-200 shadow-sm flex flex-col overflow-hidden min-h-0">
<div class="overflow-y-auto overflow-x-auto flex-1">
<table class="w-full text-left border-collapse">
<thead class="sticky top-0 z-10">
<tr class="bg-slate-50 border-b border-slate-200">
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">User Owner</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Account Username</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Service</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500 text-right">Actions</th>
</tr>
</thead>
<tbody id="accountList" class="divide-y divide-slate-100">
<!-- Accounts will be inserted here -->
</tbody>
</table>
</div>
</div>
</div> <!-- End Content Area -->
</main>
<!-- Add/Edit Account Modal -->
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-on-surface/30 backdrop-blur-[2px] hidden" id="accountModal">
<div class="w-full max-w-[640px] bg-surface-container-lowest rounded-xl shadow-[0px_12px_32px_rgba(42,52,57,0.12)] border-none overflow-hidden m-4">
<!-- Modal Header -->
<div class="px-8 py-6 border-b border-surface-container flex justify-between items-start">
<div>
<span class="text-[0.625rem] font-bold uppercase tracking-[0.1em] text-on-surface-variant block mb-1">Account Management</span>
<h2 class="text-2xl font-extrabold text-on-surface tracking-tight" id="accountModalTitle">Add New Account</h2>
</div>
<button class="w-10 h-10 flex items-center justify-center rounded-full hover:bg-surface-container transition-colors text-on-surface-variant" onclick="closeModal()">
<span class="material-symbols-outlined">close</span>
</button>
</div>
<!-- Modal Content -->
<form id="accountForm" class="p-8 space-y-6">
<div>
<label class="text-[0.65rem] font-bold uppercase tracking-widest text-on-surface-variant ml-1 block mb-2">Service</label>
<div class="flex items-center h-12 px-4 bg-surface-container-highest rounded-lg border border-transparent focus-within:border-primary/40 focus-within:shadow-[0_0_0_2px_rgba(55,85,195,0.05)] transition-all">
<span class="material-symbols-outlined text-on-surface-variant mr-3 text-sm">cloud</span>
<select id="accountService" class="flex-1 bg-transparent border-none focus:ring-0 text-sm font-medium text-on-surface p-0" required>
<option value="">Select a service</option>
</select>
</div>
</div>
<div>
<label class="text-[0.65rem] font-bold uppercase tracking-widest text-on-surface-variant ml-1 block mb-2">Owner Name</label>
<div class="flex items-center h-12 px-4 bg-surface-container-highest rounded-lg border border-transparent focus-within:border-primary/40 focus-within:shadow-[0_0_0_2px_rgba(55,85,195,0.05)] transition-all">
<span class="material-symbols-outlined text-on-surface-variant mr-3 text-sm">person</span>
<input type="text" id="accountOwner" class="flex-1 bg-transparent border-none focus:ring-0 text-sm font-medium text-on-surface placeholder:text-on-surface-variant/60 p-0" required placeholder="John Doe">
</div>
</div>
<div>
<label class="text-[0.65rem] font-bold uppercase tracking-widest text-on-surface-variant ml-1 block mb-2">Username</label>
<div class="flex items-center h-12 px-4 bg-surface-container-highest rounded-lg border border-transparent focus-within:border-primary/40 focus-within:shadow-[0_0_0_2px_rgba(55,85,195,0.05)] transition-all">
<span class="material-symbols-outlined text-on-surface-variant mr-3 text-sm">alternate_email</span>
<input type="text" id="accountUsername" class="flex-1 bg-transparent border-none focus:ring-0 text-sm font-medium text-on-surface placeholder:text-on-surface-variant/60 p-0" required placeholder="username">
</div>
</div>
<div>
<label class="text-[0.65rem] font-bold uppercase tracking-widest text-on-surface-variant ml-1 block mb-2">Password</label>
<div class="flex items-center h-12 px-4 bg-surface-container-highest rounded-lg border border-transparent focus-within:border-primary/40 focus-within:shadow-[0_0_0_2px_rgba(55,85,195,0.05)] transition-all">
<span class="material-symbols-outlined text-on-surface-variant mr-3 text-sm">lock</span>
<input type="password" id="accountPassword" class="flex-1 bg-transparent border-none focus:ring-0 text-sm font-medium text-on-surface placeholder:text-on-surface-variant/60 p-0" required placeholder="••••••••">
</div>
</div>
</form>
<!-- Modal Footer -->
<div class="px-8 py-6 bg-surface-container-low flex items-center justify-end gap-3">
<button class="px-5 h-11 text-sm font-bold text-on-surface-variant hover:bg-surface-container transition-all rounded-lg" onclick="closeModal()">
Close
</button>
<button onclick="document.getElementById('accountForm').dispatchEvent(new Event('submit'))" class="px-6 h-11 text-sm font-bold text-on-primary bg-gradient-to-br from-primary to-primary-dim rounded-lg shadow-sm hover:opacity-90 active:scale-[0.98] transition-all flex items-center gap-2">
<span class="material-symbols-outlined text-base">save</span>
Save Account
</button>
</div>
</div>
</div>
<!-- View Account Details Modal -->
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-on-surface/30 backdrop-blur-[2px] hidden" id="viewAccountModal">
<div class="w-full max-w-[640px] bg-surface-container-lowest rounded-xl shadow-[0px_12px_32px_rgba(42,52,57,0.12)] border-none overflow-hidden m-4">
<!-- Modal Header -->
<div class="px-8 py-6 border-b border-surface-container flex justify-between items-start">
<div>
<span class="text-[0.625rem] font-bold uppercase tracking-[0.1em] text-on-surface-variant block mb-1">Account Details</span>
<h2 class="text-2xl font-extrabold text-on-surface tracking-tight" id="viewAccountName">Account Owner</h2>
</div>
<button class="w-10 h-10 flex items-center justify-center rounded-full hover:bg-surface-container transition-colors text-on-surface-variant" onclick="closeViewAccountModal()">
<span class="material-symbols-outlined">close</span>
</button>
</div>
<!-- Modal Content -->
<div class="p-8 space-y-6">
<!-- Service Info Section -->
<div class="flex items-center gap-5 p-5 bg-surface-container-low rounded-xl">
<div class="w-14 h-14 bg-white rounded-xl shadow-sm flex items-center justify-center">
<span class="material-symbols-outlined text-primary text-3xl">cloud</span>
</div>
<div class="flex-1">
<div class="text-xs font-bold text-on-surface-variant uppercase tracking-wider mb-0.5">Service</div>
<div class="text-xl font-bold text-on-surface" id="viewAccountService">-</div>
</div>
</div>
<!-- Details Grid -->
<div class="space-y-4">
<div>
<label class="text-[0.65rem] font-bold uppercase tracking-widest text-on-surface-variant block mb-2">Username</label>
<div class="flex items-center h-12 px-4 bg-surface-container-highest rounded-lg">
<span class="material-symbols-outlined text-on-surface-variant mr-3 text-sm">alternate_email</span>
<span class="text-sm font-medium text-on-surface" id="viewAccountUsername">-</span>
</div>
</div>
<div>
<label class="text-[0.65rem] font-bold uppercase tracking-widest text-on-surface-variant block mb-2">Password</label>
<div class="flex items-center justify-between h-12 px-4 bg-surface-container-highest rounded-lg">
<div class="flex items-center flex-1">
<span class="material-symbols-outlined text-on-surface-variant mr-3 text-sm">lock</span>
<span class="text-sm font-medium text-on-surface" id="viewAccountPassword">••••••••</span>
</div>
<button class="p-1 text-on-surface-variant hover:text-on-surface transition-colors" onclick="togglePasswordVisibility()">
<span class="material-symbols-outlined text-sm" id="passwordToggleIcon">visibility</span>
</button>
</div>
</div>
</div>
</div>
<!-- Modal Footer -->
<div class="px-8 py-6 bg-surface-container-low flex items-center justify-end gap-3">
<button class="px-5 h-11 text-sm font-bold text-on-surface-variant hover:bg-surface-container transition-all rounded-lg" onclick="closeViewAccountModal()">
Close
</button>
<button onclick="editCurrentAccount()" class="px-6 h-11 text-sm font-bold text-on-primary bg-gradient-to-br from-primary to-primary-dim rounded-lg shadow-sm hover:opacity-90 active:scale-[0.98] transition-all flex items-center gap-2">
<span class="material-symbols-outlined text-base">edit</span>
Edit Account
</button>
</div>
</div>
</div>
<!-- Delete Confirm Modal -->
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-on-surface/30 backdrop-blur-[2px] hidden" id="deleteConfirmModal">
<div class="w-full max-w-[480px] bg-surface-container-lowest rounded-xl shadow-[0px_12px_32px_rgba(42,52,57,0.12)] border-none overflow-hidden m-4">
<!-- Modal Header -->
<div class="px-8 py-6 border-b border-surface-container bg-error/5">
<h3 class="text-base font-extrabold text-error flex items-center gap-2">
<span class="material-symbols-outlined">warning</span>
Delete Account
</h3>
</div>
<!-- Modal Content -->
<div class="px-8 py-6">
<p class="text-sm text-on-surface-variant">Are you sure you want to delete <strong id="deleteAccountUsername" class="text-on-surface"></strong>? This action cannot be undone.</p>
</div>
<!-- Modal Footer -->
<div class="px-8 py-6 bg-surface-container-low flex items-center justify-end gap-3">
<button class="px-5 h-11 text-sm font-bold text-on-surface-variant hover:bg-surface-container transition-all rounded-lg" onclick="closeDeleteModal()">
Cancel
</button>
<button onclick="confirmDelete()" class="px-6 h-11 text-sm font-bold text-on-error bg-gradient-to-br from-error to-error-dim rounded-lg shadow-sm hover:opacity-90 active:scale-[0.98] transition-all flex items-center gap-2">
<span class="material-symbols-outlined text-base">delete</span>
Delete
</button>
</div>
</div>
</div>
<script>
class AccountManager {
constructor() {
this.accounts = this.loadAccounts() || [];
this.applications = this.loadApplications() || [
{ name: 'AWS' },
{ name: 'GitHub' },
{ name: 'Google Workspace' },
{ name: 'Nginx Proxy' },
];
this.init();
}
init() {
this.setupSelects();
this.render();
this.setupEventListeners();
}
setupSelects() {
const serviceSelect = document.getElementById('accountService');
const filterSelect = document.getElementById('filterService');
this.applications.forEach(app => {
const option = new Option(app.name, app.name);
serviceSelect.add(option);
filterSelect.add(new Option(app.name, app.name));
});
}
setupEventListeners() {
document.getElementById('addAccountBtn').addEventListener('click', () => this.openModal());
document.getElementById('accountForm').addEventListener('submit', (e) => this.handleSubmit(e));
document.getElementById('filterService').addEventListener('change', (e) => this.filterByService(e.target.value));
}
render() {
this.renderTable(this.accounts);
this.setupActionButtons();
}
renderTable(accounts) {
const tbody = document.getElementById('accountList');
if (accounts.length === 0) {
tbody.innerHTML = `<tr><td colspan="4" class="px-4 py-8 text-center text-slate-500">No accounts yet. Create one to get started.</td></tr>`;
return;
}
tbody.innerHTML = accounts.map((acc, idx) => `
<tr class="hover:bg-slate-50/80 transition-colors group">
<td class="px-4 py-3 text-sm font-medium text-slate-900">${acc.owner}</td>
<td class="px-4 py-3 text-sm text-slate-600">${acc.username}</td>
<td class="px-4 py-3 text-sm">
<span class="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs font-semibold">${acc.service}</span>
</td>
<td class="px-4 py-3 text-right">
<button class="p-1.5 text-on-surface-variant hover:text-on-surface transition-colors view-account" data-idx="${idx}" title="View Details">
<span class="material-symbols-outlined text-lg">info</span>
</button>
<button class="p-1.5 text-slate-400 hover:text-primary transition-colors edit-account" data-idx="${idx}">
<span class="material-symbols-outlined text-lg">edit</span>
</button>
<button class="p-1.5 text-slate-400 hover:text-error transition-colors delete-account" data-idx="${idx}">
<span class="material-symbols-outlined text-lg">delete</span>
</button>
</td>
</tr>
`).join('');
}
setupActionButtons() {
document.querySelectorAll('.view-account').forEach(btn => {
btn.addEventListener('click', () => {
const idx = btn.dataset.idx;
const acc = this.accounts[idx];
window.currentViewAccount = acc;
window.currentViewAccountIdx = idx;
window.currentAccountPasswordVisible = false;
document.getElementById('viewAccountName').textContent = acc.owner;
document.getElementById('viewAccountService').textContent = acc.service;
document.getElementById('viewAccountUsername').textContent = acc.username;
document.getElementById('viewAccountPassword').textContent = '••••••••';
document.getElementById('viewAccountPassword').dataset.visible = 'false';
document.getElementById('passwordToggleIcon').textContent = 'visibility';
document.getElementById('viewAccountModal').classList.add('open');
});
});
document.querySelectorAll('.delete-account').forEach(btn => {
btn.addEventListener('click', () => {
const idx = btn.dataset.idx;
const acc = this.accounts[idx];
window.pendingDeleteIdx = idx;
document.getElementById('deleteAccountUsername').textContent = acc.username;
document.getElementById('deleteConfirmModal').classList.add('open');
});
});
document.querySelectorAll('.edit-account').forEach(btn => {
btn.addEventListener('click', () => {
const idx = btn.dataset.idx;
const acc = this.accounts[idx];
document.getElementById('accountService').value = acc.service;
document.getElementById('accountOwner').value = acc.owner;
document.getElementById('accountUsername').value = acc.username;
document.getElementById('accountPassword').value = acc.password;
document.getElementById('accountModalTitle').textContent = 'Edit Account';
this.editingIdx = idx;
this.openModal();
});
});
}
filterByService(service) {
const filtered = service ? this.accounts.filter(a => a.service === service) : this.accounts;
this.renderTable(filtered);
this.setupActionButtons();
}
handleSubmit(e) {
e.preventDefault();
const account = {
service: document.getElementById('accountService').value,
owner: document.getElementById('accountOwner').value,
username: document.getElementById('accountUsername').value,
password: document.getElementById('accountPassword').value,
dateCreated: new Date().toLocaleDateString()
};
if (this.editingIdx !== undefined) {
this.accounts[this.editingIdx] = account;
this.editingIdx = undefined;
} else {
this.accounts.push(account);
}
this.saveAccounts();
this.closeModal();
this.render();
}
openModal() {
if (this.editingIdx === undefined) {
document.getElementById('accountForm').reset();
document.getElementById('accountModalTitle').textContent = 'Add New Account';
}
document.getElementById('accountModal').classList.add('open');
}
closeModal() {
document.getElementById('accountModal').classList.remove('open');
this.editingIdx = undefined;
}
loadAccounts() {
return JSON.parse(localStorage.getItem('accounts'));
}
loadApplications() {
return JSON.parse(localStorage.getItem('applications'));
}
saveAccounts() {
localStorage.setItem('accounts', JSON.stringify(this.accounts));
}
}
function closeModal() {
document.getElementById('accountModal').classList.remove('open');
}
function closeDeleteModal() {
document.getElementById('deleteConfirmModal').classList.remove('open');
window.pendingDeleteIdx = null;
}
function confirmDelete() {
const idx = window.pendingDeleteIdx;
if (idx !== null && idx !== undefined) {
accountManager.accounts.splice(idx, 1);
accountManager.saveAccounts();
accountManager.render();
closeDeleteModal();
}
}
function closeViewAccountModal() {
document.getElementById('viewAccountModal').classList.remove('open');
window.currentAccountPasswordVisible = false;
}
function togglePasswordVisibility() {
const passwordEl = document.getElementById('viewAccountPassword');
const toggleIcon = document.getElementById('passwordToggleIcon');
window.currentAccountPasswordVisible = !window.currentAccountPasswordVisible;
if (window.currentAccountPasswordVisible) {
passwordEl.textContent = window.currentViewAccount.password;
toggleIcon.textContent = 'visibility_off';
} else {
passwordEl.textContent = '••••••••';
toggleIcon.textContent = 'visibility';
}
}
function editCurrentAccount() {
const idx = window.currentViewAccountIdx;
const acc = window.currentViewAccount;
closeViewAccountModal();
document.getElementById('accountService').value = acc.service;
document.getElementById('accountOwner').value = acc.owner;
document.getElementById('accountUsername').value = acc.username;
document.getElementById('accountPassword').value = acc.password;
document.getElementById('accountModalTitle').textContent = 'Edit Account';
accountManager.editingIdx = idx;
accountManager.openModal();
}
let accountManager;
document.addEventListener('DOMContentLoaded', () => {
accountManager = new AccountManager();
});
</script>
</body>
</html>

511
app.js Normal file
View File

@@ -0,0 +1,511 @@
// VaultSentinel - Account Management Application
// Main JavaScript functionality
class AccountManager {
constructor() {
this.accounts = this.loadFromStorage('accounts') || [];
this.applications = this.loadFromStorage('applications') || [
{ id: 1, name: 'AWS', type: 'Cloud', status: 'online', icon: 'cloud' },
{ id: 2, name: 'GitHub', type: 'VCS', status: 'online', icon: 'code' },
{ id: 3, name: 'Google Workspace', type: 'Collaboration', status: 'online', icon: 'mail' },
{ id: 4, name: 'Nginx Proxy', type: 'Infra', status: 'offline', icon: 'dns' },
];
this.currentPage = 'dashboard';
this.init();
}
init() {
this.setupEventListeners();
// Render dashboard content
const mainContent = document.getElementById('mainContent');
if (mainContent) {
mainContent.innerHTML = this.renderDashboard();
}
}
setupEventListeners() {
// Account modal
const addAccountBtn = document.getElementById('addAccountBtn');
if (addAccountBtn) {
addAccountBtn.addEventListener('click', () => this.openAccountModal());
}
// Application modal
const addAppBtn = document.getElementById('addAppBtn');
if (addAppBtn) {
addAppBtn.addEventListener('click', () => this.openAppModal());
}
// Modal close buttons
document.querySelectorAll('[data-close-modal]').forEach(btn => {
btn.addEventListener('click', () => this.closeModals());
});
// Form submissions
const accountForm = document.getElementById('accountForm');
if (accountForm) {
accountForm.addEventListener('submit', (e) => this.handleAccountSubmit(e));
}
const appForm = document.getElementById('appForm');
if (appForm) {
appForm.addEventListener('submit', (e) => this.handleAppSubmit(e));
}
// Account table row clicks
this.setupAccountRowListeners();
}
renderDashboard() {
return `
<div class="flex-1 flex flex-col p-6 space-y-6 min-h-0 overflow-auto">
<!-- Title and Stats -->
<div class="flex items-end justify-between shrink-0">
<div>
<h1 class="text-2xl font-extrabold text-on-surface tracking-tight leading-none">System Overview</h1>
<p class="text-xs text-on-surface-variant font-medium mt-1">Account & Service Management</p>
</div>
<div class="flex gap-2">
<a href="accounts.html" class="py-1.5 px-3 bg-primary text-on-primary rounded-lg text-xs font-bold flex items-center gap-1.5 shadow-sm active:scale-95 duration-100">
<span class="material-symbols-outlined text-sm">add</span>
Add Account
</a>
</div>
</div>
<!-- Metric Grid -->
<div class="grid grid-cols-4 gap-4 shrink-0">
<div class="bg-surface-container-lowest p-4 rounded-xl border border-outline-variant/15 flex flex-col">
<span class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider mb-2">Applications</span>
<div class="flex items-baseline justify-between">
<span class="text-2xl font-black text-on-surface">${this.applications.length}</span>
<span class="text-[10px] font-bold text-on-surface-variant">${this.applications.filter(a => a.status === 'online').length} Active</span>
</div>
</div>
<div class="bg-surface-container-lowest p-4 rounded-xl border border-outline-variant/15 flex flex-col">
<span class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider mb-2">Total Accounts</span>
<div class="flex items-baseline justify-between">
<span class="text-2xl font-black text-on-surface">${this.accounts.length}</span>
<span class="text-[10px] font-bold text-on-surface-variant/40">Managed</span>
</div>
</div>
<div class="bg-surface-container-lowest p-4 rounded-xl border border-outline-variant/15 flex flex-col">
<span class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider mb-2">Last Updated</span>
<div class="flex items-baseline justify-between">
<span class="text-sm font-black text-on-surface">${new Date().toLocaleDateString()}</span>
</div>
</div>
<div class="bg-primary-container/10 p-4 rounded-xl border border-primary/20 flex flex-col">
<span class="text-[10px] font-bold text-primary uppercase tracking-wider mb-2">Status</span>
<div class="flex items-baseline justify-between">
<span class="text-lg font-black text-primary">Operational</span>
<span class="material-symbols-outlined text-primary text-sm">check_circle</span>
</div>
</div>
</div>
<!-- Recent Activity -->
<div class="bg-surface-container-lowest rounded-xl p-5 border border-outline-variant/15 flex flex-col flex-1 min-h-0">
<div class="flex items-center justify-between mb-4 shrink-0">
<h3 class="text-sm font-bold text-on-surface flex items-center gap-2">
<span class="material-symbols-outlined text-primary text-base">history</span>
Recent Accounts
</h3>
</div>
${this.accounts.length > 0 ? `
<div class="flex-1 overflow-y-auto space-y-2">
${this.accounts.slice(-5).reverse().map(acc => `
<div class="flex gap-3 p-3 bg-surface-container-low/50 rounded-lg border-l-2 border-primary/50">
<div class="flex-1 min-w-0">
<p class="text-[11px] font-bold text-on-surface truncate">${acc.username}</p>
<p class="text-[9px] text-on-surface-variant">${acc.service}${acc.owner}</p>
</div>
</div>
`).join('')}
</div>
` : `
<div class="flex-1 flex items-center justify-center text-center">
<p class="text-sm text-on-surface-variant">No accounts yet. <a href="accounts.html" class="text-primary font-bold">Create one</a></p>
</div>
`}
</div>
</div>
`;
}
getAccountsContent() {
return `
<div class="p-4 md:p-6 flex flex-col h-full overflow-hidden">
<!-- Page Header -->
<div class="flex items-center justify-between mb-4 shrink-0">
<div>
<h1 class="text-xl font-extrabold text-on-surface tracking-tight">Accounts Management</h1>
<p class="text-[10px] text-on-surface-variant uppercase font-semibold tracking-widest mt-0.5">Administrative Access Control</p>
</div>
<button id="addAccountBtn" class="bg-primary hover:bg-primary-dim text-on-primary px-3 py-1.5 rounded-lg text-xs font-bold shadow-sm flex items-center gap-1.5 transition-all active:scale-95">
<span class="material-symbols-outlined text-base">person_add</span>
Add Account
</button>
</div>
<!-- Filters -->
<div class="flex items-center gap-3 mb-4">
<div class="flex items-center gap-1.5">
<span class="text-[10px] font-bold uppercase text-on-surface-variant">Service</span>
<select id="serviceFilter" class="bg-surface-container-low border-slate-200 rounded-md text-[11px] py-1 px-2 pr-6 focus:ring-1 focus:ring-primary shadow-sm">
<option value="">All Services</option>
${this.applications.map(app => `<option value="${app.name}">${app.name}</option>`).join('')}
</select>
</div>
<div class="flex-1"></div>
</div>
<!-- Accounts Table -->
<div class="flex-1 bg-white rounded-xl border border-slate-200 shadow-sm flex flex-col overflow-hidden min-h-0">
${this.accounts.length > 0 ? `
<div class="overflow-y-auto overflow-x-auto flex-1">
<table class="w-full text-left border-collapse w-full">
<thead class="sticky top-0 z-10">
<tr class="bg-slate-50 border-b border-slate-200">
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Owner</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Username</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500">Service</th>
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500 text-right">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
${this.accounts.map((acc, idx) => `
<tr class="hover:bg-slate-50/80 transition-colors group account-row" data-account-id="${idx}">
<td class="px-4 py-3 text-sm font-medium text-slate-900">${acc.owner}</td>
<td class="px-4 py-3 text-sm text-slate-600">${acc.username}</td>
<td class="px-4 py-3 text-sm">
<span class="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs font-semibold">${acc.service}</span>
</td>
<td class="px-4 py-3 text-right">
<button class="p-1.5 text-slate-400 hover:text-primary transition-colors edit-account" data-account-id="${idx}">
<span class="material-symbols-outlined text-lg">edit</span>
</button>
<button class="p-1.5 text-slate-400 hover:text-error transition-colors delete-account" data-account-id="${idx}">
<span class="material-symbols-outlined text-lg">delete</span>
</button>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
` : `
<div class="flex-1 flex items-center justify-center text-center">
<div>
<p class="text-sm text-on-surface-variant mb-4">No accounts yet. Create one to get started.</p>
<button id="addAccountBtn" class="bg-primary hover:bg-primary-dim text-on-primary px-4 py-2 rounded-lg text-sm font-bold">
<span class="material-symbols-outlined text-base inline mr-2">person_add</span>
Add First Account
</button>
</div>
</div>
`}
</div>
</div>
<!-- Account Modal -->
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-black/40 backdrop-blur-sm hidden" id="accountModal">
<div class="modal-content w-full max-w-lg bg-white rounded-xl shadow-2xl border border-slate-200 overflow-hidden m-4">
<div class="px-6 py-4 border-b border-slate-100 flex items-center justify-between bg-slate-50">
<h3 class="text-base font-extrabold text-slate-900">Add New Account</h3>
<button class="p-1.5 rounded-lg hover:bg-slate-200 text-slate-400 transition-colors" data-close-modal>
<span class="material-symbols-outlined">close</span>
</button>
</div>
<form id="accountForm" class="p-6 space-y-4">
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Service</label>
<select id="accountService" class="w-full border-slate-200 rounded-lg text-sm py-2.5 px-3" required>
<option value="">Select a service</option>
${this.applications.map(app => `<option value="${app.name}">${app.name}</option>`).join('')}
</select>
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Owner Name</label>
<input type="text" id="accountOwner" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3" required placeholder="John Doe">
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Username</label>
<input type="text" id="accountUsername" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3" required placeholder="username">
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Password</label>
<input type="password" id="accountPassword" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3" required placeholder="••••••••">
</div>
<div class="flex gap-3 pt-4">
<button type="button" class="flex-1 px-4 py-2 text-xs font-bold text-slate-600 border border-slate-200 rounded-lg" data-close-modal>Cancel</button>
<button type="submit" class="flex-1 px-4 py-2 bg-primary hover:bg-primary-dim text-on-primary rounded-lg text-xs font-bold">Save Account</button>
</div>
</form>
</div>
</div>
`;
}
getApplicationsContent() {
return `
<div class="flex flex-col p-6 overflow-hidden h-full">
<!-- Header Section -->
<div class="flex items-center justify-between gap-6 mb-6 shrink-0">
<div>
<h1 class="text-2xl font-extrabold text-on-surface tracking-tight">Applications</h1>
<p class="text-sm text-on-surface-variant">Manage and monitor active infrastructure services.</p>
</div>
<button id="addAppBtn" class="bg-primary hover:bg-primary-dim text-on-primary px-4 py-2 rounded-xl font-bold flex items-center gap-2 transition-all active:scale-95 shadow-sm">
<span class="material-symbols-outlined">add</span>
Add New
</button>
</div>
<!-- Dashboard Stats -->
<div class="grid grid-cols-3 gap-4 mb-6 shrink-0">
<div class="bg-surface-container-lowest px-4 py-3 rounded-xl shadow-sm border border-outline-variant/5 flex items-center gap-4">
<div class="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center text-primary">
<span class="material-symbols-outlined text-xl">lan</span>
</div>
<div>
<p class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider">Active</p>
<p class="text-lg font-black text-on-surface">${this.applications.filter(a => a.status === 'online').length}</p>
</div>
</div>
<div class="bg-surface-container-lowest px-4 py-3 rounded-xl shadow-sm border border-outline-variant/5 flex items-center gap-4">
<div class="w-10 h-10 rounded-lg bg-tertiary/10 flex items-center justify-center text-tertiary">
<span class="material-symbols-outlined text-xl">bolt</span>
</div>
<div>
<p class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider">Total</p>
<p class="text-lg font-black text-on-surface">${this.applications.length}</p>
</div>
</div>
<div class="bg-surface-container-lowest px-4 py-3 rounded-xl shadow-sm border border-outline-variant/5 flex items-center gap-4">
<div class="w-10 h-10 rounded-lg bg-secondary/10 flex items-center justify-center text-secondary">
<span class="material-symbols-outlined text-xl">database</span>
</div>
<div>
<p class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider">Health</p>
<p class="text-lg font-black text-on-surface">99.9%</p>
</div>
</div>
</div>
<!-- Applications List -->
<div class="bg-surface-container-lowest rounded-xl shadow-sm border border-outline-variant/10 overflow-hidden flex flex-col flex-1 min-h-0">
<div class="overflow-y-auto flex-1">
<table class="w-full text-left border-collapse">
<thead class="sticky top-0 bg-surface-container-lowest z-10">
<tr class="bg-surface-container-low/30 border-b border-outline-variant/10">
<th class="px-6 py-2.5 text-[10px] font-bold text-on-surface-variant uppercase tracking-widest">Name</th>
<th class="px-6 py-2.5 text-[10px] font-bold text-on-surface-variant uppercase tracking-widest">Type</th>
<th class="px-6 py-2.5 text-[10px] font-bold text-on-surface-variant uppercase tracking-widest">Status</th>
<th class="px-6 py-2.5 text-[10px] font-bold text-on-surface-variant uppercase tracking-widest text-right">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/5">
${this.applications.map((app, idx) => `
<tr class="hover:bg-surface-container-low/30 transition-colors group">
<td class="px-6 py-3">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded bg-surface-container-highest flex items-center justify-center text-primary">
<span class="material-symbols-outlined text-sm">${app.icon}</span>
</div>
<span class="font-bold text-sm text-on-surface">${app.name}</span>
</div>
</td>
<td class="px-6 py-3">
<span class="px-2 py-0.5 rounded-full text-[9px] font-black uppercase bg-surface-container-highest text-on-surface-variant">${app.type}</span>
</td>
<td class="px-6 py-3">
<div class="flex items-center gap-1.5">
<div class="w-1.5 h-1.5 rounded-full ${app.status === 'online' ? 'bg-primary' : 'bg-error'} ring-2 ${app.status === 'online' ? 'ring-primary/20' : 'ring-error/20'}"></div>
<span class="text-xs font-medium ${app.status === 'online' ? 'text-on-primary-fixed-variant' : 'text-error'}">${app.status === 'online' ? 'Online' : 'Offline'}</span>
</div>
</td>
<td class="px-6 py-3 text-right">
<div class="flex items-center justify-end gap-1">
<button class="p-1.5 text-on-surface-variant hover:text-primary transition-colors edit-app" data-app-id="${idx}">
<span class="material-symbols-outlined text-lg">edit</span>
</button>
<button class="p-1.5 text-on-surface-variant hover:text-error transition-colors delete-app" data-app-id="${idx}">
<span class="material-symbols-outlined text-lg">delete</span>
</button>
</div>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
</div>
<!-- App Modal -->
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-black/40 backdrop-blur-sm hidden" id="appModal">
<div class="modal-content w-full max-w-lg bg-white rounded-xl shadow-2xl border border-slate-200 overflow-hidden m-4">
<div class="px-6 py-4 border-b border-slate-100 flex items-center justify-between bg-slate-50">
<h3 class="text-base font-extrabold text-slate-900">Add New Application</h3>
<button class="p-1.5 rounded-lg hover:bg-slate-200 text-slate-400 transition-colors" data-close-modal>
<span class="material-symbols-outlined">close</span>
</button>
</div>
<form id="appForm" class="p-6 space-y-4">
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Application Name</label>
<input type="text" id="appName" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3" required placeholder="AWS Services">
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Type</label>
<input type="text" id="appType" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3" required placeholder="Cloud">
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Status</label>
<select id="appStatus" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3">
<option value="online">Online</option>
<option value="offline">Offline</option>
</select>
</div>
<div class="flex gap-3 pt-4">
<button type="button" class="flex-1 px-4 py-2 text-xs font-bold text-slate-600 border border-slate-200 rounded-lg" data-close-modal>Cancel</button>
<button type="submit" class="flex-1 px-4 py-2 bg-primary hover:bg-primary-dim text-on-primary rounded-lg text-xs font-bold">Save Application</button>
</div>
</form>
</div>
</div>
`;
}
setupAccountRowListeners() {
document.querySelectorAll('.delete-account').forEach(btn => {
btn.addEventListener('click', (e) => {
const accountId = btn.dataset.accountId;
if (confirm('Delete this account?')) {
this.accounts.splice(accountId, 1);
this.saveToStorage('accounts', this.accounts);
location.href = 'accounts.html';
}
});
});
document.querySelectorAll('.edit-account').forEach(btn => {
btn.addEventListener('click', (e) => {
const accountId = btn.dataset.accountId;
const account = this.accounts[accountId];
// Populate form with existing data
document.getElementById('accountUsername').value = account.username;
document.getElementById('accountPassword').value = account.password;
document.getElementById('accountOwner').value = account.owner;
document.getElementById('accountService').value = account.service;
this.editingAccountId = accountId;
this.openAccountModal();
});
});
document.querySelectorAll('.delete-app').forEach(btn => {
btn.addEventListener('click', (e) => {
const appId = btn.dataset.appId;
if (confirm('Delete this application?')) {
this.applications.splice(appId, 1);
this.saveToStorage('applications', this.applications);
location.href = 'applications.html';
}
});
});
document.querySelectorAll('.edit-app').forEach(btn => {
btn.addEventListener('click', (e) => {
const appId = btn.dataset.appId;
const app = this.applications[appId];
document.getElementById('appName').value = app.name;
document.getElementById('appType').value = app.type;
document.getElementById('appStatus').value = app.status;
this.editingAppId = appId;
this.openAppModal();
});
});
}
handleAccountSubmit(e) {
e.preventDefault();
const newAccount = {
service: document.getElementById('accountService').value,
owner: document.getElementById('accountOwner').value,
username: document.getElementById('accountUsername').value,
password: document.getElementById('accountPassword').value,
dateCreated: new Date().toLocaleDateString()
};
if (this.editingAccountId !== undefined) {
this.accounts[this.editingAccountId] = newAccount;
this.editingAccountId = undefined;
} else {
this.accounts.push(newAccount);
}
this.saveToStorage('accounts', this.accounts);
this.closeModals();
location.href = 'accounts.html';
}
handleAppSubmit(e) {
e.preventDefault();
const newApp = {
name: document.getElementById('appName').value,
type: document.getElementById('appType').value,
status: document.getElementById('appStatus').value,
icon: 'cloud'
};
if (this.editingAppId !== undefined) {
this.applications[this.editingAppId] = newApp;
this.editingAppId = undefined;
} else {
this.applications.push(newApp);
}
this.saveToStorage('applications', this.applications);
this.closeModals();
location.href = 'applications.html';
}
openAccountModal() {
this.editingAccountId = undefined;
document.getElementById('accountService').value = '';
document.getElementById('accountOwner').value = '';
document.getElementById('accountUsername').value = '';
document.getElementById('accountPassword').value = '';
document.getElementById('accountModal').classList.remove('hidden');
}
openAppModal() {
this.editingAppId = undefined;
document.getElementById('appName').value = '';
document.getElementById('appType').value = '';
document.getElementById('appStatus').value = 'online';
document.getElementById('appModal').classList.remove('hidden');
}
closeModals() {
document.querySelectorAll('.modal-backdrop').forEach(modal => {
modal.classList.add('hidden');
});
}
loadFromStorage(key) {
const data = localStorage.getItem(key);
return data ? JSON.parse(data) : null;
}
saveToStorage(key, data) {
localStorage.setItem(key, JSON.stringify(data));
}
}
// Initialize app when DOM is ready
let app;
document.addEventListener('DOMContentLoaded', () => {
app = new AccountManager();
});

575
applications.html Normal file
View File

@@ -0,0 +1,575 @@
<!DOCTYPE html>
<html class="light" lang="en">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>VaultSentinel - Applications Management</title>
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet"/>
<!-- Material Symbols -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"on-secondary-fixed-variant": "#4e5c71",
"on-secondary": "#f8f8ff",
"secondary-fixed-dim": "#c7d5ed",
"surface-variant": "#d9e4ea",
"surface-tint": "#3755c3",
"primary-container": "#dde1ff",
"primary-dim": "#2848b7",
"on-background": "#2a3439",
"surface-container-lowest": "#ffffff",
"tertiary-fixed-dim": "#d4cdee",
"on-tertiary-container": "#514d68",
"error-container": "#fe8983",
"on-secondary-container": "#455367",
"outline": "#717c82",
"on-primary": "#f8f7ff",
"on-primary-container": "#2747b6",
"inverse-primary": "#6d89fa",
"on-surface": "#2a3439",
"primary-fixed": "#dde1ff",
"on-primary-fixed": "#0732a3",
"secondary-dim": "#465468",
"surface-container-high": "#e1e9ee",
"surface-container-highest": "#d9e4ea",
"on-primary-fixed-variant": "#3352c0",
"on-error-container": "#752121",
"secondary": "#526074",
"tertiary-fixed": "#e3dbfd",
"primary": "#3755c3",
"surface-dim": "#cfdce3",
"tertiary": "#605c78",
"on-error": "#fff7f6",
"secondary-fixed": "#d5e3fc",
"error-dim": "#4e0309",
"surface-bright": "#f7f9fb",
"on-surface-variant": "#566166",
"on-tertiary": "#fcf7ff",
"tertiary-container": "#e3dbfd",
"inverse-on-surface": "#9a9d9f",
"on-tertiary-fixed-variant": "#5b5672",
"tertiary-dim": "#54506b",
"outline-variant": "#a9b4b9",
"on-secondary-fixed": "#324053",
"inverse-surface": "#0b0f10",
"on-tertiary-fixed": "#3e3a54",
"primary-fixed-dim": "#cad2ff",
"surface-container": "#e8eff3",
"secondary-container": "#d5e3fc",
"surface-container-low": "#f0f4f7",
"background": "#f7f9fb",
"error": "#9f403d",
"surface": "#f7f9fb"
},
fontFamily: {
"headline": ["Manrope"],
"body": ["Inter"],
"label": ["Inter"]
},
borderRadius: {"DEFAULT": "0.125rem", "lg": "0.25rem", "xl": "0.5rem", "full": "0.75rem"},
},
},
}
</script>
<style>
.material-symbols-outlined {
font-family: 'Material Symbols Outlined';
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;
font-size: 1.25rem;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-flex;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
}
body { font-family: 'Inter', sans-serif; overflow: hidden; height: 100vh; }
h1, h2, h3 { font-family: 'Manrope', sans-serif; }
.modal-backdrop {
opacity: 0;
transition: opacity 0.2s ease-in-out;
pointer-events: none;
display: none;
}
.modal-backdrop.open {
display: flex !important;
opacity: 1;
pointer-events: auto;
}
</style>
</head>
<body class="bg-background text-on-surface antialiased flex h-screen w-screen">
<!-- SideNavBar (shared with all pages) -->
<aside class="h-screen w-56 flex flex-col bg-slate-100 dark:bg-slate-900 font-manrope text-sm font-medium border-r border-outline-variant/10 shrink-0">
<div class="flex flex-col h-full py-6">
<!-- Header -->
<div class="px-6 mb-8">
<div class="text-lg font-black text-slate-900 dark:text-slate-50 tracking-tight leading-none">VaultSentinel</div>
<div class="text-[10px] uppercase tracking-widest text-on-surface-variant mt-1.5 font-bold">Admin Console</div>
</div>
<!-- Primary Nav -->
<nav class="flex-1 px-3 space-y-1">
<a href="index.html" class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50 transition-all group cursor-pointer">
<span class="material-symbols-outlined">dashboard</span>
<span>Dashboard</span>
</a>
<a href="applications.html" class="flex items-center gap-3 px-3 py-2 border-l-4 border-blue-600 bg-slate-200/80 dark:bg-slate-800 text-slate-900 dark:text-slate-50 font-bold transition-all group cursor-pointer">
<span class="material-symbols-outlined">apps</span>
<span>Applications</span>
</a>
<a href="accounts.html" class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50 transition-all group cursor-pointer">
<span class="material-symbols-outlined">manage_accounts</span>
<span>Accounts</span>
</a>
</nav>
<!-- Footer -->
<div class="px-6 pt-4 border-t border-outline-variant/10">
<div class="text-[10px] font-bold text-on-surface-variant/40 uppercase tracking-widest">v1.0.0</div>
</div>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 flex flex-col h-screen min-w-0">
<!-- TopAppBar -->
<header class="h-14 flex items-center justify-between px-6 bg-slate-50/80 dark:bg-slate-950/80 backdrop-blur-xl border-b border-outline-variant/10 shrink-0">
<div class="flex items-center gap-4 flex-1">
<div class="flex items-center bg-surface-container-high px-3 py-1.5 rounded-full w-64 group focus-within:ring-2 ring-primary/20 transition-all">
<span class="material-symbols-outlined text-on-surface-variant text-base">search</span>
<input id="searchInput" class="bg-transparent border-none focus:ring-0 text-xs w-full placeholder:text-on-surface-variant/60 py-0" placeholder="Search resources..." type="text"/>
</div>
</div>
<div class="flex items-center gap-3">
<button class="p-1.5 rounded-full text-slate-500 hover:bg-slate-200 transition-colors">
<span class="material-symbols-outlined">notifications</span>
</button>
<button class="p-1.5 rounded-full text-slate-500 hover:bg-slate-200 transition-colors">
<span class="material-symbols-outlined">settings</span>
</button>
</div>
</header>
<!-- Content Area -->
<div class="flex-1 flex flex-col p-6 space-y-6 min-h-0 overflow-auto">
<div class="flex items-center justify-between gap-6 mb-6 shrink-0">
<div>
<h2 class="text-2xl font-extrabold text-on-surface tracking-tight">Applications</h2>
<p class="text-sm text-on-surface-variant">Manage and monitor active infrastructure services.</p>
</div>
<button id="addAppBtn" class="bg-primary hover:bg-primary-dim text-on-primary px-4 py-2 rounded-xl font-bold flex items-center gap-2 transition-all active:scale-95 shadow-sm">
<span class="material-symbols-outlined">add</span>
Add New
</button>
</div>
<!-- Stats -->
<div class="grid grid-cols-3 gap-4 mb-6 shrink-0">
<div class="bg-surface-container-lowest px-4 py-3 rounded-xl shadow-sm border border-outline-variant/5 flex items-center gap-4">
<div class="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center text-primary">
<span class="material-symbols-outlined text-xl">lan</span>
</div>
<div>
<p class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider">Active</p>
<p class="text-lg font-black text-on-surface" id="activeCount">0</p>
</div>
</div>
<div class="bg-surface-container-lowest px-4 py-3 rounded-xl shadow-sm border border-outline-variant/5 flex items-center gap-4">
<div class="w-10 h-10 rounded-lg bg-tertiary/10 flex items-center justify-center text-tertiary">
<span class="material-symbols-outlined text-xl">bolt</span>
</div>
<div>
<p class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider">Total</p>
<p class="text-lg font-black text-on-surface" id="totalCount">0</p>
</div>
</div>
<div class="bg-surface-container-lowest px-4 py-3 rounded-xl shadow-sm border border-outline-variant/5 flex items-center gap-4">
<div class="w-10 h-10 rounded-lg bg-secondary/10 flex items-center justify-center text-secondary">
<span class="material-symbols-outlined text-xl">database</span>
</div>
<div>
<p class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider">Health</p>
<p class="text-lg font-black text-on-surface">99.9%</p>
</div>
</div>
</div>
<!-- Applications Table -->
<div class="bg-surface-container-lowest rounded-xl shadow-sm border border-outline-variant/10 overflow-hidden flex flex-col flex-1 min-h-0">
<div class="px-6 py-3 flex items-center justify-between border-b border-outline-variant/10 bg-surface-container-low/20 shrink-0">
<h3 class="text-sm font-bold text-on-surface uppercase tracking-wider">Registered Services</h3>
<div class="relative">
<span class="material-symbols-outlined absolute left-2 top-1/2 -translate-y-1/2 text-on-surface-variant text-base">search</span>
<input id="searchInput" class="pl-8 pr-3 py-1.5 bg-surface-container-low border-none rounded-lg text-xs focus:ring-1 focus:ring-primary/40 w-48" placeholder="Filter..." type="text"/>
</div>
</div>
<div class="overflow-y-auto flex-1">
<table class="w-full text-left border-collapse">
<thead class="sticky top-0 bg-surface-container-lowest z-10">
<tr class="bg-surface-container-low/30 border-b border-outline-variant/10">
<th class="px-6 py-2.5 text-[10px] font-bold text-on-surface-variant uppercase tracking-widest">Name</th>
<th class="px-6 py-2.5 text-[10px] font-bold text-on-surface-variant uppercase tracking-widest">Type</th>
<th class="px-6 py-2.5 text-[10px] font-bold text-on-surface-variant uppercase tracking-widest">Status</th>
<th class="px-6 py-2.5 text-[10px] font-bold text-on-surface-variant uppercase tracking-widest text-right">Actions</th>
</tr>
</thead>
<tbody id="appList" class="divide-y divide-outline-variant/5">
<!-- Apps will be inserted here -->
</tbody>
</table>
</div>
</div>
</div> <!-- End Content Area -->
</main>
<!-- Add/Edit App Modal -->
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-on-surface/30 backdrop-blur-[2px] hidden" id="appModal">
<div class="w-full max-w-[640px] bg-surface-container-lowest rounded-xl shadow-[0px_12px_32px_rgba(42,52,57,0.12)] border-none overflow-hidden m-4">
<!-- Modal Header -->
<div class="px-8 py-6 border-b border-surface-container flex justify-between items-start">
<div>
<span class="text-[0.625rem] font-bold uppercase tracking-[0.1em] text-on-surface-variant block mb-1">Application Management</span>
<h2 class="text-2xl font-extrabold text-on-surface tracking-tight" id="appModalTitle">Add New Application</h2>
</div>
<button class="w-10 h-10 flex items-center justify-center rounded-full hover:bg-surface-container transition-colors text-on-surface-variant" onclick="closeModal()">
<span class="material-symbols-outlined">close</span>
</button>
</div>
<!-- Modal Content -->
<form id="appForm" class="p-8 space-y-6">
<div>
<label class="text-[0.65rem] font-bold uppercase tracking-widest text-on-surface-variant ml-1 block mb-2">Application Name</label>
<div class="flex items-center h-12 px-4 bg-surface-container-highest rounded-lg border border-transparent focus-within:border-primary/40 focus-within:shadow-[0_0_0_2px_rgba(55,85,195,0.05)] transition-all">
<span class="material-symbols-outlined text-on-surface-variant mr-3 text-sm">apps</span>
<input type="text" id="appName" class="flex-1 bg-transparent border-none focus:ring-0 text-sm font-medium text-on-surface placeholder:text-on-surface-variant/60 p-0" required placeholder="AWS Services">
</div>
</div>
<div>
<label class="text-[0.65rem] font-bold uppercase tracking-widest text-on-surface-variant ml-1 block mb-2">Type</label>
<div class="flex items-center h-12 px-4 bg-surface-container-highest rounded-lg border border-transparent focus-within:border-primary/40 focus-within:shadow-[0_0_0_2px_rgba(55,85,195,0.05)] transition-all">
<span class="material-symbols-outlined text-on-surface-variant mr-3 text-sm">category</span>
<input type="text" id="appType" class="flex-1 bg-transparent border-none focus:ring-0 text-sm font-medium text-on-surface placeholder:text-on-surface-variant/60 p-0" required placeholder="Cloud">
</div>
</div>
<div>
<label class="text-[0.65rem] font-bold uppercase tracking-widest text-on-surface-variant ml-1 block mb-2">Status</label>
<div class="flex items-center h-12 px-4 bg-surface-container-highest rounded-lg border border-transparent focus-within:border-primary/40 focus-within:shadow-[0_0_0_2px_rgba(55,85,195,0.05)] transition-all">
<span class="material-symbols-outlined text-on-surface-variant mr-3 text-sm">check_circle</span>
<select id="appStatus" class="flex-1 bg-transparent border-none focus:ring-0 text-sm font-medium text-on-surface p-0">
<option value="online">Online</option>
<option value="offline">Offline</option>
</select>
</div>
</div>
</form>
<!-- Modal Footer -->
<div class="px-8 py-6 bg-surface-container-low flex items-center justify-end gap-3">
<button class="px-5 h-11 text-sm font-bold text-on-surface-variant hover:bg-surface-container transition-all rounded-lg" onclick="closeModal()">
Close
</button>
<button onclick="document.getElementById('appForm').dispatchEvent(new Event('submit'))" class="px-6 h-11 text-sm font-bold text-on-primary bg-gradient-to-br from-primary to-primary-dim rounded-lg shadow-sm hover:opacity-90 active:scale-[0.98] transition-all flex items-center gap-2">
<span class="material-symbols-outlined text-base">save</span>
Save Application
</button>
</div>
</div>
</div>
<!-- View App Details Modal -->
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-on-surface/30 backdrop-blur-[2px] hidden" id="viewAppModal">
<div class="w-full max-w-[640px] bg-surface-container-lowest rounded-xl shadow-[0px_12px_32px_rgba(42,52,57,0.12)] border-none overflow-hidden m-4">
<!-- Modal Header -->
<div class="px-8 py-6 border-b border-surface-container flex justify-between items-start">
<div>
<span class="text-[0.625rem] font-bold uppercase tracking-[0.1em] text-on-surface-variant block mb-1">Application Details</span>
<h2 class="text-2xl font-extrabold text-on-surface tracking-tight" id="viewAppName">Application Name</h2>
</div>
<button class="w-10 h-10 flex items-center justify-center rounded-full hover:bg-surface-container transition-colors text-on-surface-variant" onclick="closeViewModal()">
<span class="material-symbols-outlined">close</span>
</button>
</div>
<!-- Modal Content -->
<div class="p-8 space-y-6">
<!-- Service Info Section -->
<div class="flex items-center gap-5 p-5 bg-surface-container-low rounded-xl">
<div class="w-14 h-14 bg-white rounded-xl shadow-sm flex items-center justify-center">
<span class="material-symbols-outlined text-primary text-3xl" id="viewAppIcon">apps</span>
</div>
<div class="flex-1">
<div class="text-xs font-bold text-on-surface-variant uppercase tracking-wider mb-0.5">Application Type</div>
<div class="text-xl font-bold text-on-surface" id="viewAppType">-</div>
</div>
<div>
<span class="px-3 py-1 text-[0.7rem] font-bold rounded-full uppercase tracking-tighter" id="viewAppStatusBadge">Offline</span>
</div>
</div>
<!-- Details Grid -->
<div class="space-y-4">
<div>
<label class="text-[0.65rem] font-bold uppercase tracking-widest text-on-surface-variant block mb-2">Status</label>
<div class="flex items-center h-12 px-4 bg-surface-container-highest rounded-lg">
<span class="material-symbols-outlined text-on-surface-variant mr-3 text-sm">check_circle</span>
<span class="text-sm font-medium text-on-surface" id="viewAppStatusText">Online</span>
</div>
</div>
</div>
</div>
<!-- Modal Footer -->
<div class="px-8 py-6 bg-surface-container-low flex items-center justify-end gap-3">
<button class="px-5 h-11 text-sm font-bold text-on-surface-variant hover:bg-surface-container transition-all rounded-lg" onclick="closeViewModal()">
Close
</button>
<button onclick="editCurrentApp()" class="px-6 h-11 text-sm font-bold text-on-primary bg-gradient-to-br from-primary to-primary-dim rounded-lg shadow-sm hover:opacity-90 active:scale-[0.98] transition-all flex items-center gap-2">
<span class="material-symbols-outlined text-base">edit</span>
Edit Application
</button>
</div>
</div>
</div>
<!-- Delete Confirm Modal -->
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-on-surface/30 backdrop-blur-[2px] hidden" id="deleteConfirmModal">
<div class="w-full max-w-[480px] bg-surface-container-lowest rounded-xl shadow-[0px_12px_32px_rgba(42,52,57,0.12)] border-none overflow-hidden m-4">
<!-- Modal Header -->
<div class="px-8 py-6 border-b border-surface-container bg-error/5">
<h3 class="text-base font-extrabold text-error flex items-center gap-2">
<span class="material-symbols-outlined">warning</span>
Delete Application
</h3>
</div>
<!-- Modal Content -->
<div class="px-8 py-6">
<p class="text-sm text-on-surface-variant">Are you sure you want to delete <strong id="deleteAppName" class="text-on-surface"></strong>? This action cannot be undone.</p>
</div>
<!-- Modal Footer -->
<div class="px-8 py-6 bg-surface-container-low flex items-center justify-end gap-3">
<button class="px-5 h-11 text-sm font-bold text-on-surface-variant hover:bg-surface-container transition-all rounded-lg" onclick="closeDeleteModal()">
Cancel
</button>
<button onclick="confirmDelete()" class="px-6 h-11 text-sm font-bold text-on-error bg-gradient-to-br from-error to-error-dim rounded-lg shadow-sm hover:opacity-90 active:scale-[0.98] transition-all flex items-center gap-2">
<span class="material-symbols-outlined text-base">delete</span>
Delete
</button>
</div>
</div>
</div>
<script>
class AppManager {
constructor() {
this.applications = this.loadApps() || [
{ id: 1, name: 'AWS', type: 'Cloud', status: 'online', icon: 'cloud' },
{ id: 2, name: 'GitHub', type: 'VCS', status: 'online', icon: 'code' },
{ id: 3, name: 'Google Workspace', type: 'Collaboration', status: 'online', icon: 'mail' },
{ id: 4, name: 'Nginx Proxy', type: 'Infra', status: 'offline', icon: 'dns' },
];
this.init();
}
init() {
this.render();
this.setupEventListeners();
}
setupEventListeners() {
document.getElementById('addAppBtn').addEventListener('click', () => this.openModal());
document.getElementById('appForm').addEventListener('submit', (e) => this.handleSubmit(e));
document.getElementById('searchInput').addEventListener('input', (e) => this.filterApps(e.target.value));
}
render() {
this.updateStats();
this.renderTable(this.applications);
this.setupDeleteButtons();
}
updateStats() {
document.getElementById('activeCount').textContent = this.applications.filter(a => a.status === 'online').length;
document.getElementById('totalCount').textContent = this.applications.length;
}
renderTable(apps) {
const tbody = document.getElementById('appList');
tbody.innerHTML = apps.map((app, idx) => `
<tr class="hover:bg-surface-container-low/30 transition-colors">
<td class="px-6 py-3">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded bg-surface-container-highest flex items-center justify-center text-primary">
<span class="material-symbols-outlined text-sm">${app.icon}</span>
</div>
<span class="font-bold text-sm text-on-surface">${app.name}</span>
</div>
</td>
<td class="px-6 py-3">
<span class="px-2 py-0.5 rounded-full text-[9px] font-black uppercase bg-surface-container-highest text-on-surface-variant">${app.type}</span>
</td>
<td class="px-6 py-3">
<div class="flex items-center gap-1.5">
<div class="w-1.5 h-1.5 rounded-full ${app.status === 'online' ? 'bg-primary' : 'bg-error'} ring-2 ${app.status === 'online' ? 'ring-primary/20' : 'ring-error/20'}"></div>
<span class="text-xs font-medium ${app.status === 'online' ? 'text-on-primary-fixed-variant' : 'text-error'}">${app.status === 'online' ? 'Online' : 'Offline'}</span>
</div>
</td>
<td class="px-6 py-3 text-right">
<button class="p-1.5 text-on-surface-variant hover:text-on-surface transition-colors view-app" data-idx="${idx}" title="View Details">
<span class="material-symbols-outlined text-lg">info</span>
</button>
<button class="p-1.5 text-on-surface-variant hover:text-primary transition-colors edit-app" data-idx="${idx}" title="Edit">
<span class="material-symbols-outlined text-lg">edit</span>
</button>
<button class="p-1.5 text-on-surface-variant hover:text-error transition-colors delete-app" data-idx="${idx}" title="Delete">
<span class="material-symbols-outlined text-lg">delete</span>
</button>
</td>
</tr>
`).join('');
}
setupDeleteButtons() {
document.querySelectorAll('.view-app').forEach(btn => {
btn.addEventListener('click', () => {
const idx = btn.dataset.idx;
const app = this.applications[idx];
window.currentViewApp = app;
window.currentViewAppIdx = idx;
document.getElementById('viewAppName').textContent = app.name;
document.getElementById('viewAppType').textContent = app.type;
document.getElementById('viewAppStatusText').textContent = app.status === 'online' ? 'Online' : 'Offline';
document.getElementById('viewAppStatusBadge').textContent = app.status === 'online' ? 'Online' : 'Offline';
document.getElementById('viewAppStatusBadge').className = 'px-3 py-1 text-[0.7rem] font-bold rounded-full uppercase tracking-tighter ' + (app.status === 'online' ? 'bg-primary/15 text-primary' : 'bg-error/15 text-error');
document.getElementById('viewAppModal').classList.add('open');
});
});
document.querySelectorAll('.delete-app').forEach(btn => {
btn.addEventListener('click', () => {
const idx = btn.dataset.idx;
const app = this.applications[idx];
window.pendingDeleteIdx = idx;
document.getElementById('deleteAppName').textContent = app.name;
document.getElementById('deleteConfirmModal').classList.add('open');
});
});
document.querySelectorAll('.edit-app').forEach(btn => {
btn.addEventListener('click', () => {
const idx = btn.dataset.idx;
const app = this.applications[idx];
document.getElementById('appName').value = app.name;
document.getElementById('appType').value = app.type;
document.getElementById('appStatus').value = app.status;
document.getElementById('appModalTitle').textContent = 'Edit Application';
this.editingIdx = idx;
this.openModal();
});
});
}
filterApps(query) {
const filtered = this.applications.filter(app =>
app.name.toLowerCase().includes(query.toLowerCase()) ||
app.type.toLowerCase().includes(query.toLowerCase())
);
this.renderTable(filtered);
this.setupDeleteButtons();
}
handleSubmit(e) {
e.preventDefault();
const app = {
name: document.getElementById('appName').value,
type: document.getElementById('appType').value,
status: document.getElementById('appStatus').value,
icon: 'cloud'
};
if (this.editingIdx !== undefined) {
this.applications[this.editingIdx] = app;
this.editingIdx = undefined;
} else {
this.applications.push(app);
}
this.saveApps();
this.closeModal();
this.render();
}
openModal() {
if (this.editingIdx === undefined) {
document.getElementById('appForm').reset();
document.getElementById('appModalTitle').textContent = 'Add New Application';
}
document.getElementById('appModal').classList.add('open');
}
closeModal() {
document.getElementById('appModal').classList.remove('open');
this.editingIdx = undefined;
}
loadApps() {
return JSON.parse(localStorage.getItem('applications'));
}
saveApps() {
localStorage.setItem('applications', JSON.stringify(this.applications));
}
}
function closeModal() {
document.getElementById('appModal').classList.remove('open');
}
function closeDeleteModal() {
document.getElementById('deleteConfirmModal').classList.remove('open');
window.pendingDeleteIdx = null;
}
function confirmDelete() {
const idx = window.pendingDeleteIdx;
if (idx !== null && idx !== undefined) {
appManager.applications.splice(idx, 1);
appManager.saveApps();
appManager.render();
closeDeleteModal();
}
}
function closeViewModal() {
document.getElementById('viewAppModal').classList.remove('open');
}
function editCurrentApp() {
const idx = window.currentViewAppIdx;
const app = window.currentViewApp;
closeViewModal();
document.getElementById('appName').value = app.name;
document.getElementById('appType').value = app.type;
document.getElementById('appStatus').value = app.status;
document.getElementById('appModalTitle').textContent = 'Edit Application';
appManager.editingIdx = idx;
appManager.openModal();
}
let appManager;
document.addEventListener('DOMContentLoaded', () => {
appManager = new AppManager();
});
</script>
</body>
</html>

224
dialog.html Normal file
View File

@@ -0,0 +1,224 @@
<!DOCTYPE html>
<html class="light" lang="en"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Sentinel Accounts - Account Details</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&amp;family=Inter:wght@400;500;600&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"on-error-container": "#752121",
"background": "#f7f9fb",
"secondary-dim": "#465468",
"tertiary-dim": "#54506b",
"outline": "#717c82",
"on-secondary-container": "#455367",
"error-dim": "#4e0309",
"primary-fixed": "#dde1ff",
"on-primary-fixed": "#0732a3",
"surface-variant": "#d9e4ea",
"primary-dim": "#2848b7",
"on-tertiary": "#fcf7ff",
"surface": "#f7f9fb",
"primary-container": "#dde1ff",
"inverse-on-surface": "#9a9d9f",
"error": "#9f403d",
"on-primary": "#f8f7ff",
"on-surface-variant": "#566166",
"tertiary-container": "#e3dbfd",
"surface-container-lowest": "#ffffff",
"surface-dim": "#cfdce3",
"tertiary-fixed-dim": "#d4cdee",
"inverse-surface": "#0b0f10",
"on-primary-fixed-variant": "#3352c0",
"surface-tint": "#3755c3",
"secondary-fixed": "#d5e3fc",
"secondary-fixed-dim": "#c7d5ed",
"secondary": "#526074",
"tertiary-fixed": "#e3dbfd",
"on-secondary-fixed-variant": "#4e5c71",
"on-primary-container": "#2747b6",
"primary-fixed-dim": "#cad2ff",
"surface-container-highest": "#d9e4ea",
"surface-container-low": "#f0f4f7",
"surface-container": "#e8eff3",
"on-tertiary-fixed-variant": "#5b5672",
"secondary-container": "#d5e3fc",
"inverse-primary": "#6d89fa",
"outline-variant": "#a9b4b9",
"on-secondary-fixed": "#324053",
"on-surface": "#2a3439",
"surface-container-high": "#e1e9ee",
"on-background": "#2a3439",
"on-tertiary-fixed": "#3e3a54",
"primary": "#3755c3",
"on-tertiary-container": "#514d68",
"on-error": "#fff7f6",
"on-secondary": "#f8f7ff",
"tertiary": "#605c78",
"error-container": "#fe8983",
"surface-bright": "#f7f9fb"
},
fontFamily: {
"headline": ["Manrope"],
"body": ["Inter"],
"label": ["Inter"]
},
borderRadius: {"DEFAULT": "0.125rem", "lg": "0.25rem", "xl": "0.5rem", "full": "0.75rem"},
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
vertical-align: middle;
}
body { font-family: 'Inter', sans-serif; }
h1, h2, h3 { font-family: 'Manrope', sans-serif; }
.glass-overlay {
backdrop-filter: blur(12px);
background: rgba(247, 249, 251, 0.8);
}
</style>
</head>
<body class="bg-background text-on-surface overflow-hidden h-screen flex flex-col">
<!-- Blurred Background (Mocking the Accounts Management Table) -->
<div class="fixed inset-0 z-0 blur-[8px] opacity-40 pointer-events-none select-none overflow-hidden flex flex-col">
<!-- Mock TopNavBar (From JSON Guidance) -->
<header class="flex justify-between items-center w-full px-8 h-16 bg-[#f7f9fb] text-[#5a6a72] font-['Manrope'] text-sm tracking-wide font-medium">
<div class="text-lg font-extrabold tracking-tighter text-[#2a3439]">Sentinel Accounts</div>
<div class="flex items-center gap-6">
<span class="material-symbols-outlined">notifications</span>
<span class="material-symbols-outlined">help_outline</span>
</div>
</header>
<div class="flex flex-1">
<!-- Mock SideNavBar (From JSON Guidance) -->
<aside class="flex flex-col h-full py-6 w-64 bg-[#f0f4f7] text-[#5a6a72] font-['Inter'] text-[0.875rem] font-medium">
<div class="px-6 mb-8">
<div class="text-sm font-black uppercase tracking-[0.05em] text-[#2a3439]">Architectural Sentinel</div>
<div class="text-xs text-on-surface-variant opacity-70">Enterprise Security</div>
</div>
<nav class="flex-1">
<div class="flex items-center gap-3 px-6 py-3 bg-white text-[#3755c3] font-bold border-l-4 border-[#3755c3]">
<span class="material-symbols-outlined" data-icon="vpn_key">vpn_key</span> Account Access
</div>
<div class="flex items-center gap-3 px-6 py-3">
<span class="material-symbols-outlined" data-icon="dashboard">dashboard</span> Dashboard
</div>
</nav>
</aside>
<!-- Mock Table Stage -->
<main class="flex-1 p-8 bg-background">
<div class="h-12 w-48 bg-surface-container-high rounded-xl mb-6"></div>
<div class="bg-surface-container-lowest rounded-xl shadow-sm border border-outline-variant/10 overflow-hidden">
<div class="h-12 border-b border-outline-variant/10 bg-surface-container-low px-6 flex items-center gap-4">
<div class="h-4 w-4 bg-outline-variant/20 rounded"></div>
<div class="h-4 w-24 bg-outline-variant/20 rounded"></div>
<div class="h-4 w-32 bg-outline-variant/20 rounded ml-auto"></div>
</div>
<div class="p-6 space-y-4">
<div class="h-10 w-full bg-surface-container-low/50 rounded-lg"></div>
<div class="h-10 w-full bg-surface-container-low/50 rounded-lg"></div>
<div class="h-10 w-full bg-surface-container-low/50 rounded-lg"></div>
<div class="h-10 w-full bg-surface-container-low/50 rounded-lg"></div>
</div>
</div>
</main>
</div>
</div>
<!-- Modal Overlay -->
<div class="fixed inset-0 z-50 flex items-center justify-center bg-on-surface/30 backdrop-blur-[2px]">
<!-- Dialog Container -->
<div class="w-full max-w-[640px] bg-surface-container-lowest rounded-xl shadow-[0px_12px_32px_rgba(42,52,57,0.12)] border-none overflow-hidden">
<!-- Modal Header -->
<div class="px-8 py-6 border-b border-surface-container flex justify-between items-center">
<div>
<span class="text-[0.625rem] font-bold uppercase tracking-[0.1em] text-on-surface-variant block mb-1">Resource Details</span>
<h2 class="text-2xl font-extrabold text-on-surface tracking-tight">Account Details</h2>
</div>
<button class="w-10 h-10 flex items-center justify-center rounded-full hover:bg-surface-container transition-colors text-on-surface-variant">
<span class="material-symbols-outlined" data-icon="close">close</span>
</button>
</div>
<!-- Modal Content (Bento-style layout for metadata) -->
<div class="p-8 space-y-8">
<!-- Service Info Section -->
<div class="flex items-center gap-5 p-5 bg-surface-container-low rounded-xl">
<div class="w-14 h-14 bg-white rounded-xl shadow-sm flex items-center justify-center">
<span class="material-symbols-outlined text-primary text-3xl" data-icon="cloud">cloud</span>
</div>
<div>
<div class="text-xs font-bold text-on-surface-variant uppercase tracking-wider mb-0.5">Cloud Infrastructure</div>
<div class="text-xl font-bold text-on-surface">AWS Production</div>
</div>
<div class="ml-auto">
<span class="px-3 py-1 bg-primary-container text-on-primary-container text-[0.7rem] font-bold rounded-full uppercase tracking-tighter">Active</span>
</div>
</div>
<!-- Core Details Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Username Field -->
<div class="space-y-2">
<label class="text-[0.65rem] font-bold uppercase tracking-widest text-on-surface-variant ml-1">Username</label>
<div class="flex items-center h-12 px-4 bg-surface-container-highest rounded-lg border border-transparent focus-within:border-primary/40 focus-within:shadow-[0_0_0_2px_rgba(55,85,195,0.05)] transition-all">
<span class="material-symbols-outlined text-on-surface-variant mr-3 text-sm" data-icon="alternate_email">alternate_email</span>
<span class="text-sm font-medium text-on-surface">admin.aws_prod</span>
<button class="ml-auto text-on-surface-variant hover:text-primary transition-colors">
<span class="material-symbols-outlined text-base" data-icon="content_copy">content_copy</span>
</button>
</div>
</div>
<!-- Password Field -->
<div class="space-y-2">
<label class="text-[0.65rem] font-bold uppercase tracking-widest text-on-surface-variant ml-1">Password</label>
<div class="flex items-center h-12 px-4 bg-surface-container-highest rounded-lg border border-transparent focus-within:border-primary/40 focus-within:shadow-[0_0_0_2px_rgba(55,85,195,0.05)] transition-all">
<span class="material-symbols-outlined text-on-surface-variant mr-3 text-sm" data-icon="lock">lock</span>
<span class="text-sm font-medium text-on-surface">•••••••••••••••</span>
<button class="ml-auto text-on-surface-variant hover:text-primary transition-colors">
<span class="material-symbols-outlined text-base" data-icon="visibility">visibility</span>
</button>
</div>
</div>
</div>
<!-- Metadata Row (Asymmetric Bento) -->
<div class="grid grid-cols-12 gap-4">
<!-- Owner Card -->
<div class="col-span-7 bg-surface-container-low/50 p-4 rounded-xl flex items-center gap-4">
<img alt="Alex Rivera Profile" class="w-10 h-10 rounded-full object-cover ring-2 ring-white" data-alt="Close-up professional portrait of a male systems administrator with a friendly expression in a modern office environment" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBi4gNrkG6OjxYer2iM7vtnmB1_dhArLqll8N46GWZ4YDXLfnwRIIf_bLhZRcMjHCxtKLivBh_JJMTnGRO4kIj0ZCtbVZ61SFhSJvZlPE3ZgNmNCCh7bDXDeFgdWnHKhWAcjDcpLmO02gp5HCU_6GJpLNdIU3pJosKGJsVW_hAhIfp8OYJcepHHf_23k3eQ9ZxkOP4ZR4qu2PU6ZmO2qTCVlJCZVtB-x6RC3YsjcpMNwpyIhSNCIcAvRKTOfU_cb2vtO6t9oD38b6o"/>
<div>
<div class="text-[0.6rem] font-bold uppercase text-on-surface-variant mb-0.5">Account Owner</div>
<div class="text-sm font-semibold text-on-surface">Alex Rivera</div>
</div>
</div>
<!-- Date Card -->
<div class="col-span-5 bg-surface-container-low/50 p-4 rounded-xl">
<div class="text-[0.6rem] font-bold uppercase text-on-surface-variant mb-0.5">Date Created</div>
<div class="text-sm font-semibold text-on-surface flex items-center gap-2">
<span class="material-symbols-outlined text-base text-on-surface-variant" data-icon="calendar_today">calendar_today</span>
Oct 24, 2023
</div>
</div>
</div>
</div>
<!-- Footer Actions -->
<div class="px-8 py-6 bg-surface-container-low flex items-center justify-end gap-3">
<button class="px-5 h-11 text-sm font-bold text-on-secondary-container hover:bg-surface-container transition-all rounded-lg">
Close
</button>
<button class="px-6 h-11 text-sm font-bold text-on-primary bg-gradient-to-br from-primary to-primary-dim rounded-lg shadow-sm hover:opacity-90 active:scale-[0.98] transition-all flex items-center gap-2">
<span class="material-symbols-outlined text-base" data-icon="edit">edit</span>
Edit Account
</button>
</div>
</div>
</div>
</body></html>

172
index.html Normal file
View File

@@ -0,0 +1,172 @@
<!DOCTYPE html>
<html class="light" lang="en">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>VaultSentinel - Account Management System</title>
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet"/>
<!-- Material Symbols -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"on-secondary-fixed-variant": "#4e5c71",
"on-secondary": "#f8f8ff",
"secondary-fixed-dim": "#c7d5ed",
"surface-variant": "#d9e4ea",
"surface-tint": "#3755c3",
"primary-container": "#dde1ff",
"primary-dim": "#2848b7",
"on-background": "#2a3439",
"surface-container-lowest": "#ffffff",
"tertiary-fixed-dim": "#d4cdee",
"on-tertiary-container": "#514d68",
"error-container": "#fe8983",
"on-secondary-container": "#455367",
"outline": "#717c82",
"on-primary": "#f8f7ff",
"on-primary-container": "#2747b6",
"inverse-primary": "#6d89fa",
"on-surface": "#2a3439",
"primary-fixed": "#dde1ff",
"on-primary-fixed": "#0732a3",
"secondary-dim": "#465468",
"surface-container-high": "#e1e9ee",
"surface-container-highest": "#d9e4ea",
"on-primary-fixed-variant": "#3352c0",
"on-error-container": "#752121",
"secondary": "#526074",
"tertiary-fixed": "#e3dbfd",
"primary": "#3755c3",
"surface-dim": "#cfdce3",
"tertiary": "#605c78",
"on-error": "#fff7f6",
"secondary-fixed": "#d5e3fc",
"error-dim": "#4e0309",
"surface-bright": "#f7f9fb",
"on-surface-variant": "#566166",
"on-tertiary": "#fcf7ff",
"tertiary-container": "#e3dbfd",
"inverse-on-surface": "#9a9d9f",
"on-tertiary-fixed-variant": "#5b5672",
"tertiary-dim": "#54506b",
"outline-variant": "#a9b4b9",
"on-secondary-fixed": "#324053",
"inverse-surface": "#0b0f10",
"on-tertiary-fixed": "#3e3a54",
"primary-fixed-dim": "#cad2ff",
"surface-container": "#e8eff3",
"secondary-container": "#d5e3fc",
"surface-container-low": "#f0f4f7",
"background": "#f7f9fb",
"error": "#9f403d",
"surface": "#f7f9fb"
},
fontFamily: {
"headline": ["Manrope"],
"body": ["Inter"],
"label": ["Inter"]
},
borderRadius: {"DEFAULT": "0.125rem", "lg": "0.25rem", "xl": "0.5rem", "full": "0.75rem"},
},
},
}
</script>
<style>
.material-symbols-outlined {
font-family: 'Material Symbols Outlined';
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;
font-size: 1.25rem;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-flex;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
}
body { font-family: 'Inter', sans-serif; height: 100vh; overflow: hidden; }
h1, h2, h3, .brand-logo { font-family: 'Manrope', sans-serif; }
.modal-backdrop {
opacity: 0;
transition: opacity 0.2s ease-in-out;
pointer-events: none;
}
.modal-backdrop.open {
opacity: 1;
pointer-events: auto;
}
.modal-content {
transform: scale(0.95);
transition: transform 0.2s ease-in-out;
}
.modal-backdrop.open .modal-content {
transform: scale(1);
}
</style>
</head>
<body class="bg-background text-on-surface antialiased flex h-screen w-screen">
<!-- SideNavBar -->
<aside class="h-screen w-56 flex flex-col bg-slate-100 dark:bg-slate-900 font-manrope text-sm font-medium border-r border-outline-variant/10 shrink-0">
<div class="flex flex-col h-full py-6">
<!-- Header -->
<div class="px-6 mb-8">
<div class="text-lg font-black text-slate-900 dark:text-slate-50 tracking-tight leading-none">VaultSentinel</div>
<div class="text-[10px] uppercase tracking-widest text-on-surface-variant mt-1.5 font-bold">Admin Console</div>
</div>
<!-- Primary Nav -->
<nav class="flex-1 px-3 space-y-1">
<a href="#" onclick="location.reload(); return false;" class="flex items-center gap-3 px-3 py-2 border-l-4 border-blue-600 bg-slate-200/80 dark:bg-slate-800 text-slate-900 dark:text-slate-50 font-bold group transition-all cursor-pointer">
<span class="material-symbols-outlined">dashboard</span>
<span>Dashboard</span>
</a>
<a href="applications.html" class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50 transition-all group cursor-pointer">
<span class="material-symbols-outlined">apps</span>
<span>Applications</span>
</a>
<a href="accounts.html" class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50 transition-all group cursor-pointer">
<span class="material-symbols-outlined">manage_accounts</span>
<span>Accounts</span>
</a>
</nav>
<!-- Footer -->
<div class="px-6 pt-4 border-t border-outline-variant/10">
<div class="text-[10px] font-bold text-on-surface-variant/40 uppercase tracking-widest">v1.0.0</div>
</div>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 flex flex-col h-screen min-w-0">
<!-- TopAppBar -->
<header class="h-14 flex items-center justify-between px-6 bg-slate-50/80 dark:bg-slate-950/80 backdrop-blur-xl border-b border-outline-variant/10 shrink-0">
<div class="flex items-center gap-4 flex-1">
<div class="flex items-center bg-surface-container-high px-3 py-1.5 rounded-full w-64 group focus-within:ring-2 ring-primary/20 transition-all">
<span class="material-symbols-outlined text-on-surface-variant text-base">search</span>
<input id="searchInput" class="bg-transparent border-none focus:ring-0 text-xs w-full placeholder:text-on-surface-variant/60 py-0" placeholder="Search resources..." type="text"/>
</div>
</div>
<div class="flex items-center gap-3">
<button class="p-1.5 rounded-full text-slate-500 hover:bg-slate-200 transition-colors">
<span class="material-symbols-outlined">notifications</span>
</button>
<button class="p-1.5 rounded-full text-slate-500 hover:bg-slate-200 transition-colors">
<span class="material-symbols-outlined">settings</span>
</button>
</div>
</header>
<!-- Main Content Area -->
<div id="mainContent" class="flex-1 overflow-hidden">
<!-- Content will be rendered here by JavaScript -->
</div>
</main>
<script src="app.js"></script>
</body>
</html>

76
navbar.js Normal file
View File

@@ -0,0 +1,76 @@
// Shared Navigation Module for VaultSentinel
// Renders sidebar navigation on all pages
function createNavbar(currentPage = 'dashboard') {
const navHTML = `
<!-- SideNavBar -->
<aside class="h-screen w-56 flex flex-col bg-slate-100 dark:bg-slate-900 font-manrope text-sm font-medium border-r border-outline-variant/10 shrink-0">
<div class="flex flex-col h-full py-6">
<!-- Header -->
<div class="px-6 mb-8">
<div class="text-lg font-black text-slate-900 dark:text-slate-50 tracking-tight leading-none">VaultSentinel</div>
<div class="text-[10px] uppercase tracking-widest text-on-surface-variant mt-1.5 font-bold">Admin Console</div>
</div>
<!-- Primary Nav -->
<nav class="flex-1 px-3 space-y-1">
<a href="index.html" class="flex items-center gap-3 px-3 py-2 ${currentPage === 'dashboard' ? 'border-l-4 border-blue-600 bg-slate-200/80 dark:bg-slate-800 text-slate-900 dark:text-slate-50 font-bold' : 'text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50'} transition-all group cursor-pointer">
<span class="material-symbols-outlined">dashboard</span>
<span>Dashboard</span>
</a>
<a href="applications.html" class="flex items-center gap-3 px-3 py-2 ${currentPage === 'applications' ? 'border-l-4 border-blue-600 bg-slate-200/80 dark:bg-slate-800 text-slate-900 dark:text-slate-50 font-bold' : 'text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50'} transition-all group cursor-pointer">
<span class="material-symbols-outlined">apps</span>
<span>Applications</span>
</a>
<a href="accounts.html" class="flex items-center gap-3 px-3 py-2 ${currentPage === 'accounts' ? 'border-l-4 border-blue-600 bg-slate-200/80 dark:bg-slate-800 text-slate-900 dark:text-slate-50 font-bold' : 'text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50'} transition-all group cursor-pointer">
<span class="material-symbols-outlined">manage_accounts</span>
<span>Accounts</span>
</a>
</nav>
<!-- Footer -->
<div class="px-6 pt-4 border-t border-outline-variant/10">
<div class="text-[10px] font-bold text-on-surface-variant/40 uppercase tracking-widest">v1.0.0</div>
</div>
</div>
</aside>
<!-- Header Bar -->
<header class="h-14 flex items-center justify-between px-6 bg-slate-50/80 dark:bg-slate-950/80 backdrop-blur-xl border-b border-outline-variant/10 shrink-0">
<div class="flex items-center gap-4 flex-1">
<div class="flex items-center bg-surface-container-high px-3 py-1.5 rounded-full w-64 group focus-within:ring-2 ring-primary/20 transition-all">
<span class="material-symbols-outlined text-on-surface-variant text-base">search</span>
<input id="searchInput" class="bg-transparent border-none focus:ring-0 text-xs w-full placeholder:text-on-surface-variant/60 py-0" placeholder="Search resources..." type="text"/>
</div>
</div>
<div class="flex items-center gap-3">
<button class="p-1.5 rounded-full text-slate-500 hover:bg-slate-200 transition-colors">
<span class="material-symbols-outlined">notifications</span>
</button>
<button class="p-1.5 rounded-full text-slate-500 hover:bg-slate-200 transition-colors">
<span class="material-symbols-outlined">settings</span>
</button>
</div>
</header>
`;
return navHTML;
}
// Function to inject navbar (call this from each page)
function injectNavbar(currentPage = 'dashboard') {
const navbarWrapper = document.createElement('div');
navbarWrapper.className = 'flex h-screen w-screen';
navbarWrapper.innerHTML = createNavbar(currentPage);
// Move body content into main section
const mainSection = document.createElement('main');
mainSection.className = 'flex-1 flex flex-col h-screen min-w-0 overflow-hidden';
mainSection.innerHTML = document.body.innerHTML;
// Clear body and rebuild structure
document.body.innerHTML = '';
document.body.classList.add('flex', 'h-screen', 'w-screen', 'antialiased');
// Insert navbar + main content
document.body.appendChild(navbarWrapper);
navbarWrapper.appendChild(mainSection);
}

242
start.html Normal file
View File

@@ -0,0 +1,242 @@
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VaultSentinel - Trung Tâm Điều Khiển</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container { max-width: 1000px; margin: 0 auto; }
.header { text-align: center; color: white; margin-bottom: 40px; }
.header h1 { font-size: 3em; margin-bottom: 10px; text-shadow: 2px 2px 4px rgba(0,0,0,0.2); }
.header p { font-size: 1.2em; opacity: 0.9; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-bottom: 40px; }
.card { background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.2); transition: transform 0.3s, box-shadow 0.3s; }
.card:hover { transform: translateY(-5px); box-shadow: 0 15px 40px rgba(0,0,0,0.3); }
.card-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; font-size: 1.3em; font-weight: bold; }
.card-body { padding: 20px; }
.card-body p { color: #666; line-height: 1.6; margin-bottom: 15px; }
.card-button { display: inline-block; background: #667eea; color: white; padding: 12px 20px; border-radius: 5px; text-decoration: none; font-weight: bold; transition: background 0.3s; }
.card-button:hover { background: #764ba2; }
.status-banner { background: white; border-radius: 10px; padding: 30px; margin-bottom: 40px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); }
.status-banner h2 { color: #667eea; margin-bottom: 20px; font-size: 1.5em; }
.feature-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px; margin: 20px 0; }
.feature-item { background: #f9f9f9; padding: 15px; border-radius: 5px; border-left: 4px solid #667eea; }
.feature-item strong { color: #667eea; }
.footer { text-align: center; color: white; padding: 20px; opacity: 0.9; }
.quick-start { background: white; border-radius: 10px; padding: 30px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); }
.quick-start h2 { color: #667eea; margin-bottom: 20px; }
.quick-start ol { margin-left: 20px; line-height: 2; }
.quick-start li { color: #333; }
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<div class="header">
<h1>🔐 VaultSentinel</h1>
<p>Ứng Dụng Quản Lý Tài Khoản Dịch Vụ - Phiên Bản 1.0.0</p>
</div>
<!-- Status Banner -->
<div class="status-banner">
<h2>✅ Ứng Dụng Đã Hoàn Thành!</h2>
<p style="color: #333; line-height: 1.8;">Bạn đã tích hợp 3 file HTML thành một ứng dụng web hoàn chỉnh. Ứng dụng hiện đã sẵn sàng để sử dụng và quản lý tài khoản của các dịch vụ/ứng dụng.</p>
<h3 style="color: #667eea; margin-top: 20px; margin-bottom: 15px;">🌟 Tính Năng Chính</h3>
<div class="feature-list">
<div class="feature-item">
<strong>📊 Dashboard</strong> - Xem tổng quan thống kê
</div>
<div class="feature-item">
<strong>👥 Quản Tài Khoản</strong> - Thêm/Sửa/Xóa tài khoản
</div>
<div class="feature-item">
<strong>🔧 Quản Ứng Dụng</strong> - Theo dõi các dịch vụ
</div>
<div class="feature-item">
<strong>💾 Lưu Trữ</strong> - Dữ liệu an toàn trên trình duyệt
</div>
<div class="feature-item">
<strong>📱 Responsive</strong> - Hoạt động trên mọi thiết bị
</div>
<div class="feature-item">
<strong>⚡ Offline</strong> - Hoàn toàn offline, không cần internet
</div>
</div>
</div>
<!-- Quick Start -->
<div class="quick-start">
<h2>🚀 Bắt Đầu Nhanh</h2>
<ol>
<li><strong>Mở ứng dụng:</strong> Nhấp nút "Mở VaultSentinel" bên dưới hoặc mở file <code>index.html</code></li>
<li><strong>Khám phá:</strong> Sử dụng menu bên trái để chuyển giữa các trang</li>
<li><strong>Thêm dữ liệu:</strong> Nhấp "Add Account" hoặc "Add New Application"</li>
<li><strong>Quản lý:</strong> Chỉnh sửa hoặc xóa các mục theo nhu cầu</li>
<li><strong>Tận hưởng:</strong> Tất cả dữ liệu được lưu tự động</li>
</ol>
</div>
<!-- Main Cards Grid -->
<div class="grid">
<!-- Main App Card -->
<div class="card">
<div class="card-header">🎯 Ứng Dụng Chính</div>
<div class="card-body">
<p>Mở ứng dụng VaultSentinel để quản lý tài khoản và dịch vụ của bạn.</p>
<a href="index.html" class="card-button">Mở VaultSentinel →</a>
</div>
</div>
<!-- Guide Card -->
<div class="card">
<div class="card-header">📖 Hướng Dẫn Nhanh</div>
<div class="card-body">
<p>Hướng dẫn chi tiết cách sử dụng ứng dụng với các ví dụ thực tế.</p>
<a href="GUIDE.html" class="card-button">Xem Hướng Dẫn →</a>
</div>
</div>
<!-- Documentation Card -->
<div class="card">
<div class="card-header">📚 Tài Liệu Chi Tiết</div>
<div class="card-body">
<p>Tài liệu đầy đủ về các tính năng, cách sử dụng, và tương lai phát triển.</p>
<a href="README.md" class="card-button">Đọc README →</a>
</div>
</div>
<!-- Summary Card -->
<div class="card">
<div class="card-header">📋 Tóm Tắt Dự Án</div>
<div class="card-body">
<p>Tóm tắt về các file được tạo, tính năng, và cấu trúc ứng dụng.</p>
<a href="SUMMARY.md" class="card-button">Xem Tóm Tắt →</a>
</div>
</div>
<!-- Checklist Card -->
<div class="card">
<div class="card-header">✅ Bảng Kiểm Tra</div>
<div class="card-body">
<p>Kiểm tra xem tất cả tính năng có được thiết lập đúng không.</p>
<a href="CHECK.html" class="card-button">Xem Kiểm Tra →</a>
</div>
</div>
<!-- Tech Stack Card -->
<div class="card">
<div class="card-header">🛠️ Công Nghệ</div>
<div class="card-body">
<p>
• HTML5 & CSS3<br>
• Tailwind CSS<br>
• JavaScript (ES6+)<br>
• localStorage API<br>
• Material Symbols
</p>
</div>
</div>
</div>
<!-- File Structure -->
<div class="status-banner">
<h2>📁 Cấu Trúc File</h2>
<table style="width: 100%; border-collapse: collapse; margin-top: 15px;">
<tr style="background: #f9f9f9;">
<th style="border: 1px solid #ddd; padding: 10px; text-align: left;">File</th>
<th style="border: 1px solid #ddd; padding: 10px; text-align: left;">Mô Tả</th>
<th style="border: 1px solid #ddd; padding: 10px; text-align: center;">Kích Thước</th>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 10px;"><strong>index.html</strong></td>
<td style="border: 1px solid #ddd; padding: 10px;">File HTML chính - Mở file này</td>
<td style="border: 1px solid #ddd; padding: 10px; text-align: center;">8.5 KB</td>
</tr>
<tr style="background: #f9f9f9;">
<td style="border: 1px solid #ddd; padding: 10px;"><strong>app.js</strong></td>
<td style="border: 1px solid #ddd; padding: 10px;">Logic JavaScript chính</td>
<td style="border: 1px solid #ddd; padding: 10px; text-align: center;">32.8 KB</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 10px;"><strong>README.md</strong></td>
<td style="border: 1px solid #ddd; padding: 10px;">Tài liệu hướng dẫn (Tiếng Việt)</td>
<td style="border: 1px solid #ddd; padding: 10px; text-align: center;">4.7 KB</td>
</tr>
<tr style="background: #f9f9f9;">
<td style="border: 1px solid #ddd; padding: 10px;"><strong>GUIDE.html</strong></td>
<td style="border: 1px solid #ddd; padding: 10px;">Hướng dẫn nhanh dạng web</td>
<td style="border: 1px solid #ddd; padding: 10px; text-align: center;">13 KB</td>
</tr>
<tr>
<td style="border: 1px solid #ddd; padding: 10px;"><strong>SUMMARY.md</strong></td>
<td style="border: 1px solid #ddd; padding: 10px;">Tóm tắt dự án và tính năng</td>
<td style="border: 1px solid #ddd; padding: 10px; text-align: center;">5.2 KB</td>
</tr>
<tr style="background: #f9f9f9;">
<td style="border: 1px solid #ddd; padding: 10px;"><strong>CHECK.html</strong></td>
<td style="border: 1px solid #ddd; padding: 10px;">Bảng kiểm tra tính năng</td>
<td style="border: 1px solid #ddd; padding: 10px; text-align: center;">7.5 KB</td>
</tr>
</table>
</div>
<!-- Important Notes -->
<div class="status-banner">
<h2>⚠️ Lưu Ý Quan Trọng</h2>
<div style="background: #fff3cd; border: 1px solid #ffc107; border-radius: 5px; padding: 15px; color: #856404; margin-top: 15px;">
<strong>🔒 Bảo Mật:</strong>
<ul style="margin: 10px 0 0 20px;">
<li>Dữ liệu được lưu cục bộ trên trình duyệt của bạn</li>
<li>KHÔNG gửi dữ liệu đến máy chủ nào</li>
<li>Chỉ sử dụng trên máy tính được tin cậy</li>
<li>KHÔNG sử dụng cho dữ liệu nhạy cảm trong môi trường thực tế</li>
<li>Xóa cache trình duyệt sẽ mất ALL dữ liệu</li>
</ul>
</div>
</div>
<!-- FAQ -->
<div class="status-banner">
<h2>❓ Câu Hỏi Thường Gặp</h2>
<h3 style="color: #667eea; margin-top: 20px;">Q: Tôi phải cài đặt gì không?</h3>
<p>A: Không! Chỉ cần mở file index.html trên trình duyệt. Ứng dụng không cần cài đặt hoặc backend.</p>
<h3 style="color: #667eea; margin-top: 15px;">Q: Dữ liệu của tôi được lưu ở đâu?</h3>
<p>A: Dữ liệu được lưu trong localStorage của trình duyệt. Mỗi trình duyệt/máy tính có storage riêng.</p>
<h3 style="color: #667eea; margin-top: 15px;">Q: Ứng dụng có hoạt động offline không?</h3>
<p>A: Có! Ứng dụng hoàn toàn offline. Lệnh gọi CDN lần đầu sẽ require internet, nhưng sau đó offline vẫn được.</p>
<h3 style="color: #667eea; margin-top: 15px;">Q: Tôi có thể sao lưu dữ liệu không?</h3>
<p>A: Hiện chưa có tính năng export/import. Bạn có thể dùng DevTools để export localStorage.</p>
</div>
<!-- Support -->
<div class="status-banner" style="text-align: center;">
<h2>🆘 Hỗ Trợ</h2>
<p style="color: #333; margin: 15px 0;">Nếu gặp vấn đề:</p>
<ol style="text-align: left; display: inline-block; line-height: 2;">
<li>Kiểm tra Console trình duyệt (F12) để xem lỗi</li>
<li>Xóa cache và reload trang (Ctrl+Shift+R)</li>
<li>Thử trình duyệt khác (Chrome, Firefox, Edge, Safari)</li>
<li>Đọc README.md hoặc GUIDE.html để xem thêm</li>
</ol>
</div>
<!-- Footer -->
<div class="footer" style="margin-top: 40px;">
<p><strong>VaultSentinel v1.0.0</strong> | Ứng Dụng Quản Lý Tài Khoản Dịch Vụ</p>
<p style="margin-top: 10px;">© 2026 - Tất cả quyền được bảo lưu</p>
<p style="margin-top: 10px; font-size: 0.9em;">Phát triển: 26 Tháng 3, 2026</p>
</div>
</div>
</body>
</html>