update agent

This commit is contained in:
2026-07-17 15:41:05 +07:00
parent 13bea66473
commit 476c41cf08
15 changed files with 467 additions and 20 deletions

View File

@@ -71,7 +71,7 @@ CREATE TABLE dbo.Packages
CONSTRAINT DF_Packages_IsActive DEFAULT 1,
CONSTRAINT FK_Packages_CreatedByUser
FOREIGN KEY (CreatedByUserId) REFERENCES dbo.Users(Id),
CONSTRAINT CK_Packages_PackageType CHECK (PackageType IN (N'deb', N'docker')),
CONSTRAINT CK_Packages_PackageType CHECK (PackageType IN (N'deb', N'apt', N'docker')),
CONSTRAINT CK_Packages_PackageCode_NotBlank CHECK (LEN(LTRIM(RTRIM(PackageCode))) > 0),
CONSTRAINT CK_Packages_PackageName_NotBlank CHECK (LEN(LTRIM(RTRIM(PackageName))) > 0)
);

View File

@@ -204,10 +204,14 @@ function helpers() {
return statusClassMap[status] || 'badge-muted';
},
packageTypeLabel(type) {
return type === 'docker' ? 'Docker' : '.deb';
if (type === 'docker') return 'Docker';
if (type === 'apt') return 'APT';
return '.deb';
},
packageTypeClass(type) {
return type === 'docker' ? 'badge-info' : 'badge-primary';
if (type === 'docker') return 'badge-info';
if (type === 'apt') return 'badge-warning';
return 'badge-primary';
}
};
}
@@ -848,7 +852,8 @@ function requireAdmin(req, res, next) {
}
function normalizePackageType(value) {
return String(value || 'deb').toLowerCase() === 'docker' ? 'docker' : 'deb';
const packageType = String(value || 'deb').toLowerCase();
return ['deb', 'apt', 'docker'].includes(packageType) ? packageType : 'deb';
}
function normalizeApplicationStatus(value) {
@@ -1694,8 +1699,13 @@ app.get('/packages/export.csv', asyncRoute(async (req, res) => {
app.post('/packages', upload.single('packageFile'), asyncRoute(async (req, res) => {
try {
const artifact = await getArtifactFromUpload(req.file);
const packageType = normalizePackageType(req.body.packageType);
if (packageType === 'apt' && req.file) {
await removeUploadedFile(req.file);
}
const artifact = packageType === 'apt'
? { filePath: null, checksum: null, fileSizeBytes: null }
: await getArtifactFromUpload(req.file);
const packageCode = String(req.body.packageCode || '').trim();
const packageName = String(req.body.packageName || '').trim();
const version = String(req.body.version || '').trim();
@@ -1742,7 +1752,14 @@ app.post('/packages', upload.single('packageFile'), asyncRoute(async (req, res)
createdByUserId: req.currentUser.id
});
redirectWithNotice(res, '/packages', 'success', 'Đã upload package và lưu vào database.');
redirectWithNotice(
res,
'/packages',
'success',
packageType === 'apt'
? 'Đã tạo APT package metadata; không cần upload artifact.'
: 'Đã upload package và lưu vào database.'
);
} catch (error) {
await removeUploadedFile(req.file);
if (error.code === 'DUPLICATE_PACKAGE') {
@@ -1758,7 +1775,6 @@ app.post('/package-versions', upload.single('packageFile'), asyncRoute(async (re
let versionInput = null;
try {
const artifact = await getArtifactFromUpload(req.file);
const version = String(req.body.version || '').trim();
if (!req.body.packageId || !version) {
@@ -1780,6 +1796,13 @@ app.post('/package-versions', upload.single('packageFile'), asyncRoute(async (re
return;
}
if (packageItem.type === 'apt' && req.file) {
await removeUploadedFile(req.file);
}
const artifact = packageItem.type === 'apt'
? { filePath: null, checksum: null, fileSizeBytes: null }
: await getArtifactFromUpload(req.file);
if (packageItem.type === 'deb') {
const metadataMessage = await getDebUploadMetadataValidationMessage(req.file, packageItem.code, version);
if (metadataMessage) {

View File

@@ -10,6 +10,7 @@ const EMAIL_CONFIRMATION_EXPIRES_MS = Number(process.env.EMAIL_CONFIRMATION_EXPI
let emailConfirmationSchemaPromise;
let applicationOpenUrlSchemaPromise;
let packageTypeSchemaPromise;
function padDatePart(value) {
return String(value).padStart(2, '0');
@@ -326,6 +327,37 @@ LEFT JOIN dbo.PackageVersions AS pv
return applicationOpenUrlSchemaPromise;
}
async function ensurePackageTypeSchema() {
if (!packageTypeSchemaPromise) {
packageTypeSchemaPromise = getPool().then((pool) => pool.request().query(`
IF EXISTS (
SELECT 1
FROM sys.check_constraints
WHERE name = N'CK_Packages_PackageType'
AND parent_object_id = OBJECT_ID(N'dbo.Packages')
AND definition NOT LIKE N'%apt%'
)
BEGIN
ALTER TABLE dbo.Packages DROP CONSTRAINT CK_Packages_PackageType;
END;
IF NOT EXISTS (
SELECT 1
FROM sys.check_constraints
WHERE name = N'CK_Packages_PackageType'
AND parent_object_id = OBJECT_ID(N'dbo.Packages')
)
BEGIN
ALTER TABLE dbo.Packages WITH CHECK
ADD CONSTRAINT CK_Packages_PackageType
CHECK (PackageType IN (N'deb', N'apt', N'docker'));
END;
`));
}
return packageTypeSchemaPromise;
}
function normalizePackageStatus(isActive) {
return isActive ? 'Active' : 'Archived';
}
@@ -850,6 +882,7 @@ async function deleteUser(userId) {
}
async function listPackages() {
await ensurePackageTypeSchema();
const pool = await getPool();
const result = await pool.request().query(`
SELECT *
@@ -996,7 +1029,10 @@ async function getApplicationById(id) {
}
async function getApplicationManifest(appCode, version, baseUrl) {
await ensureApplicationOpenUrlSchema();
await Promise.all([
ensureApplicationOpenUrlSchema(),
ensurePackageTypeSchema()
]);
const pool = await getPool();
const appResult = await pool.request()
.input('AppCode', sql.NVarChar(100), String(appCode || '').trim())
@@ -1048,6 +1084,17 @@ async function getApplicationManifest(appCode, version, baseUrl) {
const components = componentResult.recordset.map((row) => {
const installOrder = Number(row.InstallOrder || 10);
if (row.PackageType === 'apt') {
return {
componentId: row.PackageCode,
type: 'apt',
installOrder,
required: true,
packageName: row.PackageCode,
version: row.Version || undefined
};
}
if (row.PackageType === 'docker') {
return {
componentId: row.PackageCode,
@@ -1167,6 +1214,7 @@ async function getPackageVersionDownload(packageVersionId) {
}
async function createPackageWithVersion(input) {
await ensurePackageTypeSchema();
const pool = await getPool();
const transaction = new sql.Transaction(pool);

View File

@@ -24,6 +24,7 @@
<select data-filter-select data-filter-column="type" data-filter-table="packagesTable">
<option value="">Tất cả</option>
<option value="deb">.deb</option>
<option value="apt">APT</option>
<option value="docker">Docker</option>
</select>
</label>

View File

@@ -16,6 +16,7 @@
<span>Package type</span>
<select name="packageType">
<option value="deb">.deb</option>
<option value="apt">APT (Ubuntu repository)</option>
<option value="docker">Docker</option>
</select>
</label>
@@ -60,6 +61,9 @@
</div>
</div>
</div>
<div class="form-field full">
<small>APT packages only store metadata and do not need an uploaded file. Package code must be an Agent-allowlisted APT name, for example <code>postgresql</code>.</small>
</div>
<label class="form-field full">
<span>Docker image/tag</span>
<input type="text" name="dockerImage" placeholder="registry.local/robot/fleet-agent:1.9.0">