This commit is contained in:
2026-03-31 13:48:10 +07:00
parent 0c1ca1d923
commit 900a569c51
20 changed files with 204 additions and 1753 deletions

View File

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

View File

@@ -1,220 +0,0 @@
# 🚀 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! 🚀**

View File

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

View File

@@ -10,15 +10,14 @@ const app = express();
app.use(cors());
app.use(express.json());
// Serve static files
// Serve static files from /public
const path = require('path');
app.use(express.static(path.join(__dirname, 'pages')));
app.use(express.static(path.join(__dirname, 'js')));
app.use(express.static(path.join(__dirname)));
const publicDir = path.join(__dirname, '..', 'public');
app.use(express.static(publicDir));
// Root route
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'pages', 'login.html'));
res.sendFile(path.join(publicDir, 'pages', 'login.html'));
});
// SQL Server Configuration

View File

@@ -1,270 +0,0 @@
<!DOCTYPE html>
<html class="light" lang="en">
<head>
<meta charset="utf-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>Sentinel Accounts - Account Details</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link
href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&amp;family=Inter:wght@400;500;600&amp;display=swap"
rel="stylesheet" />
<link
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap"
rel="stylesheet" />
<link
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap"
rel="stylesheet" />
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"on-error-container": "#752121",
"background": "#f7f9fb",
"secondary-dim": "#465468",
"tertiary-dim": "#54506b",
"outline": "#717c82",
"on-secondary-container": "#455367",
"error-dim": "#4e0309",
"primary-fixed": "#dde1ff",
"on-primary-fixed": "#0732a3",
"surface-variant": "#d9e4ea",
"primary-dim": "#2848b7",
"on-tertiary": "#fcf7ff",
"surface": "#f7f9fb",
"primary-container": "#dde1ff",
"inverse-on-surface": "#9a9d9f",
"error": "#9f403d",
"on-primary": "#f8f7ff",
"on-surface-variant": "#566166",
"tertiary-container": "#e3dbfd",
"surface-container-lowest": "#ffffff",
"surface-dim": "#cfdce3",
"tertiary-fixed-dim": "#d4cdee",
"inverse-surface": "#0b0f10",
"on-primary-fixed-variant": "#3352c0",
"surface-tint": "#3755c3",
"secondary-fixed": "#d5e3fc",
"secondary-fixed-dim": "#c7d5ed",
"secondary": "#526074",
"tertiary-fixed": "#e3dbfd",
"on-secondary-fixed-variant": "#4e5c71",
"on-primary-container": "#2747b6",
"primary-fixed-dim": "#cad2ff",
"surface-container-highest": "#d9e4ea",
"surface-container-low": "#f0f4f7",
"surface-container": "#e8eff3",
"on-tertiary-fixed-variant": "#5b5672",
"secondary-container": "#d5e3fc",
"inverse-primary": "#6d89fa",
"outline-variant": "#a9b4b9",
"on-secondary-fixed": "#324053",
"on-surface": "#2a3439",
"surface-container-high": "#e1e9ee",
"on-background": "#2a3439",
"on-tertiary-fixed": "#3e3a54",
"primary": "#3755c3",
"on-tertiary-container": "#514d68",
"on-error": "#fff7f6",
"on-secondary": "#f8f7ff",
"tertiary": "#605c78",
"error-container": "#fe8983",
"surface-bright": "#f7f9fb"
},
fontFamily: {
"headline": ["Manrope"],
"body": ["Inter"],
"label": ["Inter"]
},
borderRadius: { "DEFAULT": "0.125rem", "lg": "0.25rem", "xl": "0.5rem", "full": "0.75rem" },
},
},
}
</script>
<style>
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
vertical-align: middle;
}
body {
font-family: 'Inter', sans-serif;
}
h1,
h2,
h3 {
font-family: 'Manrope', sans-serif;
}
.glass-overlay {
backdrop-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>

View File

@@ -1,63 +0,0 @@
# 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@2022"
$SqlScriptPath = Join-Path $PSScriptRoot "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

View File

@@ -1 +0,0 @@
Sqlcmd: Error: Microsoft ODBC Driver 17 for SQL Server : Login failed for user 'sa'..

View File

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

View File

@@ -2,10 +2,10 @@
"name": "accmanager-backend",
"version": "1.0.0",
"description": "Backend server for AccManager application",
"main": "server.js",
"main": "backend/server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
"start": "node backend/server.js",
"dev": "nodemon backend/server.js"
},
"keywords": ["accmanager", "backend", "express", "mssql"],
"author": "",

View File

@@ -15,7 +15,49 @@ class AccountManager {
this.applications = [];
this.apiBase = '/api';
this.currentPage = 'dashboard';
this.accountSearchTerm = '';
this.applicationSearchTerm = '';
this.accountServiceFilter = '';
this.configureNotifications();
this.initPromise = this.init();
this.pendingAccountAppId = undefined;
}
configureNotifications() {
if (window.Notiflix?.Notify) {
Notiflix.Notify.init({
position: 'right-top',
timeout: 2500,
clickToClose: true,
pauseOnHover: true,
distance: '12px',
fontSize: '14px'
});
}
}
notifySuccess(message) {
if (window.Notiflix?.Notify) {
Notiflix.Notify.success(message);
} else {
alert(message);
}
}
notifyFailure(message) {
if (window.Notiflix?.Notify) {
Notiflix.Notify.failure(message);
} else {
alert(message);
}
}
notifyWarning(message) {
if (window.Notiflix?.Notify) {
Notiflix.Notify.warning(message);
} else {
alert(message);
}
}
getUserId() {
@@ -112,6 +154,7 @@ class AccountManager {
// Account table row clicks
this.setupAccountRowListeners();
this.setupFilters();
}
setupFormListeners() {
@@ -144,6 +187,27 @@ class AccountManager {
if (roleEl) roleEl.textContent = this.currentUser?.role || this.currentUser?.Role || 'Administrator';
}
getFilteredAccounts() {
const svcFilter = this.accountServiceFilter || '';
const search = (this.accountSearchTerm || '').toLowerCase();
return this.accounts.filter(acc => {
const matchesService = !svcFilter || String(acc.AppId) === String(svcFilter);
if (!matchesService) return false;
if (!search) return true;
const hay = [acc.AccountUsername, acc.Email, acc.AppName, acc.AppType].map(v => (v || '').toLowerCase());
return hay.some(val => val.includes(search));
});
}
getFilteredApplications() {
const search = (this.applicationSearchTerm || '').toLowerCase();
if (!search) return this.applications;
return this.applications.filter(app => {
const hay = [app.Name, app.Type, app.Description, app.Url, app.Icon].map(v => (v || '').toLowerCase());
return hay.some(val => val.includes(search));
});
}
handleLogout() {
if (confirm('Are you sure you want to logout?')) {
this.saveToStorage('currentUser', null);
@@ -234,6 +298,7 @@ class AccountManager {
}
getAccountsContent() {
const filteredAccounts = this.getFilteredAccounts();
return `
<div class="p-4 md:p-6 flex flex-col h-full overflow-hidden">
<!-- Page Header -->
@@ -257,12 +322,15 @@ class AccountManager {
${this.applications.map(app => `<option value="${app.AppId}">${app.Name}</option>`).join('')}
</select>
</div>
<div class="flex-1"></div>
<div class="flex items-center gap-1.5 flex-1">
<span class="text-[10px] font-bold uppercase text-on-surface-variant">Search</span>
<input id="accountSearch" class="flex-1 bg-surface-container-low border-slate-200 rounded-md text-[11px] py-1 px-2 focus:ring-1 focus:ring-primary shadow-sm" placeholder="Search by owner, username, service">
</div>
</div>
<!-- Accounts Table -->
<div class="flex-1 bg-white rounded-xl border border-slate-200 shadow-sm flex flex-col overflow-hidden min-h-0">
${this.accounts.length > 0 ? `
${filteredAccounts.length > 0 ? `
<div class="overflow-y-auto overflow-x-auto flex-1">
<table class="w-full text-left border-collapse w-full">
<thead class="sticky top-0 z-10">
@@ -274,7 +342,7 @@ class AccountManager {
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
${this.accounts.map(acc => `
${filteredAccounts.map(acc => `
<tr class="hover:bg-slate-50/80 transition-colors group account-row" data-account-id="${acc.AccountId}">
<td class="px-4 py-3 text-sm font-medium text-slate-900">${acc.Email || '-'}</td>
<td class="px-4 py-3 text-sm text-slate-600">${acc.AccountUsername || '-'}</td>
@@ -315,6 +383,7 @@ class AccountManager {
}
getApplicationsContent() {
const filteredApps = this.getFilteredApplications();
return `
<div class="flex flex-col p-6 overflow-hidden h-full">
<!-- Header Section -->
@@ -362,6 +431,10 @@ class AccountManager {
<!-- Applications List -->
<div class="bg-surface-container-lowest rounded-xl shadow-sm border border-outline-variant/10 overflow-hidden flex flex-col flex-1 min-h-0">
<div class="px-4 py-3 border-b border-outline-variant/10 flex items-center gap-2 bg-surface-container-low/40">
<span class="text-[10px] font-bold uppercase text-on-surface-variant">Search</span>
<input id="appSearch" class="flex-1 bg-white border border-slate-200 rounded-md text-[11px] py-1 px-2 focus:ring-1 focus:ring-primary shadow-sm" placeholder="Search by name, type, description, url">
</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">
@@ -375,7 +448,7 @@ class AccountManager {
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/5">
${this.applications.map(app => `
${filteredApps.map(app => `
<tr class="hover:bg-surface-container-low/30 transition-colors group">
<td class="px-6 py-3">
<div class="flex items-center gap-3">
@@ -457,16 +530,16 @@ class AccountManager {
.then(res => res.json())
.then(data => {
if (data.success) {
alert('Account deleted successfully');
this.notifySuccess('Account deleted successfully');
this.closeModals();
location.href = './accounts.html';
this.refreshAccountsUI();
} else {
alert(data.message || 'Delete account failed');
this.notifyFailure(data.message || 'Delete account failed');
}
})
.catch(err => {
console.error(err);
alert('Delete account failed');
this.notifyFailure('Delete account failed');
});
}
});
@@ -489,6 +562,7 @@ class AccountManager {
if (ownerInput) ownerInput.value = this.currentUser?.Username || this.currentUser?.username || '';
if (serviceSelect) serviceSelect.value = account?.AppId || '';
}
this.pendingAccountAppId = account?.AppId;
this.editingAccountId = account?.AccountId;
this.closeModals();
this.openAccountModal();
@@ -510,6 +584,7 @@ class AccountManager {
if (ownerInput) ownerInput.value = this.currentUser?.Username || this.currentUser?.username || '';
if (serviceSelect) serviceSelect.value = account?.AppId || '';
}
this.pendingAccountAppId = account?.AppId;
this.editingAccountId = account?.AccountId;
this.closeModals();
this.openAccountModal();
@@ -543,6 +618,11 @@ class AccountManager {
this.currentViewAppId = appId;
document.getElementById('viewAppName').textContent = app?.Name || '-';
document.getElementById('viewAppType').textContent = app?.Type || '-';
const iconVal = app?.Icon || app?.icon || 'apps';
const iconSymbolEl = document.getElementById('viewAppIconSymbol');
const iconNameEl = document.getElementById('viewAppIconName');
if (iconSymbolEl) iconSymbolEl.textContent = iconVal;
if (iconNameEl) iconNameEl.textContent = iconVal;
document.getElementById('viewAppDescription').textContent = app?.Description || '-';
const urlEl = document.getElementById('viewAppUrl');
const urlVal = app?.Url || app?.url;
@@ -578,16 +658,16 @@ class AccountManager {
.then(res => res.json())
.then(data => {
if (data.success) {
alert('Application deleted successfully');
this.notifySuccess('Application deleted successfully');
this.closeModals();
location.href = './applications.html';
this.refreshApplicationsUI();
} else {
alert(data.message || 'Delete application failed');
this.notifyFailure(data.message || 'Delete application failed');
}
})
.catch(err => {
console.error(err);
alert('Delete application failed');
this.notifyFailure('Delete application failed');
});
}
});
@@ -602,6 +682,7 @@ class AccountManager {
document.getElementById('appType').value = app?.Type || '';
document.getElementById('appStatus').value = app?.Status || 'online';
document.getElementById('appDescription').value = app?.Description || '';
document.getElementById('appIcon').value = app?.Icon || app?.icon || '';
document.getElementById('appUrl').value = app?.Url || app?.url || '';
this.editingAppId = app?.AppId;
this.closeModals();
@@ -618,6 +699,7 @@ class AccountManager {
document.getElementById('appType').value = app?.Type || '';
document.getElementById('appStatus').value = app?.Status || 'online';
document.getElementById('appDescription').value = app?.Description || '';
document.getElementById('appIcon').value = app?.Icon || app?.icon || '';
document.getElementById('appUrl').value = app?.Url || '';
this.editingAppId = app?.AppId;
this.closeModals();
@@ -631,6 +713,7 @@ class AccountManager {
document.querySelectorAll('#addAccountBtn').forEach(btn => {
btn.addEventListener('click', () => {
this.editingAccountId = undefined;
this.pendingAccountAppId = undefined;
this.openAccountModal();
});
});
@@ -644,7 +727,36 @@ class AccountManager {
});
}
handleAccountSubmit(e) {
setupFilters() {
const serviceFilter = document.getElementById('serviceFilter');
if (serviceFilter) {
serviceFilter.value = this.accountServiceFilter || '';
serviceFilter.addEventListener('change', (e) => {
this.accountServiceFilter = e.target.value;
this.refreshAccountsUI();
});
}
const accountSearch = document.getElementById('accountSearch');
if (accountSearch) {
accountSearch.value = this.accountSearchTerm;
accountSearch.addEventListener('input', (e) => {
this.accountSearchTerm = e.target.value.toLowerCase();
this.refreshAccountsUI();
});
}
const appSearch = document.getElementById('appSearch');
if (appSearch) {
appSearch.value = this.applicationSearchTerm;
appSearch.addEventListener('input', (e) => {
this.applicationSearchTerm = e.target.value.toLowerCase();
this.refreshApplicationsUI();
});
}
}
async handleAccountSubmit(e) {
e.preventDefault();
const accountForm = document.getElementById('accountForm');
const userId = this.getUserId();
@@ -653,23 +765,23 @@ class AccountManager {
const accountPassword = (accountForm?.querySelector('#accountPassword')?.value || '').trim();
const accountEmail = ((accountForm?.querySelector('#accountOwner')?.value || '').trim()) || this.currentUser?.Username || this.currentUser?.username || '';
if (!accountForm) {
alert('Account form not found.');
this.notifyFailure('Account form not found.');
return;
}
if (!userId) {
alert('User is not authenticated. Please login again.');
this.notifyFailure('User is not authenticated. Please login again.');
return;
}
if (!appId) {
alert('Please select a service.');
this.notifyWarning('Please select a service.');
return;
}
if (!accountUsername) {
alert('Please enter a username.');
this.notifyWarning('Please enter a username.');
return;
}
if (!accountPassword) {
alert('Please enter a password.');
this.notifyWarning('Please enter a password.');
return;
}
const payload = {
@@ -693,25 +805,37 @@ class AccountManager {
}).then(res => res.json()).then(data => {
if (data.success) {
this.editingAccountId = undefined;
alert(isEdit ? 'Account updated successfully' : 'Account created successfully');
this.pendingAccountAppId = undefined;
this.notifySuccess(isEdit ? 'Account updated successfully' : 'Account created successfully');
this.closeModals();
location.href = './accounts.html';
this.refreshAccountsUI();
} else {
alert(data.message || 'Save account failed');
this.notifyFailure(data.message || 'Save account failed');
}
}).catch(err => {
console.error(err);
alert('Save account failed');
this.notifyFailure('Save account failed');
});
}
handleAppSubmit(e) {
async refreshAccountsUI() {
await this.fetchAccounts();
const mainContent = document.getElementById('mainContent');
if (mainContent) {
mainContent.innerHTML = this.getAccountsContent();
this.setupAccountRowListeners();
this.setupAddButtonListeners();
this.setupFilters();
}
}
async handleAppSubmit(e) {
e.preventDefault();
const payload = {
name: document.getElementById('appName').value,
type: document.getElementById('appType').value,
status: document.getElementById('appStatus').value,
icon: 'cloud',
icon: (document.getElementById('appIcon')?.value || 'apps').trim() || 'apps',
description: document.getElementById('appDescription')?.value || '',
url: (document.getElementById('appUrl')?.value || '').trim()
};
@@ -727,24 +851,38 @@ class AccountManager {
}).then(res => res.json()).then(data => {
if (data.success) {
this.editingAppId = undefined;
alert(isEdit ? 'Application updated successfully' : 'Application created successfully');
this.notifySuccess(isEdit ? 'Application updated successfully' : 'Application created successfully');
this.closeModals();
location.href = './applications.html';
this.refreshApplicationsUI();
} else {
alert(data.message || 'Save application failed');
this.notifyFailure(data.message || 'Save application failed');
}
}).catch(err => {
console.error(err);
alert('Save application failed');
this.notifyFailure('Save application failed');
});
}
async refreshApplicationsUI() {
await this.fetchApplications();
const mainContent = document.getElementById('mainContent');
if (mainContent) {
mainContent.innerHTML = this.getApplicationsContent();
this.setupAccountRowListeners();
this.setupAddButtonListeners();
this.setupFilters();
}
}
openAccountModal() {
// Refresh service options so newly added applications appear
const serviceSelect = document.getElementById('accountService');
if (serviceSelect) {
serviceSelect.innerHTML = `<option value="">Select a service</option>` +
this.applications.map(app => `<option value="${app.AppId}">${app.Name}</option>`).join('');
if (this.editingAccountId !== undefined && this.pendingAccountAppId) {
serviceSelect.value = this.pendingAccountAppId;
}
}
if (this.editingAccountId === undefined) {
@@ -768,8 +906,10 @@ class AccountManager {
document.getElementById('appName').value = '';
document.getElementById('appType').value = '';
document.getElementById('appStatus').value = 'online';
const iconInput = document.getElementById('appIcon');
const desc = document.getElementById('appDescription');
const url = document.getElementById('appUrl');
if (iconInput) iconInput.value = '';
if (desc) desc.value = '';
if (url) url.value = '';
}

View File

@@ -108,6 +108,11 @@
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Type</label>
<input type="text" id="appType" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3" required placeholder="Cloud">
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Icon (Material Symbol)</label>
<input type="text" id="appIcon" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3" placeholder="apps, cloud, security">
<p class="text-[10px] text-slate-400 mt-1">Nhập tên icon từ https://fonts.google.com/icons (ví dụ: cloud, apps, security).</p>
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Description</label>
<textarea id="appDescription" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3 h-20 resize-none" placeholder="Short description"></textarea>
@@ -149,6 +154,13 @@
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Type</label>
<div id="viewAppType" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3 bg-slate-50 text-slate-600">-</div>
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Icon</label>
<div id="viewAppIcon" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3 bg-slate-50 text-slate-600 flex items-center gap-2">
<span class="material-symbols-outlined text-base" id="viewAppIconSymbol">apps</span>
<span id="viewAppIconName" class="text-sm text-slate-600">apps</span>
</div>
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Description</label>
<div id="viewAppDescription" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3 bg-slate-50 text-slate-600 break-words">-</div>

View File

@@ -3,12 +3,15 @@
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>VaultSentinel - Accounts Management</title>
<title>Robot Manager Account - 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"/>
<!-- Notiflix Notify -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/notiflix@3.2.7/dist/notiflix-3.2.7.min.css" />
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script src="https://cdn.jsdelivr.net/npm/notiflix@3.2.7/dist/notiflix-aio-3.2.7.min.js"></script>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
@@ -240,6 +243,7 @@
mainContent.innerHTML = app.getAccountsContent();
app.setupAccountRowListeners();
app.setupAddButtonListeners();
app.setupFilters();
}
}
});

View File

@@ -3,12 +3,15 @@
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>VaultSentinel - Applications Management</title>
<title>Robot Manager Account - 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"/>
<!-- Notiflix Notify -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/notiflix@3.2.7/dist/notiflix-3.2.7.min.css" />
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script src="https://cdn.jsdelivr.net/npm/notiflix@3.2.7/dist/notiflix-aio-3.2.7.min.js"></script>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
@@ -227,6 +230,7 @@
mainContent.innerHTML = app.getApplicationsContent();
app.setupAccountRowListeners();
app.setupAddButtonListeners();
app.setupFilters();
}
}
});

View File

@@ -8,7 +8,10 @@
<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"/>
<!-- Notiflix Notify -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/notiflix@3.2.7/dist/notiflix-3.2.7.min.css" />
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script src="https://cdn.jsdelivr.net/npm/notiflix@3.2.7/dist/notiflix-aio-3.2.7.min.js"></script>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>VaultSentinel - Login</title>
<title>Robotics Account - 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 -->
@@ -106,7 +106,7 @@
<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>
<h1 class="text-2xl font-black text-slate-900 dark:text-slate-50 tracking-tight">Robotics Account Manager</h1>
<p class="text-xs uppercase tracking-widest text-on-surface-variant font-bold mt-2">Account Management System</p>
</div>
@@ -173,9 +173,9 @@
</form>
<!-- Footer -->
<div class="mt-8 pt-6 border-t border-outline-variant/10 text-center">
<!-- <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> -->
</div>
<!-- Bottom info -->

View File

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

View File

@@ -1,480 +0,0 @@
# 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)

Binary file not shown.

View File

@@ -1,53 +0,0 @@
Database AccManager created successfully.
Changed database context to 'AccManager'.
Table Users created successfully.
Table Applications created successfully.
Table Accounts created successfully.
Table AuditLog created successfully.
Indexes created successfully.
(1 rows affected)
Admin user created: Username=admin, Password=admin
(4 rows affected)
Sample applications inserted successfully.
========================================
DATABASE SETUP COMPLETED SUCCESSFULLY
========================================
Database Name: AccManager
Tables created:
TableName
------------------------------------------------------------------------------------------------------------------------------------
- Accounts
- Applications
- AuditLog
- Users
(4 rows affected)
Users in system:
UserInfo
--------------------------------------------------------------------------------------------------------------------------------------------------------
Username: admin | Role: admin | Status: Active
(1 rows affected)
Applications available:
AppInfo
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- AWS (Cloud) - online
- GitHub (VCS) - online
- Google Workspace (Collaboration) - online
- Nginx Proxy (Infra) - offline
(4 rows affected)
Login Credentials:
Username: admin
Password: admin
Role: admin
========================================