fomat
This commit is contained in:
@@ -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
|
|
||||||
220
RUN_COMMANDS.md
220
RUN_COMMANDS.md
@@ -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! 🚀**
|
|
||||||
294
SETUP_GUIDE.md
294
SETUP_GUIDE.md
@@ -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
|
|
||||||
@@ -10,15 +10,14 @@ const app = express();
|
|||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
// Serve static files
|
// Serve static files from /public
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
app.use(express.static(path.join(__dirname, 'pages')));
|
const publicDir = path.join(__dirname, '..', 'public');
|
||||||
app.use(express.static(path.join(__dirname, 'js')));
|
app.use(express.static(publicDir));
|
||||||
app.use(express.static(path.join(__dirname)));
|
|
||||||
|
|
||||||
// Root route
|
// Root route
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.sendFile(path.join(__dirname, 'pages', 'login.html'));
|
res.sendFile(path.join(publicDir, 'pages', 'login.html'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// SQL Server Configuration
|
// SQL Server Configuration
|
||||||
270
codedialog.html
270
codedialog.html
@@ -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&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-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>
|
|
||||||
@@ -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
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Sqlcmd: Error: Microsoft ODBC Driver 17 for SQL Server : Login failed for user 'sa'..
|
|
||||||
@@ -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
|
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
"name": "accmanager-backend",
|
"name": "accmanager-backend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Backend server for AccManager application",
|
"description": "Backend server for AccManager application",
|
||||||
"main": "server.js",
|
"main": "backend/server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server.js",
|
"start": "node backend/server.js",
|
||||||
"dev": "nodemon server.js"
|
"dev": "nodemon backend/server.js"
|
||||||
},
|
},
|
||||||
"keywords": ["accmanager", "backend", "express", "mssql"],
|
"keywords": ["accmanager", "backend", "express", "mssql"],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|||||||
@@ -15,7 +15,49 @@ class AccountManager {
|
|||||||
this.applications = [];
|
this.applications = [];
|
||||||
this.apiBase = '/api';
|
this.apiBase = '/api';
|
||||||
this.currentPage = 'dashboard';
|
this.currentPage = 'dashboard';
|
||||||
|
this.accountSearchTerm = '';
|
||||||
|
this.applicationSearchTerm = '';
|
||||||
|
this.accountServiceFilter = '';
|
||||||
|
this.configureNotifications();
|
||||||
this.initPromise = this.init();
|
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() {
|
getUserId() {
|
||||||
@@ -112,6 +154,7 @@ class AccountManager {
|
|||||||
|
|
||||||
// Account table row clicks
|
// Account table row clicks
|
||||||
this.setupAccountRowListeners();
|
this.setupAccountRowListeners();
|
||||||
|
this.setupFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupFormListeners() {
|
setupFormListeners() {
|
||||||
@@ -144,6 +187,27 @@ class AccountManager {
|
|||||||
if (roleEl) roleEl.textContent = this.currentUser?.role || this.currentUser?.Role || 'Administrator';
|
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() {
|
handleLogout() {
|
||||||
if (confirm('Are you sure you want to logout?')) {
|
if (confirm('Are you sure you want to logout?')) {
|
||||||
this.saveToStorage('currentUser', null);
|
this.saveToStorage('currentUser', null);
|
||||||
@@ -234,6 +298,7 @@ class AccountManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAccountsContent() {
|
getAccountsContent() {
|
||||||
|
const filteredAccounts = this.getFilteredAccounts();
|
||||||
return `
|
return `
|
||||||
<div class="p-4 md:p-6 flex flex-col h-full overflow-hidden">
|
<div class="p-4 md:p-6 flex flex-col h-full overflow-hidden">
|
||||||
<!-- Page Header -->
|
<!-- Page Header -->
|
||||||
@@ -257,12 +322,15 @@ class AccountManager {
|
|||||||
${this.applications.map(app => `<option value="${app.AppId}">${app.Name}</option>`).join('')}
|
${this.applications.map(app => `<option value="${app.AppId}">${app.Name}</option>`).join('')}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Accounts Table -->
|
<!-- 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="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">
|
<div class="overflow-y-auto overflow-x-auto flex-1">
|
||||||
<table class="w-full text-left border-collapse w-full">
|
<table class="w-full text-left border-collapse w-full">
|
||||||
<thead class="sticky top-0 z-10">
|
<thead class="sticky top-0 z-10">
|
||||||
@@ -274,7 +342,7 @@ class AccountManager {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-slate-100">
|
<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}">
|
<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 font-medium text-slate-900">${acc.Email || '-'}</td>
|
||||||
<td class="px-4 py-3 text-sm text-slate-600">${acc.AccountUsername || '-'}</td>
|
<td class="px-4 py-3 text-sm text-slate-600">${acc.AccountUsername || '-'}</td>
|
||||||
@@ -315,6 +383,7 @@ class AccountManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getApplicationsContent() {
|
getApplicationsContent() {
|
||||||
|
const filteredApps = this.getFilteredApplications();
|
||||||
return `
|
return `
|
||||||
<div class="flex flex-col p-6 overflow-hidden h-full">
|
<div class="flex flex-col p-6 overflow-hidden h-full">
|
||||||
<!-- Header Section -->
|
<!-- Header Section -->
|
||||||
@@ -362,6 +431,10 @@ class AccountManager {
|
|||||||
|
|
||||||
<!-- Applications List -->
|
<!-- 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="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">
|
<div class="overflow-y-auto flex-1">
|
||||||
<table class="w-full text-left border-collapse">
|
<table class="w-full text-left border-collapse">
|
||||||
<thead class="sticky top-0 bg-surface-container-lowest z-10">
|
<thead class="sticky top-0 bg-surface-container-lowest z-10">
|
||||||
@@ -375,7 +448,7 @@ class AccountManager {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-outline-variant/5">
|
<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">
|
<tr class="hover:bg-surface-container-low/30 transition-colors group">
|
||||||
<td class="px-6 py-3">
|
<td class="px-6 py-3">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
@@ -457,16 +530,16 @@ class AccountManager {
|
|||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
alert('Account deleted successfully');
|
this.notifySuccess('Account deleted successfully');
|
||||||
this.closeModals();
|
this.closeModals();
|
||||||
location.href = './accounts.html';
|
this.refreshAccountsUI();
|
||||||
} else {
|
} else {
|
||||||
alert(data.message || 'Delete account failed');
|
this.notifyFailure(data.message || 'Delete account failed');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(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 (ownerInput) ownerInput.value = this.currentUser?.Username || this.currentUser?.username || '';
|
||||||
if (serviceSelect) serviceSelect.value = account?.AppId || '';
|
if (serviceSelect) serviceSelect.value = account?.AppId || '';
|
||||||
}
|
}
|
||||||
|
this.pendingAccountAppId = account?.AppId;
|
||||||
this.editingAccountId = account?.AccountId;
|
this.editingAccountId = account?.AccountId;
|
||||||
this.closeModals();
|
this.closeModals();
|
||||||
this.openAccountModal();
|
this.openAccountModal();
|
||||||
@@ -510,6 +584,7 @@ class AccountManager {
|
|||||||
if (ownerInput) ownerInput.value = this.currentUser?.Username || this.currentUser?.username || '';
|
if (ownerInput) ownerInput.value = this.currentUser?.Username || this.currentUser?.username || '';
|
||||||
if (serviceSelect) serviceSelect.value = account?.AppId || '';
|
if (serviceSelect) serviceSelect.value = account?.AppId || '';
|
||||||
}
|
}
|
||||||
|
this.pendingAccountAppId = account?.AppId;
|
||||||
this.editingAccountId = account?.AccountId;
|
this.editingAccountId = account?.AccountId;
|
||||||
this.closeModals();
|
this.closeModals();
|
||||||
this.openAccountModal();
|
this.openAccountModal();
|
||||||
@@ -543,6 +618,11 @@ class AccountManager {
|
|||||||
this.currentViewAppId = appId;
|
this.currentViewAppId = appId;
|
||||||
document.getElementById('viewAppName').textContent = app?.Name || '-';
|
document.getElementById('viewAppName').textContent = app?.Name || '-';
|
||||||
document.getElementById('viewAppType').textContent = app?.Type || '-';
|
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 || '-';
|
document.getElementById('viewAppDescription').textContent = app?.Description || '-';
|
||||||
const urlEl = document.getElementById('viewAppUrl');
|
const urlEl = document.getElementById('viewAppUrl');
|
||||||
const urlVal = app?.Url || app?.url;
|
const urlVal = app?.Url || app?.url;
|
||||||
@@ -578,16 +658,16 @@ class AccountManager {
|
|||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
alert('Application deleted successfully');
|
this.notifySuccess('Application deleted successfully');
|
||||||
this.closeModals();
|
this.closeModals();
|
||||||
location.href = './applications.html';
|
this.refreshApplicationsUI();
|
||||||
} else {
|
} else {
|
||||||
alert(data.message || 'Delete application failed');
|
this.notifyFailure(data.message || 'Delete application failed');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(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('appType').value = app?.Type || '';
|
||||||
document.getElementById('appStatus').value = app?.Status || 'online';
|
document.getElementById('appStatus').value = app?.Status || 'online';
|
||||||
document.getElementById('appDescription').value = app?.Description || '';
|
document.getElementById('appDescription').value = app?.Description || '';
|
||||||
|
document.getElementById('appIcon').value = app?.Icon || app?.icon || '';
|
||||||
document.getElementById('appUrl').value = app?.Url || app?.url || '';
|
document.getElementById('appUrl').value = app?.Url || app?.url || '';
|
||||||
this.editingAppId = app?.AppId;
|
this.editingAppId = app?.AppId;
|
||||||
this.closeModals();
|
this.closeModals();
|
||||||
@@ -618,6 +699,7 @@ class AccountManager {
|
|||||||
document.getElementById('appType').value = app?.Type || '';
|
document.getElementById('appType').value = app?.Type || '';
|
||||||
document.getElementById('appStatus').value = app?.Status || 'online';
|
document.getElementById('appStatus').value = app?.Status || 'online';
|
||||||
document.getElementById('appDescription').value = app?.Description || '';
|
document.getElementById('appDescription').value = app?.Description || '';
|
||||||
|
document.getElementById('appIcon').value = app?.Icon || app?.icon || '';
|
||||||
document.getElementById('appUrl').value = app?.Url || '';
|
document.getElementById('appUrl').value = app?.Url || '';
|
||||||
this.editingAppId = app?.AppId;
|
this.editingAppId = app?.AppId;
|
||||||
this.closeModals();
|
this.closeModals();
|
||||||
@@ -631,6 +713,7 @@ class AccountManager {
|
|||||||
document.querySelectorAll('#addAccountBtn').forEach(btn => {
|
document.querySelectorAll('#addAccountBtn').forEach(btn => {
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
this.editingAccountId = undefined;
|
this.editingAccountId = undefined;
|
||||||
|
this.pendingAccountAppId = undefined;
|
||||||
this.openAccountModal();
|
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();
|
e.preventDefault();
|
||||||
const accountForm = document.getElementById('accountForm');
|
const accountForm = document.getElementById('accountForm');
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
@@ -653,23 +765,23 @@ class AccountManager {
|
|||||||
const accountPassword = (accountForm?.querySelector('#accountPassword')?.value || '').trim();
|
const accountPassword = (accountForm?.querySelector('#accountPassword')?.value || '').trim();
|
||||||
const accountEmail = ((accountForm?.querySelector('#accountOwner')?.value || '').trim()) || this.currentUser?.Username || this.currentUser?.username || '';
|
const accountEmail = ((accountForm?.querySelector('#accountOwner')?.value || '').trim()) || this.currentUser?.Username || this.currentUser?.username || '';
|
||||||
if (!accountForm) {
|
if (!accountForm) {
|
||||||
alert('Account form not found.');
|
this.notifyFailure('Account form not found.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
alert('User is not authenticated. Please login again.');
|
this.notifyFailure('User is not authenticated. Please login again.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!appId) {
|
if (!appId) {
|
||||||
alert('Please select a service.');
|
this.notifyWarning('Please select a service.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!accountUsername) {
|
if (!accountUsername) {
|
||||||
alert('Please enter a username.');
|
this.notifyWarning('Please enter a username.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!accountPassword) {
|
if (!accountPassword) {
|
||||||
alert('Please enter a password.');
|
this.notifyWarning('Please enter a password.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const payload = {
|
const payload = {
|
||||||
@@ -693,25 +805,37 @@ class AccountManager {
|
|||||||
}).then(res => res.json()).then(data => {
|
}).then(res => res.json()).then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
this.editingAccountId = undefined;
|
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();
|
this.closeModals();
|
||||||
location.href = './accounts.html';
|
this.refreshAccountsUI();
|
||||||
} else {
|
} else {
|
||||||
alert(data.message || 'Save account failed');
|
this.notifyFailure(data.message || 'Save account failed');
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error(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();
|
e.preventDefault();
|
||||||
const payload = {
|
const payload = {
|
||||||
name: document.getElementById('appName').value,
|
name: document.getElementById('appName').value,
|
||||||
type: document.getElementById('appType').value,
|
type: document.getElementById('appType').value,
|
||||||
status: document.getElementById('appStatus').value,
|
status: document.getElementById('appStatus').value,
|
||||||
icon: 'cloud',
|
icon: (document.getElementById('appIcon')?.value || 'apps').trim() || 'apps',
|
||||||
description: document.getElementById('appDescription')?.value || '',
|
description: document.getElementById('appDescription')?.value || '',
|
||||||
url: (document.getElementById('appUrl')?.value || '').trim()
|
url: (document.getElementById('appUrl')?.value || '').trim()
|
||||||
};
|
};
|
||||||
@@ -727,24 +851,38 @@ class AccountManager {
|
|||||||
}).then(res => res.json()).then(data => {
|
}).then(res => res.json()).then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
this.editingAppId = undefined;
|
this.editingAppId = undefined;
|
||||||
alert(isEdit ? 'Application updated successfully' : 'Application created successfully');
|
this.notifySuccess(isEdit ? 'Application updated successfully' : 'Application created successfully');
|
||||||
this.closeModals();
|
this.closeModals();
|
||||||
location.href = './applications.html';
|
this.refreshApplicationsUI();
|
||||||
} else {
|
} else {
|
||||||
alert(data.message || 'Save application failed');
|
this.notifyFailure(data.message || 'Save application failed');
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error(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() {
|
openAccountModal() {
|
||||||
// Refresh service options so newly added applications appear
|
// Refresh service options so newly added applications appear
|
||||||
const serviceSelect = document.getElementById('accountService');
|
const serviceSelect = document.getElementById('accountService');
|
||||||
if (serviceSelect) {
|
if (serviceSelect) {
|
||||||
serviceSelect.innerHTML = `<option value="">Select a service</option>` +
|
serviceSelect.innerHTML = `<option value="">Select a service</option>` +
|
||||||
this.applications.map(app => `<option value="${app.AppId}">${app.Name}</option>`).join('');
|
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) {
|
if (this.editingAccountId === undefined) {
|
||||||
@@ -768,8 +906,10 @@ class AccountManager {
|
|||||||
document.getElementById('appName').value = '';
|
document.getElementById('appName').value = '';
|
||||||
document.getElementById('appType').value = '';
|
document.getElementById('appType').value = '';
|
||||||
document.getElementById('appStatus').value = 'online';
|
document.getElementById('appStatus').value = 'online';
|
||||||
|
const iconInput = document.getElementById('appIcon');
|
||||||
const desc = document.getElementById('appDescription');
|
const desc = document.getElementById('appDescription');
|
||||||
const url = document.getElementById('appUrl');
|
const url = document.getElementById('appUrl');
|
||||||
|
if (iconInput) iconInput.value = '';
|
||||||
if (desc) desc.value = '';
|
if (desc) desc.value = '';
|
||||||
if (url) url.value = '';
|
if (url) url.value = '';
|
||||||
}
|
}
|
||||||
@@ -108,6 +108,11 @@
|
|||||||
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Type</label>
|
<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">
|
<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>
|
||||||
|
<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>
|
<div>
|
||||||
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Description</label>
|
<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>
|
<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>
|
<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 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>
|
||||||
|
<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>
|
<div>
|
||||||
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Description</label>
|
<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>
|
<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>
|
||||||
@@ -3,12 +3,15 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
<title>VaultSentinel - Accounts Management</title>
|
<title>Robot Manager Account - Accounts Management</title>
|
||||||
<!-- Fonts -->
|
<!-- 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"/>
|
<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 -->
|
<!-- 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"/>
|
<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.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">
|
<script id="tailwind-config">
|
||||||
tailwind.config = {
|
tailwind.config = {
|
||||||
darkMode: "class",
|
darkMode: "class",
|
||||||
@@ -240,6 +243,7 @@
|
|||||||
mainContent.innerHTML = app.getAccountsContent();
|
mainContent.innerHTML = app.getAccountsContent();
|
||||||
app.setupAccountRowListeners();
|
app.setupAccountRowListeners();
|
||||||
app.setupAddButtonListeners();
|
app.setupAddButtonListeners();
|
||||||
|
app.setupFilters();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -3,12 +3,15 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
<title>VaultSentinel - Applications Management</title>
|
<title>Robot Manager Account - Applications Management</title>
|
||||||
<!-- Fonts -->
|
<!-- 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"/>
|
<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 -->
|
<!-- 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"/>
|
<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.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">
|
<script id="tailwind-config">
|
||||||
tailwind.config = {
|
tailwind.config = {
|
||||||
darkMode: "class",
|
darkMode: "class",
|
||||||
@@ -227,6 +230,7 @@
|
|||||||
mainContent.innerHTML = app.getApplicationsContent();
|
mainContent.innerHTML = app.getApplicationsContent();
|
||||||
app.setupAccountRowListeners();
|
app.setupAccountRowListeners();
|
||||||
app.setupAddButtonListeners();
|
app.setupAddButtonListeners();
|
||||||
|
app.setupFilters();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -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"/>
|
<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 -->
|
<!-- 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"/>
|
<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.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">
|
<script id="tailwind-config">
|
||||||
tailwind.config = {
|
tailwind.config = {
|
||||||
darkMode: "class",
|
darkMode: "class",
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
<title>VaultSentinel - Login</title>
|
<title>Robotics Account - Login</title>
|
||||||
<!-- Fonts -->
|
<!-- 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"/>
|
<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 -->
|
<!-- Material Symbols -->
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
<span class="material-symbols-outlined text-primary text-4xl">security</span>
|
<span class="material-symbols-outlined text-primary text-4xl">security</span>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<p class="text-xs uppercase tracking-widest text-on-surface-variant font-bold mt-2">Account Management System</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -173,9 +173,9 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Footer -->
|
<!-- 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>
|
<p class="text-[10px] text-on-surface-variant/60">Default credentials for demo: admin / admin</p>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bottom info -->
|
<!-- Bottom info -->
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
Flask==2.3.2
|
|
||||||
Flask-CORS==4.0.0
|
|
||||||
pyodbc==4.0.35
|
|
||||||
python-dotenv==1.0.0
|
|
||||||
480
server_python.py
480
server_python.py
@@ -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.
@@ -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
|
|
||||||
|
|
||||||
========================================
|
|
||||||
Reference in New Issue
Block a user