first
This commit is contained in:
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"liveServer.settings.multiRootWorkspaceName": "AccManager"
|
||||
}
|
||||
139
CHECK.html
Normal file
139
CHECK.html
Normal 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
248
GUIDE.html
Normal 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
137
README.md
Normal 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
585
accounts.html
Normal 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
511
app.js
Normal 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
575
applications.html
Normal 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
224
dialog.html
Normal 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&family=Inter:wght@400;500;600&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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
172
index.html
Normal 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
76
navbar.js
Normal 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
242
start.html
Normal 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>
|
||||
Reference in New Issue
Block a user