second
This commit is contained in:
15
.env
Normal file
15
.env
Normal 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
|
||||||
139
CHECK.html
139
CHECK.html
@@ -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
291
DATABASE_SETUP.md
Normal 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
|
||||||
248
GUIDE.html
248
GUIDE.html
@@ -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
235
README.md
@@ -1,137 +1,166 @@
|
|||||||
# VaultSentinel - Account Management System
|
# 🎯 AccManager - SQL Server Backend Setup Complete
|
||||||
|
|
||||||
## Giới Thiệu
|
## ✅ Database Configuration Complete
|
||||||
VaultSentinel là một ứng dụng web quản lý tài khoản dịch vụ/ứng dụng. Ứng dụng cho phép bạn:
|
|
||||||
|
|
||||||
- **Quản lý Tài Khoản**: Thêm, sửa, xóa tài khoản cho các dịch vụ
|
SQL Server database **AccManager** has been successfully configured with all necessary tables and initial data.
|
||||||
- **Quản lý Ứng Dụng**: Theo dõi các dịch vụ/ứng dụng đang sử dụng
|
|
||||||
- **Dashboard**: Xem tổng quan số lượng tài khoản và dịch vụ
|
|
||||||
- **Lưu Trữ Dữ Liệu**: Tất cả dữ liệu được lưu trên trình duyệt (localStorage)
|
|
||||||
|
|
||||||
## Các Tính Năng
|
### 📊 Database Information
|
||||||
|
|
||||||
### 1. Dashboard
|
```
|
||||||
- Xem tổng quan các thống kê:
|
Server IP: 172.20.235.176
|
||||||
- Số lượng ứng dụng đang hoạt động
|
Database: AccManager
|
||||||
- Tổng số tài khoản được quản lý
|
User: sa
|
||||||
- Ngày cập nhật cuối cùng
|
Password: robotics@2020
|
||||||
- Trạng thái hệ thống
|
Port: 1433 (default)
|
||||||
- Xem các tài khoản gần đây được tạo
|
```
|
||||||
|
|
||||||
### 2. Quản Lý Tài Khoản (Accounts)
|
### 👤 Default Admin Account
|
||||||
- **Thêm Tài Khoản Mới**:
|
|
||||||
- Nhấp nút "Add Account"
|
|
||||||
- Điền thông tin: Service, Owner, Username, Password
|
|
||||||
- Nhấp "Save Account"
|
|
||||||
|
|
||||||
- **Chỉnh Sửa Tài Khoản**:
|
```
|
||||||
- Nhấp nút "Edit" (biểu tượng bút chì) trên dòng tài khoản
|
Username: admin
|
||||||
- Cập nhật thông tin
|
Password: admin
|
||||||
- Nhấp "Save Account"
|
Role: admin
|
||||||
|
Status: Active
|
||||||
|
```
|
||||||
|
|
||||||
- **Xóa Tài Khoản**:
|
## 📋 Database Tables Created
|
||||||
- Nhấp nút "Delete" (biểu tượng thùng rác)
|
|
||||||
- Xác nhận xóa
|
|
||||||
|
|
||||||
- **Lọc Tài Khoản**:
|
### 1. **Users** - User Management
|
||||||
- Sử dụng dropdown "Service" để lọc theo dịch vụ
|
- Stores login credentials and user roles
|
||||||
|
- Default admin user: admin/admin
|
||||||
|
|
||||||
### 3. Quản Lý Ứng Dụng (Applications)
|
### 2. **Applications** - Service Management
|
||||||
- **Xem Danh Sách**:
|
- 4 sample applications pre-loaded:
|
||||||
- Danh sách tất cả các ứng dụng/dịch vụ
|
- AWS (Cloud) - online
|
||||||
- Hiển thị trạng thái (Online/Offline)
|
- GitHub (VCS) - online
|
||||||
- Hiển thị loại ứng dụng
|
- Google Workspace (Collaboration) - online
|
||||||
|
- Nginx Proxy (Infra) - offline
|
||||||
|
|
||||||
- **Thêm Ứng Dụng**:
|
### 3. **Accounts** - Credential Storage
|
||||||
- Nhấp "Add New"
|
- Stores credentials for each user-application combination
|
||||||
- Điền tên, loại, trạng thái
|
- Linked to Users and Applications tables
|
||||||
- Nhấp "Save Application"
|
|
||||||
|
|
||||||
- **Chỉnh Sửa Ứng Dụng**:
|
### 4. **AuditLog** - Activity Tracking
|
||||||
- Nhấp nút "Edit"
|
- Logs all INSERT, UPDATE, DELETE operations
|
||||||
- Cập nhật thông tin
|
- User actions tracked for security
|
||||||
- Nhấp "Save Application"
|
|
||||||
|
|
||||||
- **Xóa Ứng Dụng**:
|
## 🚀 Backend Server Options
|
||||||
- Nhấp nút "Delete"
|
|
||||||
- Xác nhận xóa
|
|
||||||
|
|
||||||
## Cách Sử Dụng
|
### Option 1️⃣: Node.js + Express (Recommended)
|
||||||
|
|
||||||
### Khởi Động Ứng Dụng
|
**Files:**
|
||||||
1. Mở file `index.html` trên trình duyệt
|
- `server.js` - Main server file
|
||||||
2. Giao diện chính sẽ tải lên
|
- `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
|
# 3. Run server
|
||||||
1. Nhấp "Accounts" trên thanh điều hướng bên trái
|
npm start
|
||||||
2. Nhấp nút "Add Account"
|
|
||||||
3. Chọn dịch vụ từ dropdown
|
|
||||||
4. Nhập tên chủ sở hữu, tên đăng nhập, mật khẩu
|
|
||||||
5. Nhấp "Save Account"
|
|
||||||
|
|
||||||
#### Xem Danh Sách Ứng Dụng
|
# Server runs on: http://localhost:3000
|
||||||
1. Nhấp "Applications" trên thanh điều hướng
|
```
|
||||||
2. Xem danh sách tất cả các ứng dụng đã đăng ký
|
|
||||||
3. Sử dụng các nút hành động để chỉnh sửa hoặc xóa
|
|
||||||
|
|
||||||
#### Kiểm Tra Tổng Quan
|
### Option 2️⃣: Python + Flask
|
||||||
1. Nhấp "Dashboard" trên thanh điều hướng
|
|
||||||
2. Xem các thống kê chính và hoạt động gần đây
|
|
||||||
|
|
||||||
## Lưu Ý Quan Trọng
|
**Files:**
|
||||||
|
- `server_python.py` - Main server file
|
||||||
|
- `requirements.txt` - Dependencies
|
||||||
|
|
||||||
⚠️ **Bảo Mật**:
|
**Quick Start:**
|
||||||
- Tất cả dữ liệu được lưu trữ cục bộ trên trình duyệt (localStorage)
|
```bash
|
||||||
- Không gửi dữ liệu đến máy chủ nào
|
# 1. Install Python 3.8+ from https://www.python.org/
|
||||||
- Dữ liệu sẽ bị xóa nếu bạn xóa cache trình duyệt
|
# 2. Install dependencies
|
||||||
- Đâ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ế
|
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
|
# Server runs on: http://localhost:5000
|
||||||
- `app.js` - Mã JavaScript xử lý logic và chức năng
|
```
|
||||||
- `acc.html` - Trang tài khoản cũ (tham khảo)
|
|
||||||
- `app.html` - Trang ứng dụng cũ (tham khảo)
|
|
||||||
- `main.html` - File HTML thay thế (tham khảo)
|
|
||||||
|
|
||||||
## Các Công Nghệ Sử Dụng
|
## 📡 API Endpoints
|
||||||
|
|
||||||
- **HTML5**: Cấu trúc trang
|
### Health Check
|
||||||
- **CSS (Tailwind CSS)**: Tạo cách bố trí và kiểu dáng
|
```http
|
||||||
- **JavaScript**: Xử lý logic ứng dụng
|
GET /api/health
|
||||||
- **Material Symbols**: Biểu tượng
|
```
|
||||||
- **localStorage API**: Lưu trữ dữ liệu
|
|
||||||
|
|
||||||
## 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**:
|
### Applications
|
||||||
- Xác định nội dung trong `getApplicationsContent()`
|
```http
|
||||||
- Thêm hàm xử lý sự kiện
|
GET /api/applications
|
||||||
- Cập nhật `renderPage()` để thêm trang mới
|
POST /api/applications
|
||||||
|
```
|
||||||
|
|
||||||
2. **Cải Thiện Bảo Mật**:
|
### Accounts
|
||||||
- Thêm mã hóa cho mật khẩu
|
```http
|
||||||
- Triển khai xác thực người dùng
|
GET /api/accounts/user/:userId
|
||||||
- Sử dụng database backend
|
POST /api/accounts
|
||||||
|
```
|
||||||
|
|
||||||
3. **Tính Năng Khác**:
|
### Database Info
|
||||||
- Xuất/Nhập dữ liệu
|
```http
|
||||||
- Nhật ký hoạt động
|
GET /api/database/info
|
||||||
- Tìm kiếm nâng cao
|
```
|
||||||
- Sao lưu/Khôi phục
|
|
||||||
|
|
||||||
## Hỗ Trợ
|
## 📚 Documentation Files
|
||||||
|
|
||||||
Nếu bạn gặp vấn đề:
|
- **README.md** (this file) - Overview
|
||||||
1. Kiểm tra console trình duyệt (F12) để xem lỗi
|
- **SETUP_GUIDE.md** - Detailed installation steps
|
||||||
2. Xóa cache trình duyệt nếu dữ liệu không hiển thị
|
- **DATABASE_SETUP.md** - Schema and API documentation
|
||||||
3. Đảm bảo trình duyệt hỗ trợ localStorage
|
- **server.js** - Node.js backend source
|
||||||
4. Thử trên trình duyệt khác
|
- **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
220
RUN_COMMANDS.md
Normal 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
294
SETUP_GUIDE.md
Normal 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
|
||||||
585
accounts.html
585
accounts.html
@@ -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>
|
|
||||||
@@ -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
63
database/run-setup.ps1
Normal 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
168
database/setup.sql
Normal 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 '========================================';
|
||||||
224
dialog.html
224
dialog.html
@@ -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&family=Inter:wght@400;500;600&display=swap" rel="stylesheet"/>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
|
||||||
<script id="tailwind-config">
|
|
||||||
tailwind.config = {
|
|
||||||
darkMode: "class",
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
"on-error-container": "#752121",
|
|
||||||
"background": "#f7f9fb",
|
|
||||||
"secondary-dim": "#465468",
|
|
||||||
"tertiary-dim": "#54506b",
|
|
||||||
"outline": "#717c82",
|
|
||||||
"on-secondary-container": "#455367",
|
|
||||||
"error-dim": "#4e0309",
|
|
||||||
"primary-fixed": "#dde1ff",
|
|
||||||
"on-primary-fixed": "#0732a3",
|
|
||||||
"surface-variant": "#d9e4ea",
|
|
||||||
"primary-dim": "#2848b7",
|
|
||||||
"on-tertiary": "#fcf7ff",
|
|
||||||
"surface": "#f7f9fb",
|
|
||||||
"primary-container": "#dde1ff",
|
|
||||||
"inverse-on-surface": "#9a9d9f",
|
|
||||||
"error": "#9f403d",
|
|
||||||
"on-primary": "#f8f7ff",
|
|
||||||
"on-surface-variant": "#566166",
|
|
||||||
"tertiary-container": "#e3dbfd",
|
|
||||||
"surface-container-lowest": "#ffffff",
|
|
||||||
"surface-dim": "#cfdce3",
|
|
||||||
"tertiary-fixed-dim": "#d4cdee",
|
|
||||||
"inverse-surface": "#0b0f10",
|
|
||||||
"on-primary-fixed-variant": "#3352c0",
|
|
||||||
"surface-tint": "#3755c3",
|
|
||||||
"secondary-fixed": "#d5e3fc",
|
|
||||||
"secondary-fixed-dim": "#c7d5ed",
|
|
||||||
"secondary": "#526074",
|
|
||||||
"tertiary-fixed": "#e3dbfd",
|
|
||||||
"on-secondary-fixed-variant": "#4e5c71",
|
|
||||||
"on-primary-container": "#2747b6",
|
|
||||||
"primary-fixed-dim": "#cad2ff",
|
|
||||||
"surface-container-highest": "#d9e4ea",
|
|
||||||
"surface-container-low": "#f0f4f7",
|
|
||||||
"surface-container": "#e8eff3",
|
|
||||||
"on-tertiary-fixed-variant": "#5b5672",
|
|
||||||
"secondary-container": "#d5e3fc",
|
|
||||||
"inverse-primary": "#6d89fa",
|
|
||||||
"outline-variant": "#a9b4b9",
|
|
||||||
"on-secondary-fixed": "#324053",
|
|
||||||
"on-surface": "#2a3439",
|
|
||||||
"surface-container-high": "#e1e9ee",
|
|
||||||
"on-background": "#2a3439",
|
|
||||||
"on-tertiary-fixed": "#3e3a54",
|
|
||||||
"primary": "#3755c3",
|
|
||||||
"on-tertiary-container": "#514d68",
|
|
||||||
"on-error": "#fff7f6",
|
|
||||||
"on-secondary": "#f8f7ff",
|
|
||||||
"tertiary": "#605c78",
|
|
||||||
"error-container": "#fe8983",
|
|
||||||
"surface-bright": "#f7f9fb"
|
|
||||||
},
|
|
||||||
fontFamily: {
|
|
||||||
"headline": ["Manrope"],
|
|
||||||
"body": ["Inter"],
|
|
||||||
"label": ["Inter"]
|
|
||||||
},
|
|
||||||
borderRadius: {"DEFAULT": "0.125rem", "lg": "0.25rem", "xl": "0.5rem", "full": "0.75rem"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.material-symbols-outlined {
|
|
||||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
body { font-family: 'Inter', sans-serif; }
|
|
||||||
h1, h2, h3 { font-family: 'Manrope', sans-serif; }
|
|
||||||
.glass-overlay {
|
|
||||||
backdrop-filter: blur(12px);
|
|
||||||
background: rgba(247, 249, 251, 0.8);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="bg-background text-on-surface overflow-hidden h-screen flex flex-col">
|
|
||||||
<!-- Blurred Background (Mocking the Accounts Management Table) -->
|
|
||||||
<div class="fixed inset-0 z-0 blur-[8px] opacity-40 pointer-events-none select-none overflow-hidden flex flex-col">
|
|
||||||
<!-- Mock TopNavBar (From JSON Guidance) -->
|
|
||||||
<header class="flex justify-between items-center w-full px-8 h-16 bg-[#f7f9fb] text-[#5a6a72] font-['Manrope'] text-sm tracking-wide font-medium">
|
|
||||||
<div class="text-lg font-extrabold tracking-tighter text-[#2a3439]">Sentinel Accounts</div>
|
|
||||||
<div class="flex items-center gap-6">
|
|
||||||
<span class="material-symbols-outlined">notifications</span>
|
|
||||||
<span class="material-symbols-outlined">help_outline</span>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div class="flex flex-1">
|
|
||||||
<!-- Mock SideNavBar (From JSON Guidance) -->
|
|
||||||
<aside class="flex flex-col h-full py-6 w-64 bg-[#f0f4f7] text-[#5a6a72] font-['Inter'] text-[0.875rem] font-medium">
|
|
||||||
<div class="px-6 mb-8">
|
|
||||||
<div class="text-sm font-black uppercase tracking-[0.05em] text-[#2a3439]">Architectural Sentinel</div>
|
|
||||||
<div class="text-xs text-on-surface-variant opacity-70">Enterprise Security</div>
|
|
||||||
</div>
|
|
||||||
<nav class="flex-1">
|
|
||||||
<div class="flex items-center gap-3 px-6 py-3 bg-white text-[#3755c3] font-bold border-l-4 border-[#3755c3]">
|
|
||||||
<span class="material-symbols-outlined" data-icon="vpn_key">vpn_key</span> Account Access
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-3 px-6 py-3">
|
|
||||||
<span class="material-symbols-outlined" data-icon="dashboard">dashboard</span> Dashboard
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</aside>
|
|
||||||
<!-- Mock Table Stage -->
|
|
||||||
<main class="flex-1 p-8 bg-background">
|
|
||||||
<div class="h-12 w-48 bg-surface-container-high rounded-xl mb-6"></div>
|
|
||||||
<div class="bg-surface-container-lowest rounded-xl shadow-sm border border-outline-variant/10 overflow-hidden">
|
|
||||||
<div class="h-12 border-b border-outline-variant/10 bg-surface-container-low px-6 flex items-center gap-4">
|
|
||||||
<div class="h-4 w-4 bg-outline-variant/20 rounded"></div>
|
|
||||||
<div class="h-4 w-24 bg-outline-variant/20 rounded"></div>
|
|
||||||
<div class="h-4 w-32 bg-outline-variant/20 rounded ml-auto"></div>
|
|
||||||
</div>
|
|
||||||
<div class="p-6 space-y-4">
|
|
||||||
<div class="h-10 w-full bg-surface-container-low/50 rounded-lg"></div>
|
|
||||||
<div class="h-10 w-full bg-surface-container-low/50 rounded-lg"></div>
|
|
||||||
<div class="h-10 w-full bg-surface-container-low/50 rounded-lg"></div>
|
|
||||||
<div class="h-10 w-full bg-surface-container-low/50 rounded-lg"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Modal Overlay -->
|
|
||||||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-on-surface/30 backdrop-blur-[2px]">
|
|
||||||
<!-- Dialog Container -->
|
|
||||||
<div class="w-full max-w-[640px] bg-surface-container-lowest rounded-xl shadow-[0px_12px_32px_rgba(42,52,57,0.12)] border-none overflow-hidden">
|
|
||||||
<!-- Modal Header -->
|
|
||||||
<div class="px-8 py-6 border-b border-surface-container flex justify-between items-center">
|
|
||||||
<div>
|
|
||||||
<span class="text-[0.625rem] font-bold uppercase tracking-[0.1em] text-on-surface-variant block mb-1">Resource Details</span>
|
|
||||||
<h2 class="text-2xl font-extrabold text-on-surface tracking-tight">Account Details</h2>
|
|
||||||
</div>
|
|
||||||
<button class="w-10 h-10 flex items-center justify-center rounded-full hover:bg-surface-container transition-colors text-on-surface-variant">
|
|
||||||
<span class="material-symbols-outlined" data-icon="close">close</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<!-- Modal Content (Bento-style layout for metadata) -->
|
|
||||||
<div class="p-8 space-y-8">
|
|
||||||
<!-- Service Info Section -->
|
|
||||||
<div class="flex items-center gap-5 p-5 bg-surface-container-low rounded-xl">
|
|
||||||
<div class="w-14 h-14 bg-white rounded-xl shadow-sm flex items-center justify-center">
|
|
||||||
<span class="material-symbols-outlined text-primary text-3xl" data-icon="cloud">cloud</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="text-xs font-bold text-on-surface-variant uppercase tracking-wider mb-0.5">Cloud Infrastructure</div>
|
|
||||||
<div class="text-xl font-bold text-on-surface">AWS Production</div>
|
|
||||||
</div>
|
|
||||||
<div class="ml-auto">
|
|
||||||
<span class="px-3 py-1 bg-primary-container text-on-primary-container text-[0.7rem] font-bold rounded-full uppercase tracking-tighter">Active</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Core Details Grid -->
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<!-- Username Field -->
|
|
||||||
<div class="space-y-2">
|
|
||||||
<label class="text-[0.65rem] font-bold uppercase tracking-widest text-on-surface-variant ml-1">Username</label>
|
|
||||||
<div class="flex items-center h-12 px-4 bg-surface-container-highest rounded-lg border border-transparent focus-within:border-primary/40 focus-within:shadow-[0_0_0_2px_rgba(55,85,195,0.05)] transition-all">
|
|
||||||
<span class="material-symbols-outlined text-on-surface-variant mr-3 text-sm" data-icon="alternate_email">alternate_email</span>
|
|
||||||
<span class="text-sm font-medium text-on-surface">admin.aws_prod</span>
|
|
||||||
<button class="ml-auto text-on-surface-variant hover:text-primary transition-colors">
|
|
||||||
<span class="material-symbols-outlined text-base" data-icon="content_copy">content_copy</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Password Field -->
|
|
||||||
<div class="space-y-2">
|
|
||||||
<label class="text-[0.65rem] font-bold uppercase tracking-widest text-on-surface-variant ml-1">Password</label>
|
|
||||||
<div class="flex items-center h-12 px-4 bg-surface-container-highest rounded-lg border border-transparent focus-within:border-primary/40 focus-within:shadow-[0_0_0_2px_rgba(55,85,195,0.05)] transition-all">
|
|
||||||
<span class="material-symbols-outlined text-on-surface-variant mr-3 text-sm" data-icon="lock">lock</span>
|
|
||||||
<span class="text-sm font-medium text-on-surface">•••••••••••••••</span>
|
|
||||||
<button class="ml-auto text-on-surface-variant hover:text-primary transition-colors">
|
|
||||||
<span class="material-symbols-outlined text-base" data-icon="visibility">visibility</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Metadata Row (Asymmetric Bento) -->
|
|
||||||
<div class="grid grid-cols-12 gap-4">
|
|
||||||
<!-- Owner Card -->
|
|
||||||
<div class="col-span-7 bg-surface-container-low/50 p-4 rounded-xl flex items-center gap-4">
|
|
||||||
<img alt="Alex Rivera Profile" class="w-10 h-10 rounded-full object-cover ring-2 ring-white" data-alt="Close-up professional portrait of a male systems administrator with a friendly expression in a modern office environment" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBi4gNrkG6OjxYer2iM7vtnmB1_dhArLqll8N46GWZ4YDXLfnwRIIf_bLhZRcMjHCxtKLivBh_JJMTnGRO4kIj0ZCtbVZ61SFhSJvZlPE3ZgNmNCCh7bDXDeFgdWnHKhWAcjDcpLmO02gp5HCU_6GJpLNdIU3pJosKGJsVW_hAhIfp8OYJcepHHf_23k3eQ9ZxkOP4ZR4qu2PU6ZmO2qTCVlJCZVtB-x6RC3YsjcpMNwpyIhSNCIcAvRKTOfU_cb2vtO6t9oD38b6o"/>
|
|
||||||
<div>
|
|
||||||
<div class="text-[0.6rem] font-bold uppercase text-on-surface-variant mb-0.5">Account Owner</div>
|
|
||||||
<div class="text-sm font-semibold text-on-surface">Alex Rivera</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Date Card -->
|
|
||||||
<div class="col-span-5 bg-surface-container-low/50 p-4 rounded-xl">
|
|
||||||
<div class="text-[0.6rem] font-bold uppercase text-on-surface-variant mb-0.5">Date Created</div>
|
|
||||||
<div class="text-sm font-semibold text-on-surface flex items-center gap-2">
|
|
||||||
<span class="material-symbols-outlined text-base text-on-surface-variant" data-icon="calendar_today">calendar_today</span>
|
|
||||||
Oct 24, 2023
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Footer Actions -->
|
|
||||||
<div class="px-8 py-6 bg-surface-container-low flex items-center justify-end gap-3">
|
|
||||||
<button class="px-5 h-11 text-sm font-bold text-on-secondary-container hover:bg-surface-container transition-all rounded-lg">
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
<button class="px-6 h-11 text-sm font-bold text-on-primary bg-gradient-to-br from-primary to-primary-dim rounded-lg shadow-sm hover:opacity-90 active:scale-[0.98] transition-all flex items-center gap-2">
|
|
||||||
<span class="material-symbols-outlined text-base" data-icon="edit">edit</span>
|
|
||||||
Edit Account
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body></html>
|
|
||||||
134
docs/README.md
Normal file
134
docs/README.md
Normal 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
|
||||||
172
index.html
172
index.html
@@ -1,172 +1,12 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="light" lang="en">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<title>Redirecting...</title>
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
<script>
|
||||||
<title>VaultSentinel - Account Management System</title>
|
window.location.href = './pages/login.html';
|
||||||
<!-- 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>
|
</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>
|
</head>
|
||||||
<body class="bg-background text-on-surface antialiased flex h-screen w-screen">
|
<body>
|
||||||
<!-- SideNavBar -->
|
Redirecting to login...
|
||||||
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
35
install-nodejs.ps1
Normal file
35
install-nodejs.ps1
Normal 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
|
||||||
@@ -3,6 +3,14 @@
|
|||||||
|
|
||||||
class AccountManager {
|
class AccountManager {
|
||||||
constructor() {
|
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.accounts = this.loadFromStorage('accounts') || [];
|
||||||
this.applications = this.loadFromStorage('applications') || [
|
this.applications = this.loadFromStorage('applications') || [
|
||||||
{ id: 1, name: 'AWS', type: 'Cloud', status: 'online', icon: 'cloud' },
|
{ id: 1, name: 'AWS', type: 'Cloud', status: 'online', icon: 'cloud' },
|
||||||
@@ -52,10 +60,36 @@ class AccountManager {
|
|||||||
appForm.addEventListener('submit', (e) => this.handleAppSubmit(e));
|
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
|
// Account table row clicks
|
||||||
this.setupAccountRowListeners();
|
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() {
|
renderDashboard() {
|
||||||
return `
|
return `
|
||||||
<div class="flex-1 flex flex-col p-6 space-y-6 min-h-0 overflow-auto">
|
<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>
|
<p class="text-xs text-on-surface-variant font-medium mt-1">Account & Service Management</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<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>
|
<span class="material-symbols-outlined text-sm">add</span>
|
||||||
Add Account
|
Add Account
|
||||||
</a>
|
</a>
|
||||||
@@ -125,7 +159,7 @@ class AccountManager {
|
|||||||
</div>
|
</div>
|
||||||
` : `
|
` : `
|
||||||
<div class="flex-1 flex items-center justify-center text-center">
|
<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>
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
@@ -385,7 +419,7 @@ class AccountManager {
|
|||||||
if (confirm('Delete this account?')) {
|
if (confirm('Delete this account?')) {
|
||||||
this.accounts.splice(accountId, 1);
|
this.accounts.splice(accountId, 1);
|
||||||
this.saveToStorage('accounts', this.accounts);
|
this.saveToStorage('accounts', this.accounts);
|
||||||
location.href = 'accounts.html';
|
location.href = './accounts.html';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -410,7 +444,7 @@ class AccountManager {
|
|||||||
if (confirm('Delete this application?')) {
|
if (confirm('Delete this application?')) {
|
||||||
this.applications.splice(appId, 1);
|
this.applications.splice(appId, 1);
|
||||||
this.saveToStorage('applications', this.applications);
|
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.saveToStorage('accounts', this.accounts);
|
||||||
this.closeModals();
|
this.closeModals();
|
||||||
location.href = 'accounts.html';
|
location.href = './accounts.html';
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAppSubmit(e) {
|
handleAppSubmit(e) {
|
||||||
@@ -468,7 +502,7 @@ class AccountManager {
|
|||||||
|
|
||||||
this.saveToStorage('applications', this.applications);
|
this.saveToStorage('applications', this.applications);
|
||||||
this.closeModals();
|
this.closeModals();
|
||||||
location.href = 'applications.html';
|
location.href = './applications.html';
|
||||||
}
|
}
|
||||||
|
|
||||||
openAccountModal() {
|
openAccountModal() {
|
||||||
76
navbar.js
76
navbar.js
@@ -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
22
package.json
Normal 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
152
pages/accounts.html
Normal 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
152
pages/applications.html
Normal 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
176
pages/index.html
Normal 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
254
pages/login.html
Normal 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
4
requirements.txt
Normal 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
438
server.js
Normal 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
480
server_python.py
Normal 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)
|
||||||
242
start.html
242
start.html
@@ -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>
|
|
||||||
Reference in New Issue
Block a user