This commit is contained in:
2026-03-27 09:56:26 +07:00
parent 56ab9f931e
commit 251b4ee673
26 changed files with 3076 additions and 2364 deletions

15
.env Normal file
View File

@@ -0,0 +1,15 @@
# AccManager Backend Configuration
# Server Port
PORT=3000
# SQL Server Configuration
DB_SERVER=172.20.235.176
DB_USER=sa
DB_PASSWORD=robotics@2020
DB_NAME=AccManager
DB_ENCRYPT=true
DB_TRUST_CERTIFICATE=true
# Application
NODE_ENV=development

View File

@@ -1,139 +0,0 @@
<!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>

291
DATABASE_SETUP.md Normal file
View File

@@ -0,0 +1,291 @@
# AccManager Backend Setup Guide
## 📋 Database Information
**Server:** 172.20.235.176
**Database:** AccManager
**User:** sa
**Password:** robotics@2020
## 📊 Database Structure
### Tables Created
#### 1. **Users** - Quản lý người dùng
- `UserId` (INT) - Primary Key
- `Username` (NVARCHAR) - Unique
- `Password` (NVARCHAR)
- `Email` (NVARCHAR)
- `FullName` (NVARCHAR)
- `Role` (NVARCHAR) - admin, user, viewer
- `Status` (NVARCHAR) - Active/Inactive
- `CreatedDate` (DATETIME)
- `LastLogin` (DATETIME)
- `IsActive` (BIT)
#### 2. **Applications** - Danh sách ứng dụng
- `AppId` (INT) - Primary Key
- `Name` (NVARCHAR)
- `Type` (NVARCHAR) - Cloud, VCS, Collaboration, Infra
- `Status` (NVARCHAR) - online/offline
- `Icon` (NVARCHAR)
- `Description` (NVARCHAR)
- `CreatedDate` (DATETIME)
- `UpdatedDate` (DATETIME)
#### 3. **Accounts** - Tài khoản ứng dụng
- `AccountId` (INT) - Primary Key
- `UserId` (INT) - Foreign Key
- `AppId` (INT) - Foreign Key
- `AccountUsername` (NVARCHAR)
- `AccountPassword` (NVARCHAR)
- `Email` (NVARCHAR)
- `AccessLevel` (NVARCHAR)
- `Status` (NVARCHAR)
- `Notes` (NVARCHAR)
- `CreatedDate` (DATETIME)
- `UpdatedDate` (DATETIME)
#### 4. **AuditLog** - Nhật ký hoạt động
- `LogId` (INT) - Primary Key
- `UserId` (INT) - Foreign Key
- `Action` (NVARCHAR) - INSERT, UPDATE, DELETE
- `TableName` (NVARCHAR)
- `RecordId` (INT)
- `OldValue` (NVARCHAR)
- `NewValue` (NVARCHAR)
- `Timestamp` (DATETIME)
## 🔐 Default Admin Account
**Username:** admin
**Password:** admin
**Role:** admin
**Status:** Active
## 🚀 Installation & Setup
### 1. Install Node.js Dependencies
```bash
npm install
```
### 2. Run Backend Server
```bash
npm start
```
Server sẽ chạy tại: **http://localhost:3000**
### 3. Kiểm tra Database Connection
```bash
curl http://localhost:3000/api/health
```
Response:
```json
{
"status": "OK",
"database": "Connected"
}
```
## 📡 API Endpoints
### Authentication
#### Login
```bash
POST /api/auth/login
Content-Type: application/json
{
"username": "admin",
"password": "admin"
}
```
### Users
#### Get All Users
```bash
GET /api/users
```
#### Get User Details
```bash
GET /api/users/:id
```
#### Create New User
```bash
POST /api/users
Content-Type: application/json
{
"username": "newuser",
"password": "password123",
"email": "user@example.com",
"fullname": "Full Name",
"role": "user"
}
```
### Applications
#### Get All Applications
```bash
GET /api/applications
```
#### Create Application
```bash
POST /api/applications
Content-Type: application/json
{
"name": "New App",
"type": "Cloud",
"status": "online",
"icon": "cloud",
"description": "Application description"
}
```
### Accounts
#### Get User Accounts
```bash
GET /api/accounts/user/:userId
```
#### Create Account
```bash
POST /api/accounts
Content-Type: application/json
{
"userId": 1,
"appId": 1,
"accountUsername": "account_user",
"accountPassword": "account_pass",
"email": "account@example.com",
"accessLevel": "Admin",
"notes": "Account notes"
}
```
### Database Info
#### Get Database Statistics
```bash
GET /api/database/info
```
Response:
```json
{
"success": true,
"database": "AccManager",
"server": "172.20.235.176",
"tables": [
{
"TableName": "Accounts",
"ColumnCount": 11
},
{
"TableName": "Applications",
"ColumnCount": 7
},
{
"TableName": "AuditLog",
"ColumnCount": 8
},
{
"TableName": "Users",
"ColumnCount": 10
}
],
"statistics": {
"users": 1,
"applications": 4,
"accounts": 0
}
}
```
## 🔧 Frontend Integration
Update frontend API calls to use the backend server:
```javascript
// Change from localStorage to API calls
const API_URL = 'http://localhost:3000/api';
// Example: Login
async function login(username, password) {
const response = await fetch(`${API_URL}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
return response.json();
}
// Example: Get user accounts
async function getAccounts(userId) {
const response = await fetch(`${API_URL}/accounts/user/${userId}`);
return response.json();
}
```
## 📝 Initial Data Created
### Users
- **admin** (admin role)
### Applications
- AWS (Cloud) - online
- GitHub (VCS) - online
- Google Workspace (Collaboration) - online
- Nginx Proxy (Infra) - offline
## 🐛 Troubleshooting
### Connection Error
```
Database connection failed: Error
```
**Solution:**
- Kiểm tra SQL Server đang chạy
- Kiểm tra network connectivity đến 172.20.235.176
- Kiểm tra username/password đúng
- Kiểm tra SQL Server Authentication được enable
### Port Already in Use
```
listen EADDRINUSE: address already in use :::3000
```
**Solution:**
```bash
# Change port in .env
PORT=3001
```
### MSSQL Module Not Found
```bash
npm install mssql
```
## 📚 References
- [ExpressJS Documentation](https://expressjs.com/)
- [MSSQL Package](https://github.com/tediousjs/node-mssql)
- [SQL Server Documentation](https://docs.microsoft.com/en-us/sql/)
---
**Status:** ✓ Database created, ✓ Tables created, ✓ Admin user created, ✓ Backend ready

View File

@@ -1,248 +0,0 @@
<!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>

235
README.md
View File

@@ -1,137 +1,166 @@
# VaultSentinel - Account Management System
# 🎯 AccManager - SQL Server Backend Setup Complete
## 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:
## ✅ Database Configuration Complete
- **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)
SQL Server database **AccManager** has been successfully configured with all necessary tables and initial data.
## Các Tính Năng
### 📊 Database Information
### 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
```
Server IP: 172.20.235.176
Database: AccManager
User: sa
Password: robotics@2020
Port: 1433 (default)
```
### 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"
### 👤 Default Admin 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"
```
Username: admin
Password: admin
Role: admin
Status: Active
```
- **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
## 📋 Database Tables Created
- **Lọc Tài Khoản**:
- Sử dụng dropdown "Service" để lọc theo dịch vụ
### 1. **Users** - User Management
- Stores login credentials and user roles
- Default admin user: admin/admin
### 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
### 2. **Applications** - Service Management
- 4 sample applications pre-loaded:
- AWS (Cloud) - online
- GitHub (VCS) - online
- Google Workspace (Collaboration) - online
- Nginx Proxy (Infra) - offline
- **Thêm Ứng Dụng**:
- Nhấp "Add New"
- Điền tên, loại, trạng thái
- Nhấp "Save Application"
### 3. **Accounts** - Credential Storage
- Stores credentials for each user-application combination
- Linked to Users and Applications tables
- **Chỉnh Sửa Ứng Dụng**:
- Nhấp nút "Edit"
- Cập nhật thông tin
- Nhấp "Save Application"
### 4. **AuditLog** - Activity Tracking
- Logs all INSERT, UPDATE, DELETE operations
- User actions tracked for security
- **Xóa Ứng Dụng**:
- Nhấp nút "Delete"
- Xác nhận xóa
## 🚀 Backend Server Options
## Cách Sử Dụng
### Option 1⃣: Node.js + Express (Recommended)
### 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
**Files:**
- `server.js` - Main server file
- `package.json` - Dependencies
### Công Việc Thường Xuyên
**Quick Start:**
```bash
# 1. Install Node.js from https://nodejs.org/
# 2. Install dependencies
npm install
#### 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"
# 3. Run server
npm start
#### 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
# Server runs on: http://localhost:3000
```
#### 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
### Option 2⃣: Python + Flask
## Lưu Ý Quan Trọng
**Files:**
- `server_python.py` - Main server file
- `requirements.txt` - Dependencies
⚠️ **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ế
**Quick Start:**
```bash
# 1. Install Python 3.8+ from https://www.python.org/
# 2. Install dependencies
pip install -r requirements.txt
## Cấu Trúc File
# 3. Run server
python server_python.py
- `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)
# Server runs on: http://localhost:5000
```
## Các Công Nghệ Sử Dụng
## 📡 API Endpoints
- **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
### Health Check
```http
GET /api/health
```
## Mở Rộng Ứng Dụng
### Authentication
```http
POST /api/auth/login
```
### Để Thêm Chức Năng Mới:
### Users Management
```http
GET /api/users
GET /api/users/:id
POST /api/users
```
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
### Applications
```http
GET /api/applications
POST /api/applications
```
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
### Accounts
```http
GET /api/accounts/user/:userId
POST /api/accounts
```
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
### Database Info
```http
GET /api/database/info
```
## Hỗ Trợ
## 📚 Documentation Files
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
- **README.md** (this file) - Overview
- **SETUP_GUIDE.md** - Detailed installation steps
- **DATABASE_SETUP.md** - Schema and API documentation
- **server.js** - Node.js backend source
- **server_python.py** - Python backend source
## 🔐 Default Credentials
```
Username: admin
Password: admin
Role: admin
```
## 🔧 Project Files
```
d:\RoboticsSource\AccManager\
├── server.js (Node.js backend)
├── server_python.py (Python backend)
├── package.json (Node.js dependencies)
├── requirements.txt (Python dependencies)
├── .env (Configuration)
├── database/
│ └── setup.sql (SQL setup script)
├── SETUP_GUIDE.md (Installation guide)
├── DATABASE_SETUP.md (Database documentation)
└── README.md (This file)
```
## ✅ Status
- ✓ Database created (AccManager)
- ✓ 4 tables created (Users, Applications, Accounts, AuditLog)
- ✓ Admin user created (admin/admin)
- ✓ Sample applications added
- ✓ Backend servers ready (Node.js + Python options)
- ✓ API endpoints documented
---
**Phiên Bản**: 1.0.0
**Version:** 2.0.0 (Backend Ready)
**Database:** SQL Server / AccManager
**Last Updated:** March 27, 2026

220
RUN_COMMANDS.md Normal file
View File

@@ -0,0 +1,220 @@
# 🚀 Hướng Dẫn Chạy AccManager - Dành cho Người Dùng Mới
## 📋 Bước 1: Kiểm Tra Node.js
Mở **PowerShell** và chạy:
```powershell
node --version
npm --version
```
**Kết quả mong đợi:**
```
v18.16.0 (hoặc version cao hơn)
9.6.7 (hoặc version khác)
```
Nếu thấy version → **OK, tiếp tục bước 2**
---
## 📋 Bước 2: Vào Thư Mục Dự Án
Copy-paste lệnh này vào PowerShell:
```powershell
cd d:\RoboticsSource\AccManager
```
---
## 📋 Bước 3: Cài Đặt Dependencies
Copy-paste lệnh này vào PowerShell:
```powershell
npm install
```
**Chờ cho tới khi thấy:**
```
added XXX packages in XXs
```
(Sẽ mất 1-3 phút tùy tốc độ mạng)
---
## 📋 Bước 4: Chạy Backend Server
Copy-paste lệnh này vào PowerShell:
```powershell
npm start
```
**Khi thành công, bạn sẽ thấy:**
```
========================================
AccManager Backend Server
========================================
✓ Server running on http://localhost:3000
✓ Database: AccManager
✓ Default admin: admin / admin
API Endpoints:
POST /api/auth/login
GET /api/database/info
GET /api/users
GET /api/applications
GET /api/accounts/user/:userId
========================================
```
**👉 ĐỪNG ĐÓNG TERMINAL NÀY - Server đang chạy!**
---
## 🌐 Bước 5: Mở Ứng Dụng
**Mở trình duyệt** (Chrome, Firefox, Edge...) copy-paste URL này:
```
http://localhost:3000/pages/login.html
```
---
## 🔐 Bước 6: Đăng Nhập
**Nhập thông tin:**
- Username: `admin`
- Password: `admin`
Nhấp **Login**
---
## ✅ Nếu Thành Công
Bạn sẽ thấy:
- ✓ Trang Dashboard
- ✓ Quản lý Accounts
- ✓ Quản lý Applications
- ✓ Thông tin Database
---
## ❌ Nếu Có Lỗi
### Lỗi 1: "npm: The term 'npm' is not recognized"
**Giải pháp:**
- Đóng PowerShell hiện tại
- Mở PowerShell **mới**
- Chạy lại `npm --version`
### Lỗi 2: "Cannot find module 'express'"
**Giải pháp:**
```powershell
npm install
npm start
```
### Lỗi 3: "Port 3000 already in use"
**Giải pháp:** Có chương trình khác dùng port 3000
```powershell
# Dừng server cũ (Ctrl+C)
# Rồi chạy lại: npm start
```
### Lỗi 4: "Cannot connect to database"
**Giải pháp:** Kiểm tra SQL Server
- Đảm bảo SQL Server đang chạy
- IP: 172.20.235.176 có reach được không
```powershell
ping 172.20.235.176
```
---
## 💡 Full Workflow - Copy Toàn Bộ Lệnh
Nếu bạn muốn copy toàn bộ một lần:
```powershell
# Kiểm tra Node.js
node --version
# Vào thư mục
cd d:\RoboticsSource\AccManager
# Cài packages
npm install
# Chạy server
npm start
```
---
## 🎯 Các Lệnh Hữu Ích Khác
### Dừng Server
Nhấp **Ctrl + C** trong PowerShell
### Chạy Server Ở Chế Độ Development (auto-restart)
```powershell
npm run dev
```
### Kiểm Tra API Health
Mở browser khác nhập:
```
http://localhost:3000/api/health
```
### Xem Tất Cả Users
```
http://localhost:3000/api/users
```
### Xem Tất Cả Applications
```
http://localhost:3000/api/applications
```
### Database Info
```
http://localhost:3000/api/database/info
```
---
## 📊 Sơ Đồ Port
| URL | Port | Mục đích |
|-----|------|---------|
| http://localhost:3000/pages/login.html | 3000 | Frontend |
| http://localhost:3000/api/health | 3000 | Backend API |
| 172.20.235.176 | 1433 | SQL Server |
---
## 🎯 Tóm Tắt - 4 Bước Chính
1. **Mở PowerShell**
2. **Chạy:**
```powershell
cd d:\RoboticsSource\AccManager
npm install
npm start
```
3. **Chờ thấy "Server running on http://localhost:3000"**
4. **Mở browser vào: http://localhost:3000/pages/login.html**
5. **Đăng nhập: admin / admin**
---
**Chúc bạn thành công! 🚀**

294
SETUP_GUIDE.md Normal file
View File

@@ -0,0 +1,294 @@
# 🚀 AccManager Backend - Complete Setup Guide
## ⚠️ Pre-requisites
### 1. Install Node.js & npm
**Download từ:** https://nodejs.org/
**Khuyến khích:** LTS version (v18 hoặc mới hơn)
#### Kiểm tra installation:
```bash
node --version
npm --version
```
Expected output:
```
v18.* (or newer)
9.* (or newer)
```
### 2. Verify SQL Server Connection
Trước khi chạy backend, kiểm tra SQL Server:
```bash
ping 172.20.235.176
```
Nếu không ping được, kiểm tra:
- SQL Server đang chạy
- Firewall cho phép port 1433
- Network connectivity
---
## 📥 Setup Steps
### Step 1: Install Node Packages
```bash
cd d:\RoboticsSource\AccManager
npm install
```
Wait cho tới khi mô tả xuất hiện `added X packages`
### Step 2: Run Backend Server
```bash
npm start
```
**Expected Output:**
```
========================================
AccManager Backend Server
========================================
✓ Server running on http://localhost:3000
✓ Database: AccManager
✓ Default admin: admin / admin
API Endpoints:
POST /api/auth/login
GET /api/database/info
GET /api/users
GET /api/applications
GET /api/accounts/user/:userId
========================================
```
### Step 3: Test Connection
Mở terminal mới, chạy:
```bash
curl http://localhost:3000/api/health
```
**Response:**
```json
{
"status": "OK",
"database": "Connected"
}
```
### Step 4: Test Database Info
```bash
curl http://localhost:3000/api/database/info
```
---
## 📝 Database Structure
### Database Name: `AccManager`
#### Tables:
1. **Users** (1 admin account)
- Username: admin
- Password: admin
- Role: admin
2. **Applications** (4 sample apps)
- AWS
- GitHub
- Google Workspace
- Nginx Proxy
3. **Accounts** (empty, ready to use)
4. **AuditLog** (empty, for logging)
---
## 🧪 Test API Endpoints
### Test 1: Login
```bash
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d "{\"username\":\"admin\",\"password\":\"admin\"}"
```
### Test 2: Get Users
```bash
curl http://localhost:3000/api/users
```
### Test 3: Get Applications
```bash
curl http://localhost:3000/api/applications
```
### Test 4: View Database Info
```bash
curl http://localhost:3000/api/database/info
```
---
## 🔌 Frontend Integration
Update your frontend to connect to the backend:
### Option 1: Update app.js
```javascript
const API_URL = 'http://localhost:3000/api';
// Update login function
async function login(username, password) {
const response = await fetch(`${API_URL}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (data.success) {
localStorage.setItem('currentUser', JSON.stringify(data.user));
window.location.href = './pages/accounts.html';
} else {
alert('Login failed: ' + data.message);
}
}
// Update get accounts function
async function getAccounts(userId) {
const response = await fetch(`${API_URL}/accounts/user/${userId}`);
const data = await response.json();
return data.data || [];
}
```
---
## 📦 Project Structure
```
d:\RoboticsSource\AccManager\
├── server.js # Backend server (Node.js)
├── package.json # Dependencies
├── .env # Configuration
├── DATABASE_SETUP.md # This file
├── database/
│ └── setup.sql # SQL setup script
├── index.html # Frontend entry
├── pages/
│ ├── login.html
│ ├── accounts.html
│ ├── applications.html
│ └── index.html
└── js/
└── app.js # Frontend logic
```
---
## 🔒 Security Notes
⚠️ **For Development Only:**
- Admin password is hardcoded as "admin"
- SQL credentials in code (not recommended for production)
- CORS enabled for all origins
### For Production:
1. Use environment variables
2. Hash passwords with bcrypt
3. Implement JWT authentication
4. Use firewalls and VPNs
5. Enable SSL/TLS
---
## 🐛 Troubleshooting
### Error: "Cannot find module 'express'"
```bash
npm install
```
### Error: "Connection failed"
Check:
- SQL Server running
- IP address correct: 172.20.235.176
- Port 1433 accessible
- Username/password correct
### Error: "Port 3000 already in use"
```bash
# Change port in .env
PORT=3001
# Then restart server
npm start
```
### Error: "CORS error in browser"
This is normal during development. The backend already has CORS enabled.
---
## 📊 Database Credentials
```
Server: 172.20.235.176
Database: AccManager
User: sa
Password: robotics@2020
```
---
## ✅ Verification Checklist
- [ ] Node.js installed
- [ ] npm packages installed (`npm install`)
- [ ] Backend server running (`npm start`)
- [ ] Can access http://localhost:3000/api/health
- [ ] Can login with admin/admin
- [ ] Database shows tables and statistics
- [ ] Frontend connects to backend
---
## 📚 Additional Commands
### Install Development Tools
```bash
npm install -D nodemon
npm run dev # Auto-restart on code changes
```
### Check npm packages
```bash
npm list
```
### Update packages
```bash
npm update
```
### Clear npm cache
```bash
npm cache clean --force
```
---
**For Support:** Check server logs for error messages

View File

@@ -1,585 +0,0 @@
<!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>

View File

@@ -1,575 +0,0 @@
<!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>

63
database/run-setup.ps1 Normal file
View File

@@ -0,0 +1,63 @@
# PowerShell Script to Execute SQL Server Setup
# Database: AccManager
# Server: 172.20.235.176
# SQL Server Connection Info
$ServerName = "172.20.235.176"
$Username = "sa"
$Password = "robotics@2020"
$SqlScriptPath = ".\setup.sql"
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "AccManager Database Setup" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
# Check if sqlcmd is available
if (-not (Get-Command sqlcmd -ErrorAction SilentlyContinue)) {
Write-Host "ERROR: sqlcmd is not available on this system." -ForegroundColor Red
Write-Host "Please install SQL Server Command Line Tools (sqlcmd)" -ForegroundColor Yellow
Write-Host "Download from: https://docs.microsoft.com/en-us/sql/tools/sqlcmd-utility" -ForegroundColor Yellow
exit
}
# Check if SQL script file exists
if (-not (Test-Path $SqlScriptPath)) {
Write-Host "ERROR: SQL script file not found: $SqlScriptPath" -ForegroundColor Red
exit
}
Write-Host "Server: $ServerName" -ForegroundColor Green
Write-Host "User: $Username" -ForegroundColor Green
Write-Host "Script: $SqlScriptPath" -ForegroundColor Green
Write-Host ""
# Execute SQL Script
Write-Host "Executing SQL script..." -ForegroundColor Yellow
Write-Host ""
try {
sqlcmd -S $ServerName -U $Username -P $Password -i $SqlScriptPath -o "setup_output.log"
Write-Host ""
Write-Host "========================================" -ForegroundColor Green
Write-Host "SETUP COMPLETED SUCCESSFULLY!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host ""
Write-Host "Output saved to: setup_output.log" -ForegroundColor Cyan
Write-Host ""
# Display output
Write-Host "Setup Output:" -ForegroundColor Cyan
Get-Content "setup_output.log"
}
catch {
Write-Host "ERROR executing SQL script:" -ForegroundColor Red
Write-Host $_.Exception.Message -ForegroundColor Red
exit
}
Write-Host ""
Write-Host "========================================" -ForegroundColor Green
Write-Host "Database is ready to use!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green

168
database/setup.sql Normal file
View File

@@ -0,0 +1,168 @@
-- ===========================================
-- SQL Server Setup Script for AccManager
-- Database: AccManager
-- Server: 172.20.235.176
-- ===========================================
-- Create Database
IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = 'AccManager')
BEGIN
CREATE DATABASE AccManager;
PRINT 'Database AccManager created successfully.';
END
ELSE
BEGIN
PRINT 'Database AccManager already exists.';
END
USE AccManager;
-- ===========================================
-- 1. CREATE USERS TABLE
-- ===========================================
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Users')
BEGIN
CREATE TABLE Users (
UserId INT PRIMARY KEY IDENTITY(1,1),
Username NVARCHAR(50) UNIQUE NOT NULL,
Password NVARCHAR(255) NOT NULL,
Email NVARCHAR(100),
FullName NVARCHAR(100),
Role NVARCHAR(50) NOT NULL,
Status NVARCHAR(20) DEFAULT 'Active',
CreatedDate DATETIME DEFAULT GETDATE(),
LastLogin DATETIME,
IsActive BIT DEFAULT 1
);
PRINT 'Table Users created successfully.';
END
-- ===========================================
-- 2. CREATE APPLICATIONS TABLE
-- ===========================================
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Applications')
BEGIN
CREATE TABLE Applications (
AppId INT PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(100) NOT NULL,
Type NVARCHAR(50),
Status NVARCHAR(20) DEFAULT 'online',
Icon NVARCHAR(50),
Description NVARCHAR(500),
CreatedDate DATETIME DEFAULT GETDATE(),
UpdatedDate DATETIME DEFAULT GETDATE()
);
PRINT 'Table Applications created successfully.';
END
-- ===========================================
-- 3. CREATE ACCOUNTS TABLE
-- ===========================================
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Accounts')
BEGIN
CREATE TABLE Accounts (
AccountId INT PRIMARY KEY IDENTITY(1,1),
UserId INT NOT NULL,
AppId INT NOT NULL,
AccountUsername NVARCHAR(100),
AccountPassword NVARCHAR(255),
Email NVARCHAR(100),
AccessLevel NVARCHAR(50),
Status NVARCHAR(20) DEFAULT 'Active',
Notes NVARCHAR(MAX),
CreatedDate DATETIME DEFAULT GETDATE(),
UpdatedDate DATETIME DEFAULT GETDATE(),
FOREIGN KEY (UserId) REFERENCES Users(UserId) ON DELETE CASCADE,
FOREIGN KEY (AppId) REFERENCES Applications(AppId) ON DELETE CASCADE
);
PRINT 'Table Accounts created successfully.';
END
-- ===========================================
-- 4. CREATE AUDIT LOG TABLE
-- ===========================================
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'AuditLog')
BEGIN
CREATE TABLE AuditLog (
LogId INT PRIMARY KEY IDENTITY(1,1),
UserId INT,
Action NVARCHAR(50),
TableName NVARCHAR(50),
RecordId INT,
OldValue NVARCHAR(MAX),
NewValue NVARCHAR(MAX),
Timestamp DATETIME DEFAULT GETDATE(),
FOREIGN KEY (UserId) REFERENCES Users(UserId)
);
PRINT 'Table AuditLog created successfully.';
END
-- ===========================================
-- 5. CREATE INDEXES
-- ===========================================
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_Users_Username')
BEGIN
CREATE INDEX IX_Users_Username ON Users(Username);
END
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_Accounts_UserId')
BEGIN
CREATE INDEX IX_Accounts_UserId ON Accounts(UserId);
END
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_Accounts_AppId')
BEGIN
CREATE INDEX IX_Accounts_AppId ON Accounts(AppId);
END
PRINT 'Indexes created successfully.';
-- ===========================================
-- 6. INSERT INITIAL DATA
-- ===========================================
-- Check if admin user exists
IF NOT EXISTS (SELECT * FROM Users WHERE Username = 'admin')
BEGIN
INSERT INTO Users (Username, Password, Email, FullName, Role, Status, IsActive)
VALUES ('admin', 'admin', 'admin@accmanager.local', 'Administrator', 'admin', 'Active', 1);
PRINT 'Admin user created: Username=admin, Password=admin';
END
-- Insert sample applications
IF (SELECT COUNT(*) FROM Applications) = 0
BEGIN
INSERT INTO Applications (Name, Type, Status, Icon, Description)
VALUES
('AWS', 'Cloud', 'online', 'cloud', 'Amazon Web Services - Cloud Computing'),
('GitHub', 'VCS', 'online', 'code', 'GitHub - Version Control System'),
('Google Workspace', 'Collaboration', 'online', 'mail', 'Google Workspace - Email and Collaboration'),
('Nginx Proxy', 'Infra', 'offline', 'dns', 'Nginx - Web Server and Reverse Proxy');
PRINT 'Sample applications inserted successfully.';
END
-- ===========================================
-- 7. DISPLAY DATABASE INFORMATION
-- ===========================================
PRINT '';
PRINT '========================================';
PRINT 'DATABASE SETUP COMPLETED SUCCESSFULLY';
PRINT '========================================';
PRINT '';
PRINT 'Database Name: AccManager';
PRINT '';
PRINT 'Tables created:';
SELECT ' - ' + name AS TableName FROM sys.tables ORDER BY name;
PRINT '';
PRINT 'Users in system:';
SELECT ' Username: ' + Username + ' | Role: ' + Role + ' | Status: ' + Status AS UserInfo FROM Users;
PRINT '';
PRINT 'Applications available:';
SELECT ' - ' + Name + ' (' + Type + ') - ' + Status AS AppInfo FROM Applications ORDER BY Name;
PRINT '';
PRINT 'Login Credentials:';
PRINT ' Username: admin';
PRINT ' Password: admin';
PRINT ' Role: admin';
PRINT '';
PRINT '========================================';

View File

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

134
docs/README.md Normal file
View File

@@ -0,0 +1,134 @@
# Robot Account Manager - Project Structure
## Directory Organization
```
AccManager/
├── pages/ # HTML pages
│ ├── login.html # Login page (entry point)
│ ├── index.html # Dashboard
│ ├── accounts.html # Accounts management
│ └── applications.html # Applications management
├── js/ # JavaScript files
│ └── app.js # Main application logic
├── docs/ # Documentation
│ └── README.md # This file
├── index.html # Root redirect to pages/login.html
├── .git/ # Version control
├── .vscode/ # VS Code settings
└── README.md # Project root README
```
## Quick Start
1. **Access the application:**
- Open `index.html` in a browser
- You will be automatically redirected to `pages/login.html`
2. **Login Credentials (Demo):**
- Username: `admin`
- Password: `admin`
3. **Features:**
- Dashboard: Overview of accounts and applications
- Accounts: Manage user accounts for various services
- Applications: Manage connected applications/services
## File Structure Explanation
### /pages/
Contains all HTML pages with updated relative paths for script imports:
- `login.html` - Authentication page
- `index.html` - Main dashboard
- `accounts.html` - Accounts management interface
- `applications.html` - Applications management interface
All pages reference scripts using `../js/app.js` for correct path resolution.
### /js/
Application logic and state management:
- `app.js` - Main AccountManager class with all functionality
### /docs/
Documentation files for reference and development.
## Application Architecture
### AccountManager Class (app.js)
- **Storage**: Uses localStorage for data persistence
- **Pages**: Dynamically renders content based on user navigation
- **Authentication**: Checks for valid session on page load
- **Modals**: Manages adding/editing accounts and applications
### Data Storage
Application stores data in browser's localStorage:
- `currentUser` - Logged-in user information
- `accounts` - List of managed accounts
- `applications` - List of managed applications
- `rememberedUsername` - Optional saved username
## Development Notes
- **Framework**: Tailwind CSS for styling
- **Icons**: Material Design symbols
- **Storage**: Browser localStorage (client-side only)
- **Responsive**: Built with mobile-first approach
- **Modern**: ES6+ JavaScript features
## Navigation Flow
```
index.html (root)
pages/login.html
↓ (after login)
pages/index.html (dashboard)
├→ pages/accounts.html
└→ pages/applications.html
```
## Features
### Authentication
- Username/password login
- Session management
- Remember me functionality
- Logout with confirmation
### Dashboard
- Overview statistics
- Recent account activity
- Quick access to management pages
### Account Management
- Add new accounts
- Edit existing accounts
- Delete accounts
- Filter by service
- View account details
### Application Management
- Add new applications
- Edit application details
- Delete applications
- View application status (online/offline)
- Health monitoring
## Browser Compatibility
- Modern browsers (Chrome, Firefox, Safari, Edge)
- Requires JavaScript enabled
- Uses localStorage API
## Future Improvements
- Server-side authentication
- Database integration
- Advanced filtering and search
- User roles and permissions
- Audit logging
- Dark mode toggle persistence
---
Generated: March 27, 2026
Version: 1.0.0

View File

@@ -1,172 +1,12 @@
<!DOCTYPE html>
<html class="light" lang="en">
<html>
<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"},
},
},
}
<title>Redirecting...</title>
<script>
window.location.href = './pages/login.html';
</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>
Redirecting to login...
</body>
</html>

35
install-nodejs.ps1 Normal file
View File

@@ -0,0 +1,35 @@
# Install Node.js automatically (Run as Administrator)
Write-Host "Installing Node.js LTS..." -ForegroundColor Green
# Check if winget is available
if (Get-Command winget -ErrorAction SilentlyContinue) {
Write-Host "Found winget, installing Node.js..." -ForegroundColor Cyan
winget install OpenJS.NodeJS
}
else {
Write-Host "winget not found. Downloading Node.js installer..." -ForegroundColor Yellow
# Download Node.js LTS
$NodeURL = "https://nodejs.org/dist/v18.16.0/node-v18.16.0-x64.msi"
$InstallerPath = "$env:TEMP\nodejs-installer.msi"
Write-Host "Downloading from: $NodeURL"
Invoke-WebRequest -Uri $NodeURL -OutFile $InstallerPath -UseBasicParsing
Write-Host "Running installer..." -ForegroundColor Cyan
Start-Process -FilePath "msiexec.exe" -ArgumentList "/i $InstallerPath /quiet /norestart" -Wait
Remove-Item $InstallerPath
}
Write-Host "Installation complete! Refreshing environment..." -ForegroundColor Green
# Refresh PATH
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
Write-Host "Checking installation..." -ForegroundColor Cyan
node --version
npm --version
Write-Host "Ready to run npm install!" -ForegroundColor Green

View File

@@ -3,6 +3,14 @@
class AccountManager {
constructor() {
// Check if user is logged in
const currentUser = this.loadFromStorage('currentUser');
if (!currentUser) {
window.location.href = '../pages/login.html';
return;
}
this.currentUser = currentUser;
this.accounts = this.loadFromStorage('accounts') || [];
this.applications = this.loadFromStorage('applications') || [
{ id: 1, name: 'AWS', type: 'Cloud', status: 'online', icon: 'cloud' },
@@ -52,10 +60,36 @@ class AccountManager {
appForm.addEventListener('submit', (e) => this.handleAppSubmit(e));
}
// Logout button
const logoutBtn = document.getElementById('logoutBtn');
if (logoutBtn) {
logoutBtn.addEventListener('click', () => this.handleLogout());
}
// Update account display
this.updateAccountDisplay();
// Account table row clicks
this.setupAccountRowListeners();
}
updateAccountDisplay() {
// Use the logged-in user from constructor
const usernameEl = document.getElementById('accountUsername');
const roleEl = document.getElementById('accountRole');
if (usernameEl) usernameEl.textContent = this.currentUser?.username || 'User';
if (roleEl) roleEl.textContent = this.currentUser?.role || 'Administrator';
}
handleLogout() {
if (confirm('Are you sure you want to logout?')) {
this.saveToStorage('currentUser', null);
localStorage.clear();
window.location.href = '../pages/login.html';
}
}
renderDashboard() {
return `
<div class="flex-1 flex flex-col p-6 space-y-6 min-h-0 overflow-auto">
@@ -66,7 +100,7 @@ class AccountManager {
<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">
<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>
@@ -125,7 +159,7 @@ class AccountManager {
</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>
<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>
@@ -385,7 +419,7 @@ class AccountManager {
if (confirm('Delete this account?')) {
this.accounts.splice(accountId, 1);
this.saveToStorage('accounts', this.accounts);
location.href = 'accounts.html';
location.href = './accounts.html';
}
});
});
@@ -410,7 +444,7 @@ class AccountManager {
if (confirm('Delete this application?')) {
this.applications.splice(appId, 1);
this.saveToStorage('applications', this.applications);
location.href = 'applications.html';
location.href = './applications.html';
}
});
});
@@ -447,7 +481,7 @@ class AccountManager {
this.saveToStorage('accounts', this.accounts);
this.closeModals();
location.href = 'accounts.html';
location.href = './accounts.html';
}
handleAppSubmit(e) {
@@ -468,7 +502,7 @@ class AccountManager {
this.saveToStorage('applications', this.applications);
this.closeModals();
location.href = 'applications.html';
location.href = './applications.html';
}
openAccountModal() {

View File

@@ -1,76 +0,0 @@
// 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);
}

22
package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "accmanager-backend",
"version": "1.0.0",
"description": "Backend server for AccManager application",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"keywords": ["accmanager", "backend", "express", "mssql"],
"author": "",
"license": "MIT",
"dependencies": {
"express": "^4.18.2",
"mssql": "^9.1.1",
"cors": "^2.8.5",
"dotenv": "^16.3.1"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}

152
pages/accounts.html Normal file
View File

@@ -0,0 +1,152 @@
<!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; 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">
<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">
<div class="px-6 mb-8">
<div class="text-lg font-black text-slate-900 dark:text-slate-50 tracking-tight leading-none">Robot Account</div>
<div class="text-[10px] uppercase tracking-widest text-on-surface-variant mt-1.5 font-bold">Admin Console</div>
</div>
<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 group transition-all cursor-pointer">
<span class="material-symbols-outlined">manage_accounts</span>
<span>Accounts</span>
</a>
</nav>
<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 class="flex-1 flex flex-col h-screen min-w-0">
<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-4">
<div class="flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-100 dark:bg-slate-800">
<span class="material-symbols-outlined text-slate-600 dark:text-slate-400">account_circle</span>
<div class="flex flex-col">
<span id="accountUsername" class="text-xs font-semibold text-slate-900 dark:text-slate-50">User Account</span>
<span id="accountRole" class="text-[10px] text-slate-500 dark:text-slate-400">Administrator</span>
</div>
</div>
<button id="logoutBtn" class="p-2 rounded-lg text-slate-600 dark:text-slate-300 hover:bg-red-100 dark:hover:bg-red-950 hover:text-red-700 dark:hover:text-red-300 transition-colors" title="Logout">
<span class="material-symbols-outlined">logout</span>
</button>
</div>
</header>
<div id="mainContent" class="flex-1 overflow-hidden"></div>
</main>
<script src="../js/app.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
if (app) {
const mainContent = document.getElementById('mainContent');
if (mainContent) {
mainContent.innerHTML = app.getAccountsContent();
app.setupAccountRowListeners();
}
}
});
</script>
</body>
</html>

152
pages/applications.html Normal file
View File

@@ -0,0 +1,152 @@
<!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; 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">
<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">
<div class="px-6 mb-8">
<div class="text-lg font-black text-slate-900 dark:text-slate-50 tracking-tight leading-none">Robot Account</div>
<div class="text-[10px] uppercase tracking-widest text-on-surface-variant mt-1.5 font-bold">Admin Console</div>
</div>
<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 group transition-all 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>
<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 class="flex-1 flex flex-col h-screen min-w-0">
<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-4">
<div class="flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-100 dark:bg-slate-800">
<span class="material-symbols-outlined text-slate-600 dark:text-slate-400">account_circle</span>
<div class="flex flex-col">
<span id="accountUsername" class="text-xs font-semibold text-slate-900 dark:text-slate-50">User Account</span>
<span id="accountRole" class="text-[10px] text-slate-500 dark:text-slate-400">Administrator</span>
</div>
</div>
<button id="logoutBtn" class="p-2 rounded-lg text-slate-600 dark:text-slate-300 hover:bg-red-100 dark:hover:bg-red-950 hover:text-red-700 dark:hover:text-red-300 transition-colors" title="Logout">
<span class="material-symbols-outlined">logout</span>
</button>
</div>
</header>
<div id="mainContent" class="flex-1 overflow-hidden"></div>
</main>
<script src="../js/app.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
if (app) {
const mainContent = document.getElementById('mainContent');
if (mainContent) {
mainContent.innerHTML = app.getApplicationsContent();
app.setupAccountRowListeners();
}
}
});
</script>
</body>
</html>

176
pages/index.html Normal file
View File

@@ -0,0 +1,176 @@
<!DOCTYPE html>
<html class="light" lang="en">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Robot Manager Account - 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">Robot Account</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-4">
<div class="flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-100 dark:bg-slate-800">
<span class="material-symbols-outlined text-slate-600 dark:text-slate-400">account_circle</span>
<div class="flex flex-col">
<span id="accountUsername" class="text-xs font-semibold text-slate-900 dark:text-slate-50">User Account</span>
<span id="accountRole" class="text-[10px] text-slate-500 dark:text-slate-400">Administrator</span>
</div>
</div>
<button id="logoutBtn" class="p-2 rounded-lg text-slate-600 dark:text-slate-300 hover:bg-red-100 dark:hover:bg-red-950 hover:text-red-700 dark:hover:text-red-300 transition-colors" title="Logout">
<span class="material-symbols-outlined">logout</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="../js/app.js"></script>
</body>
</html>

254
pages/login.html Normal file
View File

@@ -0,0 +1,254 @@
<!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 - Login</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; min-height: 100vh; }
h1, h2, h3 { font-family: 'Manrope', sans-serif; }
</style>
</head>
<body class="bg-gradient-to-br from-blue-50 via-background to-purple-50 dark:from-slate-950 dark:via-slate-900 dark:to-slate-950 antialiased">
<div class="min-h-screen flex items-center justify-center px-4">
<div class="w-full max-w-md">
<!-- Login Card -->
<div class="bg-white dark:bg-slate-900 rounded-2xl shadow-lg border border-outline-variant/10 p-8">
<!-- Header -->
<div class="text-center mb-8">
<div class="flex justify-center mb-4">
<div class="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center">
<span class="material-symbols-outlined text-primary text-4xl">security</span>
</div>
</div>
<h1 class="text-2xl font-black text-slate-900 dark:text-slate-50 tracking-tight">VaultSentinel</h1>
<p class="text-xs uppercase tracking-widest text-on-surface-variant font-bold mt-2">Account Management System</p>
</div>
<!-- Login Form -->
<form id="loginForm" class="space-y-5">
<!-- Username/Email Input -->
<div>
<label for="username" class="block text-xs font-bold text-on-surface-variant uppercase tracking-wider mb-2">Username or Email</label>
<div class="relative">
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-on-surface-variant/60">
<span class="material-symbols-outlined text-base">person</span>
</span>
<input
type="text"
id="username"
name="username"
placeholder="Enter your username"
required
class="w-full pl-10 pr-4 py-2.5 bg-surface-container-low border border-outline-variant/30 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent transition-all text-sm font-medium"
/>
</div>
</div>
<!-- Password Input -->
<div>
<label for="password" class="block text-xs font-bold text-on-surface-variant uppercase tracking-wider mb-2">Password</label>
<div class="relative">
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-on-surface-variant/60">
<span class="material-symbols-outlined text-base">lock</span>
</span>
<input
type="password"
id="password"
name="password"
placeholder="Enter your password"
required
class="w-full pl-10 pr-4 py-2.5 bg-surface-container-low border border-outline-variant/30 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent transition-all text-sm font-medium"
/>
</div>
</div>
<!-- Remember Me Checkbox -->
<div class="flex items-center">
<input
type="checkbox"
id="remember"
name="remember"
class="w-4 h-4 rounded border-outline-variant/30 text-primary focus:ring-2 focus:ring-primary/50 cursor-pointer"
/>
<label for="remember" class="ml-2.5 text-xs font-medium text-on-surface-variant cursor-pointer">Remember me</label>
</div>
<!-- Login Button -->
<button
type="submit"
class="w-full bg-primary hover:bg-primary-dim text-on-primary font-bold py-2.5 px-4 rounded-lg transition-all active:scale-95 duration-100 flex items-center justify-center gap-2 mt-6"
>
<span class="material-symbols-outlined text-sm">login</span>
<span>Sign In</span>
</button>
<!-- Error Message -->
<div id="errorMessage" class="hidden bg-error-container/20 text-error/80 border border-error/30 rounded-lg px-4 py-3 text-xs font-medium"></div>
</form>
<!-- Footer -->
<div class="mt-8 pt-6 border-t border-outline-variant/10 text-center">
<p class="text-[10px] text-on-surface-variant/60">Default credentials for demo: admin / admin</p>
</div>
</div>
<!-- Bottom info -->
<div class="text-center mt-6">
<p class="text-xs text-on-surface-variant/60">v1.0.0</p>
</div>
</div>
</div>
<script>
// Simple login functionality
const loginForm = document.getElementById('loginForm');
const errorMessage = document.getElementById('errorMessage');
const usernameInput = document.getElementById('username');
const passwordInput = document.getElementById('password');
const rememberCheckbox = document.getElementById('remember');
// Demo credentials
const validCredentials = {
username: 'admin',
password: 'admin'
};
// Check if already logged in
document.addEventListener('DOMContentLoaded', () => {
const currentUser = localStorage.getItem('currentUser');
if (currentUser) {
window.location.href = './index.html';
}
// Restore remembered username
const rememberedUsername = localStorage.getItem('rememberedUsername');
if (rememberedUsername) {
usernameInput.value = rememberedUsername;
rememberCheckbox.checked = true;
}
});
loginForm.addEventListener('submit', (e) => {
e.preventDefault();
errorMessage.classList.add('hidden');
const username = usernameInput.value.trim();
const password = passwordInput.value;
// Validate credentials
if (username === validCredentials.username && password === validCredentials.password) {
// Store user info
const userData = {
username: username,
role: 'Administrator',
loginTime: new Date().toISOString()
};
localStorage.setItem('currentUser', JSON.stringify(userData));
// Handle remember me
if (rememberCheckbox.checked) {
localStorage.setItem('rememberedUsername', username);
} else {
localStorage.removeItem('rememberedUsername');
}
// Redirect to dashboard
window.location.href = './index.html';
} else {
// Show error
errorMessage.textContent = 'Invalid username or password. Try admin / admin';
errorMessage.classList.remove('hidden');
passwordInput.value = '';
passwordInput.focus();
}
});
</script>
</body>
</html>

4
requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
Flask==2.3.2
Flask-CORS==4.0.0
pyodbc==4.0.35
python-dotenv==1.0.0

438
server.js Normal file
View File

@@ -0,0 +1,438 @@
// Backend Server for AccManager
// Express.js + mssql
const express = require('express');
const sql = require('mssql');
const cors = require('cors');
const app = express();
// Middleware
app.use(cors());
app.use(express.json());
// SQL Server Configuration
const sqlConfig = {
server: '172.20.235.176',
authentication: {
type: 'default',
options: {
userName: 'sa',
password: 'robotics@2020'
}
},
options: {
database: 'AccManager',
trustServerCertificate: true,
enableKeepAlive: true,
connectTimeout: 30000
}
};
// Initialize Database Pool
let pool;
async function initializeDatabase() {
try {
pool = new sql.ConnectionPool(sqlConfig);
await pool.connect();
console.log('✓ Connected to SQL Server');
// Check and create database if not exists
const masterConnection = new sql.ConnectionPool({
server: '172.20.235.176',
authentication: { type: 'default', options: { userName: 'sa', password: 'robotics@2020' } },
options: { connectTimeout: 30000, database: 'master', trustServerCertificate: true }
});
await masterConnection.connect();
const createDbResult = await masterConnection.request()
.query(`IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = 'AccManager')
BEGIN
CREATE DATABASE AccManager;
END`);
await masterConnection.close();
// Now create tables in AccManager
await createTables();
console.log('✓ Database and tables created');
} catch (err) {
console.error('Database connection failed:', err);
process.exit(1);
}
}
async function createTables() {
const queries = [
// Users Table
`IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Users')
BEGIN
CREATE TABLE Users (
UserId INT PRIMARY KEY IDENTITY(1,1),
Username NVARCHAR(50) UNIQUE NOT NULL,
Password NVARCHAR(255) NOT NULL,
Email NVARCHAR(100),
FullName NVARCHAR(100),
Role NVARCHAR(50) NOT NULL,
Status NVARCHAR(20) DEFAULT 'Active',
CreatedDate DATETIME DEFAULT GETDATE(),
LastLogin DATETIME,
IsActive BIT DEFAULT 1
)
END`,
// Applications Table
`IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Applications')
BEGIN
CREATE TABLE Applications (
AppId INT PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(100) NOT NULL,
Type NVARCHAR(50),
Status NVARCHAR(20) DEFAULT 'online',
Icon NVARCHAR(50),
Description NVARCHAR(500),
CreatedDate DATETIME DEFAULT GETDATE(),
UpdatedDate DATETIME DEFAULT GETDATE()
)
END`,
// Accounts Table
`IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Accounts')
BEGIN
CREATE TABLE Accounts (
AccountId INT PRIMARY KEY IDENTITY(1,1),
UserId INT NOT NULL,
AppId INT NOT NULL,
AccountUsername NVARCHAR(100),
AccountPassword NVARCHAR(255),
Email NVARCHAR(100),
AccessLevel NVARCHAR(50),
Status NVARCHAR(20) DEFAULT 'Active',
Notes NVARCHAR(MAX),
CreatedDate DATETIME DEFAULT GETDATE(),
UpdatedDate DATETIME DEFAULT GETDATE(),
FOREIGN KEY (UserId) REFERENCES Users(UserId) ON DELETE CASCADE,
FOREIGN KEY (AppId) REFERENCES Applications(AppId) ON DELETE CASCADE
)
END`,
// AuditLog Table
`IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'AuditLog')
BEGIN
CREATE TABLE AuditLog (
LogId INT PRIMARY KEY IDENTITY(1,1),
UserId INT,
Action NVARCHAR(50),
TableName NVARCHAR(50),
RecordId INT,
OldValue NVARCHAR(MAX),
NewValue NVARCHAR(MAX),
Timestamp DATETIME DEFAULT GETDATE(),
FOREIGN KEY (UserId) REFERENCES Users(UserId)
)
END`
];
for (let query of queries) {
try {
await pool.request().query(query);
} catch (err) {
console.error('Table creation error:', err.message);
}
}
// Insert initial admin user
try {
await pool.request()
.input('username', sql.NVarChar, 'admin')
.input('password', sql.NVarChar, 'admin')
.input('email', sql.NVarChar, 'admin@accmanager.local')
.input('fullname', sql.NVarChar, 'Administrator')
.input('role', sql.NVarChar, 'admin')
.query(`IF NOT EXISTS (SELECT * FROM Users WHERE Username = @username)
INSERT INTO Users (Username, Password, Email, FullName, Role, IsActive)
VALUES (@username, @password, @email, @fullname, @role, 1)`);
console.log('✓ Admin user created: admin / admin');
} catch (err) {
console.error('Admin user error:', err.message);
}
// Insert sample applications
try {
await pool.request()
.query(`IF (SELECT COUNT(*) FROM Applications) = 0
BEGIN
INSERT INTO Applications (Name, Type, Status, Icon, Description)
VALUES
('AWS', 'Cloud', 'online', 'cloud', 'Amazon Web Services'),
('GitHub', 'VCS', 'online', 'code', 'GitHub - Version Control'),
('Google Workspace', 'Collaboration', 'online', 'mail', 'Google Workspace'),
('Nginx Proxy', 'Infra', 'offline', 'dns', 'Nginx Web Server')
END`);
console.log('✓ Sample applications created');
} catch (err) {
console.error('Applications error:', err.message);
}
}
// ==========================================
// API ROUTES - Authentication
// ==========================================
// Login endpoint
app.post('/api/auth/login', async (req, res) => {
try {
const { username, password } = req.body;
const result = await pool.request()
.input('username', sql.NVarChar, username)
.input('password', sql.NVarChar, password)
.query('SELECT UserId, Username, Email, FullName, Role, Status FROM Users WHERE Username = @username AND Password = @password AND IsActive = 1');
if (result.recordset.length > 0) {
const user = result.recordset[0];
// Update last login
await pool.request()
.input('userId', sql.Int, user.UserId)
.query('UPDATE Users SET LastLogin = GETDATE() WHERE UserId = @userId');
res.json({
success: true,
message: 'Login successful',
user: user
});
} else {
res.status(401).json({
success: false,
message: 'Invalid username or password'
});
}
} catch (err) {
console.error('Login error:', err);
res.status(500).json({ success: false, message: err.message });
}
});
// ==========================================
// API ROUTES - Users
// ==========================================
// Get all users
app.get('/api/users', async (req, res) => {
try {
const result = await pool.request()
.query('SELECT UserId, Username, Email, FullName, Role, Status, CreatedDate FROM Users ORDER BY CreatedDate DESC');
res.json({ success: true, data: result.recordset });
} catch (err) {
res.status(500).json({ success: false, message: err.message });
}
});
// Get user by ID
app.get('/api/users/:id', async (req, res) => {
try {
const result = await pool.request()
.input('userId', sql.Int, req.params.id)
.query('SELECT * FROM Users WHERE UserId = @userId');
if (result.recordset.length > 0) {
res.json({ success: true, data: result.recordset[0] });
} else {
res.status(404).json({ success: false, message: 'User not found' });
}
} catch (err) {
res.status(500).json({ success: false, message: err.message });
}
});
// Create new user
app.post('/api/users', async (req, res) => {
try {
const { username, password, email, fullname, role } = req.body;
const result = await pool.request()
.input('username', sql.NVarChar, username)
.input('password', sql.NVarChar, password)
.input('email', sql.NVarChar, email)
.input('fullname', sql.NVarChar, fullname)
.input('role', sql.NVarChar, role)
.query(`INSERT INTO Users (Username, Password, Email, FullName, Role, IsActive)
VALUES (@username, @password, @email, @fullname, @role, 1);
SELECT SCOPE_IDENTITY() as UserId`);
res.json({ success: true, message: 'User created', userId: result.recordset[0].UserId });
} catch (err) {
res.status(500).json({ success: false, message: err.message });
}
});
// ==========================================
// API ROUTES - Applications
// ==========================================
// Get all applications
app.get('/api/applications', async (req, res) => {
try {
const result = await pool.request()
.query('SELECT * FROM Applications ORDER BY Name');
res.json({ success: true, data: result.recordset });
} catch (err) {
res.status(500).json({ success: false, message: err.message });
}
});
// Create application
app.post('/api/applications', async (req, res) => {
try {
const { name, type, status, icon, description } = req.body;
const result = await pool.request()
.input('name', sql.NVarChar, name)
.input('type', sql.NVarChar, type)
.input('status', sql.NVarChar, status)
.input('icon', sql.NVarChar, icon)
.input('description', sql.NVarChar, description)
.query(`INSERT INTO Applications (Name, Type, Status, Icon, Description)
VALUES (@name, @type, @status, @icon, @description);
SELECT SCOPE_IDENTITY() as AppId`);
res.json({ success: true, message: 'Application created', appId: result.recordset[0].AppId });
} catch (err) {
res.status(500).json({ success: false, message: err.message });
}
});
// ==========================================
// API ROUTES - Accounts
// ==========================================
// Get accounts for a user
app.get('/api/accounts/user/:userId', async (req, res) => {
try {
const result = await pool.request()
.input('userId', sql.Int, req.params.userId)
.query(`SELECT a.*, app.Name as AppName, app.Type as AppType
FROM Accounts a
JOIN Applications app ON a.AppId = app.AppId
WHERE a.UserId = @userId
ORDER BY a.CreatedDate DESC`);
res.json({ success: true, data: result.recordset });
} catch (err) {
res.status(500).json({ success: false, message: err.message });
}
});
// Create account
app.post('/api/accounts', async (req, res) => {
try {
const { userId, appId, accountUsername, accountPassword, email, accessLevel, notes } = req.body;
const result = await pool.request()
.input('userId', sql.Int, userId)
.input('appId', sql.Int, appId)
.input('accountUsername', sql.NVarChar, accountUsername)
.input('accountPassword', sql.NVarChar, accountPassword)
.input('email', sql.NVarChar, email)
.input('accessLevel', sql.NVarChar, accessLevel)
.input('notes', sql.NVarChar, notes)
.query(`INSERT INTO Accounts (UserId, AppId, AccountUsername, AccountPassword, Email, AccessLevel, Notes)
VALUES (@userId, @appId, @accountUsername, @accountPassword, @email, @accessLevel, @notes);
SELECT SCOPE_IDENTITY() as AccountId`);
res.json({ success: true, message: 'Account created', accountId: result.recordset[0].AccountId });
} catch (err) {
res.status(500).json({ success: false, message: err.message });
}
});
// ==========================================
// API ROUTES - Database Info
// ==========================================
// Get database information
app.get('/api/database/info', async (req, res) => {
try {
const tables = await pool.request().query(`
SELECT TABLE_NAME as TableName,
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = t.TABLE_NAME) as ColumnCount
FROM INFORMATION_SCHEMA.TABLES t
WHERE TABLE_SCHEMA = 'dbo'
ORDER BY TABLE_NAME
`);
const users = await pool.request().query('SELECT COUNT(*) as Count FROM Users');
const apps = await pool.request().query('SELECT COUNT(*) as Count FROM Applications');
const accounts = await pool.request().query('SELECT COUNT(*) as Count FROM Accounts');
res.json({
success: true,
database: 'AccManager',
server: '172.20.235.176',
tables: tables.recordset,
statistics: {
users: users.recordset[0].Count,
applications: apps.recordset[0].Count,
accounts: accounts.recordset[0].Count
}
});
} catch (err) {
res.status(500).json({ success: false, message: err.message });
}
});
// Health check
app.get('/api/health', (req, res) => {
res.json({ status: 'OK', database: 'Connected' });
});
// ==========================================
// Error Handling
// ==========================================
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ success: false, message: err.message });
});
// ==========================================
// Server Startup
// ==========================================
const PORT = process.env.PORT || 3000;
async function startServer() {
try {
await initializeDatabase();
app.listen(PORT, () => {
console.log(`\n========================================`);
console.log(`AccManager Backend Server`);
console.log(`========================================`);
console.log(`✓ Server running on http://localhost:${PORT}`);
console.log(`✓ Database: AccManager`);
console.log(`✓ Default admin: admin / admin`);
console.log(`\nAPI Endpoints:`);
console.log(` POST /api/auth/login`);
console.log(` GET /api/database/info`);
console.log(` GET /api/users`);
console.log(` GET /api/applications`);
console.log(` GET /api/accounts/user/:userId`);
console.log(`========================================\n`);
});
} catch (err) {
console.error('Failed to start server:', err);
process.exit(1);
}
}
// Graceful shutdown
process.on('SIGINT', async () => {
console.log('\nShutting down...');
if (pool) {
await pool.close();
}
process.exit(0);
});
startServer();

480
server_python.py Normal file
View File

@@ -0,0 +1,480 @@
# AccManager Backend Server (Python Alternative)
# Requirements: Python 3.8+, Flask, pyodbc
from flask import Flask, request, jsonify, render_template
from flask_cors import CORS
from datetime import datetime
import pyodbc
import json
import os
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
CORS(app)
# SQL Server Connection Settings
DB_SERVER = os.getenv('DB_SERVER', '172.20.235.176')
DB_USER = os.getenv('DB_USER', 'sa')
DB_PASSWORD = os.getenv('DB_PASSWORD', 'robotics@2020')
DB_NAME = os.getenv('DB_NAME', 'AccManager')
# Connection string
CONNECTION_STRING = f'''
Driver={{ODBC Driver 17 for SQL Server}};
Server={DB_SERVER};
Database={DB_NAME};
UID={DB_USER};
PWD={DB_PASSWORD};
TrustServerCertificate=yes;
'''
# Initialize database connection
def get_db_connection():
try:
conn = pyodbc.connect(CONNECTION_STRING)
return conn
except Exception as e:
print(f"Database connection error: {e}")
return None
# Create tables
def create_tables():
conn = get_db_connection()
if not conn:
return False
cursor = conn.cursor()
try:
# Users table
cursor.execute('''
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Users')
BEGIN
CREATE TABLE Users (
UserId INT PRIMARY KEY IDENTITY(1,1),
Username NVARCHAR(50) UNIQUE NOT NULL,
Password NVARCHAR(255) NOT NULL,
Email NVARCHAR(100),
FullName NVARCHAR(100),
Role NVARCHAR(50) NOT NULL,
Status NVARCHAR(20) DEFAULT 'Active',
CreatedDate DATETIME DEFAULT GETDATE(),
LastLogin DATETIME,
IsActive BIT DEFAULT 1
)
END
''')
# Applications table
cursor.execute('''
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Applications')
BEGIN
CREATE TABLE Applications (
AppId INT PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(100) NOT NULL,
Type NVARCHAR(50),
Status NVARCHAR(20) DEFAULT 'online',
Icon NVARCHAR(50),
Description NVARCHAR(500),
CreatedDate DATETIME DEFAULT GETDATE(),
UpdatedDate DATETIME DEFAULT GETDATE()
)
END
''')
# Accounts table
cursor.execute('''
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Accounts')
BEGIN
CREATE TABLE Accounts (
AccountId INT PRIMARY KEY IDENTITY(1,1),
UserId INT NOT NULL,
AppId INT NOT NULL,
AccountUsername NVARCHAR(100),
AccountPassword NVARCHAR(255),
Email NVARCHAR(100),
AccessLevel NVARCHAR(50),
Status NVARCHAR(20) DEFAULT 'Active',
Notes NVARCHAR(MAX),
CreatedDate DATETIME DEFAULT GETDATE(),
UpdatedDate DATETIME DEFAULT GETDATE(),
FOREIGN KEY (UserId) REFERENCES Users(UserId) ON DELETE CASCADE,
FOREIGN KEY (AppId) REFERENCES Applications(AppId) ON DELETE CASCADE
)
END
''')
# AuditLog table
cursor.execute('''
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'AuditLog')
BEGIN
CREATE TABLE AuditLog (
LogId INT PRIMARY KEY IDENTITY(1,1),
UserId INT,
Action NVARCHAR(50),
TableName NVARCHAR(50),
RecordId INT,
OldValue NVARCHAR(MAX),
NewValue NVARCHAR(MAX),
Timestamp DATETIME DEFAULT GETDATE(),
FOREIGN KEY (UserId) REFERENCES Users(UserId)
)
END
''')
conn.commit()
# Insert admin user
cursor.execute('''
IF NOT EXISTS (SELECT * FROM Users WHERE Username = 'admin')
INSERT INTO Users (Username, Password, Email, FullName, Role, IsActive)
VALUES ('admin', 'admin', 'admin@accmanager.local', 'Administrator', 'admin', 1)
''')
# Insert sample applications
cursor.execute('''
IF (SELECT COUNT(*) FROM Applications) = 0
BEGIN
INSERT INTO Applications (Name, Type, Status, Icon, Description)
VALUES
('AWS', 'Cloud', 'online', 'cloud', 'Amazon Web Services'),
('GitHub', 'VCS', 'online', 'code', 'GitHub - Version Control'),
('Google Workspace', 'Collaboration', 'online', 'mail', 'Google Workspace'),
('Nginx Proxy', 'Infra', 'offline', 'dns', 'Nginx Web Server')
END
''')
conn.commit()
cursor.close()
conn.close()
return True
except Exception as e:
print(f"Table creation error: {e}")
return False
# ==========================================
# Routes: Health Check
# ==========================================
@app.route('/api/health', methods=['GET'])
def health():
conn = get_db_connection()
if conn:
conn.close()
return jsonify({"status": "OK", "database": "Connected"}), 200
else:
return jsonify({"status": "ERROR", "database": "Disconnected"}), 500
# ==========================================
# Routes: Authentication
# ==========================================
@app.route('/api/auth/login', methods=['POST'])
def login():
try:
data = request.get_json()
username = data.get('username')
password = data.get('password')
conn = get_db_connection()
if not conn:
return jsonify({"success": False, "message": "Database connection failed"}), 500
cursor = conn.cursor()
cursor.execute('''
SELECT UserId, Username, Email, FullName, Role, Status
FROM Users
WHERE Username = ? AND Password = ? AND IsActive = 1
''', (username, password))
user = cursor.fetchone()
if user:
cursor.execute('UPDATE Users SET LastLogin = GETDATE() WHERE UserId = ?', user[0])
conn.commit()
return jsonify({
"success": True,
"message": "Login successful",
"user": {
"UserId": user[0],
"username": user[1],
"email": user[2],
"fullname": user[3],
"role": user[4],
"status": user[5]
}
}), 200
else:
return jsonify({
"success": False,
"message": "Invalid username or password"
}), 401
except Exception as e:
return jsonify({"success": False, "message": str(e)}), 500
finally:
if conn:
conn.close()
# ==========================================
# Routes: Users
# ==========================================
@app.route('/api/users', methods=['GET'])
def get_users():
try:
conn = get_db_connection()
if not conn:
return jsonify({"success": False, "message": "Database connection failed"}), 500
cursor = conn.cursor()
cursor.execute('''
SELECT UserId, Username, Email, FullName, Role, Status, CreatedDate
FROM Users
ORDER BY CreatedDate DESC
''')
users = []
for row in cursor.fetchall():
users.append({
"UserId": row[0],
"Username": row[1],
"Email": row[2],
"FullName": row[3],
"Role": row[4],
"Status": row[5],
"CreatedDate": str(row[6])
})
return jsonify({"success": True, "data": users}), 200
except Exception as e:
return jsonify({"success": False, "message": str(e)}), 500
finally:
if conn:
conn.close()
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
try:
conn = get_db_connection()
if not conn:
return jsonify({"success": False, "message": "Database connection failed"}), 500
cursor = conn.cursor()
cursor.execute('SELECT * FROM Users WHERE UserId = ?', (user_id,))
user = cursor.fetchone()
if user:
return jsonify({
"success": True,
"data": {"UserId": user[0], "Username": user[1]}
}), 200
else:
return jsonify({"success": False, "message": "User not found"}), 404
except Exception as e:
return jsonify({"success": False, "message": str(e)}), 500
finally:
if conn:
conn.close()
@app.route('/api/users', methods=['POST'])
def create_user():
try:
data = request.get_json()
conn = get_db_connection()
if not conn:
return jsonify({"success": False, "message": "Database connection failed"}), 500
cursor = conn.cursor()
cursor.execute('''
INSERT INTO Users (Username, Password, Email, FullName, Role, IsActive)
VALUES (?, ?, ?, ?, ?, 1)
''', (data['username'], data['password'], data['email'],
data['fullname'], data['role']))
conn.commit()
return jsonify({
"success": True,
"message": "User created"
}), 201
except Exception as e:
return jsonify({"success": False, "message": str(e)}), 500
finally:
if conn:
conn.close()
# ==========================================
# Routes: Applications
# ==========================================
@app.route('/api/applications', methods=['GET'])
def get_applications():
try:
conn = get_db_connection()
if not conn:
return jsonify({"success": False, "message": "Database connection failed"}), 500
cursor = conn.cursor()
cursor.execute('SELECT * FROM Applications ORDER BY Name')
apps = []
for row in cursor.fetchall():
apps.append({
"AppId": row[0],
"Name": row[1],
"Type": row[2],
"Status": row[3],
"Icon": row[4],
"Description": row[5]
})
return jsonify({"success": True, "data": apps}), 200
except Exception as e:
return jsonify({"success": False, "message": str(e)}), 500
finally:
if conn:
conn.close()
# ==========================================
# Routes: Accounts
# ==========================================
@app.route('/api/accounts/user/<int:user_id>', methods=['GET'])
def get_accounts(user_id):
try:
conn = get_db_connection()
if not conn:
return jsonify({"success": False, "message": "Database connection failed"}), 500
cursor = conn.cursor()
cursor.execute('''
SELECT a.*, app.Name, app.Type
FROM Accounts a
JOIN Applications app ON a.AppId = app.AppId
WHERE a.UserId = ?
ORDER BY a.CreatedDate DESC
''', (user_id,))
accounts = []
for row in cursor.fetchall():
accounts.append({
"AccountId": row[0],
"UserId": row[1],
"AppId": row[2],
"AccountUsername": row[3],
"AccountPassword": row[4],
"AppName": row[12],
"AppType": row[13]
})
return jsonify({"success": True, "data": accounts}), 200
except Exception as e:
return jsonify({"success": False, "message": str(e)}), 500
finally:
if conn:
conn.close()
# ==========================================
# Routes: Database Info
# ==========================================
@app.route('/api/database/info', methods=['GET'])
def database_info():
try:
conn = get_db_connection()
if not conn:
return jsonify({"success": False, "message": "Database connection failed"}), 500
cursor = conn.cursor()
# Get tables
cursor.execute('''
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'dbo'
ORDER BY TABLE_NAME
''')
tables = [row[0] for row in cursor.fetchall()]
# Get statistics
cursor.execute('SELECT COUNT(*) FROM Users')
user_count = cursor.fetchone()[0]
cursor.execute('SELECT COUNT(*) FROM Applications')
app_count = cursor.fetchone()[0]
cursor.execute('SELECT COUNT(*) FROM Accounts')
account_count = cursor.fetchone()[0]
return jsonify({
"success": True,
"database": "AccManager",
"server": DB_SERVER,
"tables": tables,
"statistics": {
"users": user_count,
"applications": app_count,
"accounts": account_count
}
}), 200
except Exception as e:
return jsonify({"success": False, "message": str(e)}), 500
finally:
if conn:
conn.close()
# ==========================================
# Error Handlers
# ==========================================
@app.errorhandler(404)
def not_found(error):
return jsonify({"error": "Not found"}), 404
@app.errorhandler(500)
def server_error(error):
return jsonify({"error": "Internal server error"}), 500
# ==========================================
# Startup
# ==========================================
if __name__ == '__main__':
print("\n========================================")
print("AccManager Backend Server (Python)")
print("========================================")
print(f"Server: {DB_SERVER}")
print(f"Database: {DB_NAME}")
print(f"User: {DB_USER}")
# Initialize database
print("\nInitializing database...")
if create_tables():
print("✓ Database tables created/verified")
else:
print("✗ Database initialization failed")
print("\n✓ Server running on http://localhost:5000")
print("\nAPI Endpoints:")
print(" /api/health")
print(" /api/auth/login")
print(" /api/users")
print(" /api/applications")
print(" /api/accounts/user/<id>")
print(" /api/database/info")
print("========================================\n")
app.run(debug=True, host='localhost', port=5000)

View File

@@ -1,242 +0,0 @@
<!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>