web server

This commit is contained in:
2026-05-20 14:10:25 +07:00
parent 5ade939ff9
commit 190d2418da
30 changed files with 8917 additions and 0 deletions

49
web-server/src/db.js Normal file
View File

@@ -0,0 +1,49 @@
const sql = require('mssql');
let poolPromise;
function boolFromEnv(value, fallback) {
if (value === undefined || value === null || value === '') return fallback;
return ['1', 'true', 'yes'].includes(String(value).toLowerCase());
}
function getConfig() {
return {
server: process.env.SQLSERVER_HOST || 'localhost',
port: Number(process.env.SQLSERVER_PORT || 1433),
database: process.env.SQLSERVER_DATABASE || 'RobotInstaller',
user: process.env.SQLSERVER_USER,
password: process.env.SQLSERVER_PASSWORD,
options: {
encrypt: boolFromEnv(process.env.SQLSERVER_ENCRYPT, false),
trustServerCertificate: boolFromEnv(process.env.SQLSERVER_TRUST_SERVER_CERTIFICATE, true)
},
pool: {
max: 10,
min: 0,
idleTimeoutMillis: 30000
}
};
}
async function getPool() {
if (!poolPromise) {
poolPromise = sql.connect(getConfig());
}
return poolPromise;
}
async function closePool() {
if (!poolPromise) return;
const pool = await poolPromise;
await pool.close();
poolPromise = undefined;
}
module.exports = {
sql,
getPool,
closePool
};

100
web-server/src/mailer.js Normal file
View File

@@ -0,0 +1,100 @@
const nodemailer = require('nodemailer');
let transporter;
function boolFromEnv(value, fallback) {
if (value === undefined || value === null || value === '') return fallback;
return ['1', 'true', 'yes'].includes(String(value).toLowerCase());
}
function isConfigured() {
return Boolean(process.env.SMTP_HOST && process.env.SMTP_USER);
}
function escapeHtml(value) {
return String(value || '')
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
function getSenderAddress() {
return process.env.MAIL_FROM || process.env.SMTP_USER;
}
function getTransporter() {
if (!isConfigured()) return null;
if (!transporter) {
const port = Number(process.env.SMTP_PORT || 587);
transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port,
secure: boolFromEnv(process.env.SMTP_SECURE, port === 465),
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASSWORD || ''
}
});
}
return transporter;
}
async function sendConfirmationEmail({ to, name, confirmUrl }) {
const mailTransporter = getTransporter();
const safeName = escapeHtml(name || to);
const safeConfirmUrl = escapeHtml(confirmUrl);
if (process.env.NODE_ENV !== 'production') {
console.info(`Email confirmation link for ${to}: ${confirmUrl}`);
}
if (!mailTransporter) {
console.warn(`SMTP is not configured. Email confirmation link for ${to}: ${confirmUrl}`);
return { sent: false, reason: 'SMTP_NOT_CONFIGURED' };
}
const result = await mailTransporter.sendMail({
from: getSenderAddress(),
to,
subject: 'Xác nhận tài khoản Robot Installer',
text: [
`Xin chào ${name || to},`,
'',
'Bạn vừa đăng ký tài khoản hoặc cập nhật email Robot Installer.',
`Bấm link sau để xác nhận email và kích hoạt tài khoản: ${confirmUrl}`,
'',
'Nếu bạn không thực hiện đăng ký này, hãy bỏ qua email.'
].join('\n'),
html: `
<div style="font-family: Arial, sans-serif; color: #172033; line-height: 1.5;">
<h2 style="margin: 0 0 12px;">Xác nhận tài khoản Robot Installer</h2>
<p>Xin chào ${safeName},</p>
<p>Bạn vừa đăng ký tài khoản hoặc cập nhật email Robot Installer. Bấm nút bên dưới để xác nhận email và kích hoạt tài khoản.</p>
<p>
<a href="${safeConfirmUrl}" style="background: #3755c3; border-radius: 8px; color: #ffffff; display: inline-block; font-weight: 700; padding: 10px 14px; text-decoration: none;">
Xác nhận email
</a>
</p>
<p style="color: #566166; font-size: 13px;">Nếu nút không mở được, copy link này vào trình duyệt:<br>${safeConfirmUrl}</p>
</div>
`
});
console.info('Confirmation email sent:', {
to,
messageId: result.messageId,
accepted: result.accepted,
rejected: result.rejected
});
return { sent: true };
}
module.exports = {
sendConfirmationEmail
};

137
web-server/src/mock-data.js Normal file
View File

@@ -0,0 +1,137 @@
const packages = [
{
id: 'navigation-stack',
code: 'NAV-STACK',
name: 'Navigation Stack',
type: 'deb',
latestVersion: '2.4.1',
latestReleaseDate: '2026-05-17',
status: 'Active',
owner: 'Dũng Tào',
description: 'Core navigation package for robot route planning and localization.',
artifact: '/packages/navigation-stack_2.4.1_amd64.deb',
versions: [
{ version: '2.4.1', releaseDate: '2026-05-17', uploadedBy: 'Dũng Tào', status: 'Latest', size: '84.2 MB', changeLog: 'Improve recovery flow and map alignment.' },
{ version: '2.4.0', releaseDate: '2026-05-10', uploadedBy: 'Dũng Tào', status: 'Stable', size: '83.8 MB', changeLog: 'Add obstacle avoidance tuning profile.' },
{ version: '2.3.8', releaseDate: '2026-04-28', uploadedBy: 'QA Robot', status: 'Deprecated', size: '81.6 MB', changeLog: 'Legacy build kept for rollback.' }
]
},
{
id: 'fleet-agent',
code: 'FLEET-AGENT',
name: 'Fleet Agent',
type: 'docker',
latestVersion: '1.9.0',
latestReleaseDate: '2026-05-16',
status: 'Active',
owner: 'Minh Anh',
description: 'Docker service that connects robot clients to Fleet Manager.',
artifact: 'registry.local/robot/fleet-agent:1.9.0',
versions: [
{ version: '1.9.0', releaseDate: '2026-05-16', uploadedBy: 'Minh Anh', status: 'Latest', size: '412 MB', changeLog: 'Add heartbeat metrics and websocket retry.' },
{ version: '1.8.2', releaseDate: '2026-05-01', uploadedBy: 'Minh Anh', status: 'Stable', size: '405 MB', changeLog: 'Fix token renewal after sleep.' }
]
},
{
id: 'map-sync',
code: 'MAP-SYNC',
name: 'Map Sync Service',
type: 'docker',
latestVersion: '3.1.2',
latestReleaseDate: '2026-05-12',
status: 'Active',
owner: 'Hải Nam',
description: 'Synchronizes robot map revisions between web server and edge clients.',
artifact: 'registry.local/robot/map-sync:3.1.2',
versions: [
{ version: '3.1.2', releaseDate: '2026-05-12', uploadedBy: 'Hải Nam', status: 'Latest', size: '268 MB', changeLog: 'Compress map snapshot before upload.' },
{ version: '3.0.5', releaseDate: '2026-04-22', uploadedBy: 'Hải Nam', status: 'Stable', size: '251 MB', changeLog: 'Improve checksum validation.' }
]
},
{
id: 'ui-kiosk',
code: 'UI-KIOSK',
name: 'Robot Kiosk UI',
type: 'deb',
latestVersion: '0.8.6',
latestReleaseDate: '2026-05-08',
status: 'Testing',
owner: 'Linh Phạm',
description: 'Touch-screen UI package for robot kiosk mode.',
artifact: '/packages/robot-kiosk-ui_0.8.6_amd64.deb',
versions: [
{ version: '0.8.6', releaseDate: '2026-05-08', uploadedBy: 'Linh Phạm', status: 'Latest', size: '62.4 MB', changeLog: 'Refine operator handoff screens.' },
{ version: '0.8.3', releaseDate: '2026-04-19', uploadedBy: 'Linh Phạm', status: 'Deprecated', size: '59.9 MB', changeLog: 'Initial kiosk dashboard.' }
]
}
];
const applications = [
{
id: 'warehouse-basic',
code: 'APP-WH-BASIC',
name: 'Warehouse Basic',
version: '1.2.0',
status: 'Released',
createdAt: '2026-05-18',
createdBy: 'Dũng Tào',
notes: 'Base package set for warehouse robots.',
packages: [
{ code: 'NAV-STACK', name: 'Navigation Stack', type: 'deb', selectedVersion: '2.4.1' },
{ code: 'FLEET-AGENT', name: 'Fleet Agent', type: 'docker', selectedVersion: '1.9.0' },
{ code: 'MAP-SYNC', name: 'Map Sync Service', type: 'docker', selectedVersion: '3.1.2' }
]
},
{
id: 'kiosk-demo',
code: 'APP-KIOSK-DEMO',
name: 'Kiosk Demo',
version: '0.4.0',
status: 'Draft',
createdAt: '2026-05-16',
createdBy: 'Linh Phạm',
notes: 'Demo bundle for touch kiosk testing.',
packages: [
{ code: 'UI-KIOSK', name: 'Robot Kiosk UI', type: 'deb', selectedVersion: '0.8.6' },
{ code: 'NAV-STACK', name: 'Navigation Stack', type: 'deb', selectedVersion: '2.4.0' }
]
},
{
id: 'fleet-edge',
code: 'APP-FLEET-EDGE',
name: 'Fleet Edge',
version: '2.0.1',
status: 'Released',
createdAt: '2026-05-11',
createdBy: 'Minh Anh',
notes: 'Fleet manager edge runtime.',
packages: [
{ code: 'FLEET-AGENT', name: 'Fleet Agent', type: 'docker', selectedVersion: '1.9.0' },
{ code: 'MAP-SYNC', name: 'Map Sync Service', type: 'docker', selectedVersion: '3.0.5' }
]
}
];
const activity = [
{ title: 'Upload version 2.4.1', detail: 'Navigation Stack được đặt là latest', time: '09:25', icon: 'upload_file' },
{ title: 'Release Warehouse Basic', detail: 'App version 1.2.0 đã sẵn sàng đóng gói', time: 'Hôm qua', icon: 'task_alt' },
{ title: 'Update Fleet Agent', detail: 'Thêm heartbeat metrics cho Docker image', time: '16/05', icon: 'sync' }
];
module.exports = {
currentUser: {
name: 'Dũng Tào',
role: 'Admin',
email: 'admin@robotics.local'
},
packages,
applications,
activity,
stats: {
totalPackages: packages.length,
activePackages: packages.filter((item) => item.status === 'Active').length,
totalVersions: packages.reduce((total, item) => total + item.versions.length, 0),
totalApplications: applications.length,
releasedApplications: applications.filter((item) => item.status === 'Released').length
}
};

1231
web-server/src/repository.js Normal file

File diff suppressed because it is too large Load Diff