web server
This commit is contained in:
49
web-server/src/db.js
Normal file
49
web-server/src/db.js
Normal 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
100
web-server/src/mailer.js
Normal 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, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
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
137
web-server/src/mock-data.js
Normal 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
1231
web-server/src/repository.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user