fix server - agent

This commit is contained in:
2026-05-27 08:37:47 +07:00
parent 13765e58d2
commit b0443d5950
13 changed files with 170 additions and 11 deletions

View File

@@ -38,6 +38,7 @@ class TaskRunner:
manifest["appName"], manifest["appName"],
manifest["version"], manifest["version"],
manifest_hash, manifest_hash,
manifest.get("openUrl"),
) )
self.repository.update_task( self.repository.update_task(
task_id, task_id,

View File

@@ -181,6 +181,7 @@ class AppManifest(CamelModel):
app_id: str = Field(alias="appId") app_id: str = Field(alias="appId")
app_name: str = Field(alias="appName") app_name: str = Field(alias="appName")
version: str version: str
open_url: str | None = Field(default=None, alias="openUrl")
architecture: str = "amd64" architecture: str = "amd64"
components: list[dict[str, Any]] components: list[dict[str, Any]]
signature: str | None = None signature: str | None = None

View File

@@ -38,6 +38,7 @@ CREATE TABLE IF NOT EXISTS installed_apps (
app_id TEXT PRIMARY KEY, app_id TEXT PRIMARY KEY,
app_name TEXT NOT NULL, app_name TEXT NOT NULL,
version TEXT NOT NULL, version TEXT NOT NULL,
open_url TEXT,
manifest_hash TEXT, manifest_hash TEXT,
status TEXT NOT NULL, status TEXT NOT NULL,
installed_at TEXT NOT NULL, installed_at TEXT NOT NULL,
@@ -86,6 +87,15 @@ CREATE TABLE IF NOT EXISTS agent_config (
""" """
def _ensure_column(connection: sqlite3.Connection, table: str, column: str, definition: str) -> None:
columns = {
row["name"]
for row in connection.execute(f"PRAGMA table_info({table})").fetchall()
}
if column not in columns:
connection.execute(f"ALTER TABLE {table} ADD COLUMN {column} {definition}")
def get_connection() -> sqlite3.Connection: def get_connection() -> sqlite3.Connection:
connection = sqlite3.connect(settings.db_path, timeout=30) connection = sqlite3.connect(settings.db_path, timeout=30)
connection.row_factory = sqlite3.Row connection.row_factory = sqlite3.Row
@@ -99,4 +109,4 @@ def initialize_database() -> None:
settings.log_dir.mkdir(parents=True, exist_ok=True) settings.log_dir.mkdir(parents=True, exist_ok=True)
with get_connection() as connection: with get_connection() as connection:
connection.executescript(SCHEMA) connection.executescript(SCHEMA)
_ensure_column(connection, "installed_apps", "open_url", "TEXT")

View File

@@ -160,7 +160,7 @@ class Repository:
with get_connection() as connection: with get_connection() as connection:
rows = connection.execute( rows = connection.execute(
""" """
SELECT app_id, app_name, version, manifest_hash, status, installed_at, updated_at SELECT app_id, app_name, version, open_url, manifest_hash, status, installed_at, updated_at
FROM installed_apps FROM installed_apps
ORDER BY app_name ASC ORDER BY app_name ASC
""" """
@@ -173,22 +173,24 @@ class Repository:
app_name: str, app_name: str,
version: str, version: str,
manifest_hash: str | None, manifest_hash: str | None,
open_url: str | None = None,
status: str = "installed", status: str = "installed",
) -> None: ) -> None:
now = utc_now() now = utc_now()
with get_connection() as connection: with get_connection() as connection:
connection.execute( connection.execute(
""" """
INSERT INTO installed_apps (app_id, app_name, version, manifest_hash, status, installed_at, updated_at) INSERT INTO installed_apps (app_id, app_name, version, open_url, manifest_hash, status, installed_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(app_id) DO UPDATE SET ON CONFLICT(app_id) DO UPDATE SET
app_name = excluded.app_name, app_name = excluded.app_name,
version = excluded.version, version = excluded.version,
open_url = excluded.open_url,
manifest_hash = excluded.manifest_hash, manifest_hash = excluded.manifest_hash,
status = excluded.status, status = excluded.status,
updated_at = excluded.updated_at updated_at = excluded.updated_at
""", """,
(app_id, app_name, version, manifest_hash, status, now, now), (app_id, app_name, version, open_url, manifest_hash, status, now, now),
) )
def delete_installed_app(self, app_id: str) -> None: def delete_installed_app(self, app_id: str) -> None:
@@ -252,4 +254,3 @@ class Repository:
def export_manifest_hash(self, manifest: dict[str, Any]) -> str: def export_manifest_hash(self, manifest: dict[str, Any]) -> str:
return json.dumps(manifest, sort_keys=True, separators=(",", ":")) return json.dumps(manifest, sort_keys=True, separators=(",", ":"))

View File

@@ -125,6 +125,7 @@ CREATE TABLE dbo.Applications
Status NVARCHAR(50) NOT NULL Status NVARCHAR(50) NOT NULL
CONSTRAINT DF_Applications_Status DEFAULT N'Draft', CONSTRAINT DF_Applications_Status DEFAULT N'Draft',
Notes NVARCHAR(500) NULL, Notes NVARCHAR(500) NULL,
OpenUrl NVARCHAR(500) NULL,
CONSTRAINT FK_Applications_CreatedByUser CONSTRAINT FK_Applications_CreatedByUser
FOREIGN KEY (CreatedByUserId) REFERENCES dbo.Users(Id), FOREIGN KEY (CreatedByUserId) REFERENCES dbo.Users(Id),
CONSTRAINT CK_Applications_Status CHECK (Status IN (N'Draft', N'Released', N'Archived')), CONSTRAINT CK_Applications_Status CHECK (Status IN (N'Draft', N'Released', N'Archived')),

View File

@@ -76,6 +76,7 @@ SELECT
a.Description, a.Description,
a.Status, a.Status,
a.Notes, a.Notes,
a.OpenUrl,
a.CreatedAt, a.CreatedAt,
a.UpdatedAt, a.UpdatedAt,
a.CreatedByUserId, a.CreatedByUserId,
@@ -94,6 +95,7 @@ GROUP BY
a.Description, a.Description,
a.Status, a.Status,
a.Notes, a.Notes,
a.OpenUrl,
a.CreatedAt, a.CreatedAt,
a.UpdatedAt, a.UpdatedAt,
a.CreatedByUserId, a.CreatedByUserId,
@@ -108,6 +110,7 @@ SELECT
a.AppCode, a.AppCode,
a.AppName, a.AppName,
a.AppVersion, a.AppVersion,
a.OpenUrl AS AppOpenUrl,
ap.PackageId, ap.PackageId,
p.PackageCode, p.PackageCode,
p.PackageName, p.PackageName,

View File

@@ -412,6 +412,7 @@
name: trigger.dataset.appName || '', name: trigger.dataset.appName || '',
version: trigger.dataset.appVersion || '', version: trigger.dataset.appVersion || '',
status: trigger.dataset.appStatus || 'Draft', status: trigger.dataset.appStatus || 'Draft',
openUrl: trigger.dataset.appOpenUrl || '',
notes: trigger.dataset.appNotes || '', notes: trigger.dataset.appNotes || '',
packages: parseJsonAttribute(trigger.dataset.appPackages, []) packages: parseJsonAttribute(trigger.dataset.appPackages, [])
}; };
@@ -446,6 +447,7 @@
setEditAppField(form, 'appName', app.name); setEditAppField(form, 'appName', app.name);
setEditAppField(form, 'appVersion', app.version); setEditAppField(form, 'appVersion', app.version);
setEditAppField(form, 'status', app.status); setEditAppField(form, 'status', app.status);
setEditAppField(form, 'openUrl', app.openUrl);
setEditAppField(form, 'notes', app.notes); setEditAppField(form, 'notes', app.notes);
form.querySelectorAll('[data-edit-app-package]').forEach((checkbox) => { form.querySelectorAll('[data-edit-app-package]').forEach((checkbox) => {

View File

@@ -844,6 +844,24 @@ function normalizeApplicationStatus(value) {
return ['Draft', 'Released', 'Archived'].includes(value) ? value : 'Draft'; return ['Draft', 'Released', 'Archived'].includes(value) ? value : 'Draft';
} }
function normalizeApplicationOpenUrl(value) {
const text = String(value || '').trim();
if (!text) return '';
const candidate = /^https?:\/\//i.test(text) ? text : `http://${text}`;
try {
const parsed = new URL(candidate);
if (!['http:', 'https:'].includes(parsed.protocol) || !parsed.hostname) {
return null;
}
return parsed.href.replace(/\/+$/, '');
} catch {
return null;
}
}
function isInstallerIdentifier(value) { function isInstallerIdentifier(value) {
return installerIdentifierPattern.test(String(value || '').trim()); return installerIdentifierPattern.test(String(value || '').trim());
} }
@@ -1297,7 +1315,8 @@ app.get('/api/apps', asyncRoute(async (req, res) => {
appName: application.name, appName: application.name,
version: application.version, version: application.version,
status: application.status, status: application.status,
packageCount: application.packageCount packageCount: application.packageCount,
openUrl: application.openUrl
})) }))
}); });
})); }));
@@ -1316,6 +1335,7 @@ app.get('/api/apps/:appCode', asyncRoute(async (req, res) => {
version: application.version, version: application.version,
status: application.status, status: application.status,
packageCount: application.packageCount, packageCount: application.packageCount,
openUrl: application.openUrl,
packages: application.packages packages: application.packages
}); });
})); }));
@@ -1839,7 +1859,7 @@ app.get('/applications', asyncRoute(async (req, res) => {
app.get('/applications/export.csv', asyncRoute(async (req, res) => { app.get('/applications/export.csv', asyncRoute(async (req, res) => {
const applications = await repository.listApplications(); const applications = await repository.listApplications();
const rows = [ const rows = [
['AppCode', 'AppName', 'AppVersion', 'Status', 'CreatedAt', 'CreatedBy', 'PackageCount', 'Packages', 'Notes'], ['AppCode', 'AppName', 'AppVersion', 'Status', 'CreatedAt', 'CreatedBy', 'PackageCount', 'Packages', 'OpenUrl', 'Notes'],
...applications.map((application) => [ ...applications.map((application) => [
application.code, application.code,
application.name, application.name,
@@ -1849,6 +1869,7 @@ app.get('/applications/export.csv', asyncRoute(async (req, res) => {
application.createdBy, application.createdBy,
application.packageCount, application.packageCount,
application.packages.map((packageItem) => `${packageItem.code}@${packageItem.selectedVersion}`).join('; '), application.packages.map((packageItem) => `${packageItem.code}@${packageItem.selectedVersion}`).join('; '),
application.openUrl,
application.notes application.notes
]) ])
]; ];
@@ -1860,12 +1881,18 @@ app.post('/applications', asyncRoute(async (req, res) => {
const appCode = String(req.body.appCode || '').trim(); const appCode = String(req.body.appCode || '').trim();
const appName = String(req.body.appName || '').trim(); const appName = String(req.body.appName || '').trim();
const appVersion = String(req.body.appVersion || '').trim(); const appVersion = String(req.body.appVersion || '').trim();
const openUrl = normalizeApplicationOpenUrl(req.body.openUrl);
if (!appCode || !appName || !appVersion) { if (!appCode || !appName || !appVersion) {
redirectWithNotice(res, '/builder', 'warning', 'Vui lòng nhập App code, App name và App version.'); redirectWithNotice(res, '/builder', 'warning', 'Vui lòng nhập App code, App name và App version.');
return; return;
} }
if (openUrl === null) {
redirectWithNotice(res, '/builder', 'warning', 'Open URL khong hop le. Hay nhap URL http/https, vi du http://127.0.0.1:5000.');
return;
}
if (!isInstallerIdentifier(appCode)) { if (!isInstallerIdentifier(appCode)) {
redirectWithNotice(res, '/builder', 'warning', `App code khong hop le. ${installerIdentifierHint}`); redirectWithNotice(res, '/builder', 'warning', `App code khong hop le. ${installerIdentifierHint}`);
return; return;
@@ -1882,6 +1909,7 @@ app.post('/applications', asyncRoute(async (req, res) => {
appName, appName,
appVersion, appVersion,
notes: req.body.notes, notes: req.body.notes,
openUrl,
status: normalizeApplicationStatus(req.body.status), status: normalizeApplicationStatus(req.body.status),
createdByUserId: req.currentUser.id, createdByUserId: req.currentUser.id,
packages: getSelectedApplicationPackages(req.body) packages: getSelectedApplicationPackages(req.body)
@@ -1904,12 +1932,18 @@ app.post('/applications/:id/edit', asyncRoute(async (req, res) => {
const appCode = String(req.body.appCode || '').trim(); const appCode = String(req.body.appCode || '').trim();
const appName = String(req.body.appName || '').trim(); const appName = String(req.body.appName || '').trim();
const appVersion = String(req.body.appVersion || '').trim(); const appVersion = String(req.body.appVersion || '').trim();
const openUrl = normalizeApplicationOpenUrl(req.body.openUrl);
if (!appCode || !appName || !appVersion) { if (!appCode || !appName || !appVersion) {
redirectWithNotice(res, returnTo, 'warning', 'Vui lòng nhập App code, App name và App version.'); redirectWithNotice(res, returnTo, 'warning', 'Vui lòng nhập App code, App name và App version.');
return; return;
} }
if (openUrl === null) {
redirectWithNotice(res, returnTo, 'warning', 'Open URL khong hop le. Hay nhap URL http/https, vi du http://127.0.0.1:5000.');
return;
}
if (!isInstallerIdentifier(appCode)) { if (!isInstallerIdentifier(appCode)) {
redirectWithNotice(res, returnTo, 'warning', `App code khong hop le. ${installerIdentifierHint}`); redirectWithNotice(res, returnTo, 'warning', `App code khong hop le. ${installerIdentifierHint}`);
return; return;
@@ -1927,6 +1961,7 @@ app.post('/applications/:id/edit', asyncRoute(async (req, res) => {
appName, appName,
appVersion, appVersion,
notes: req.body.notes, notes: req.body.notes,
openUrl,
status: normalizeApplicationStatus(req.body.status), status: normalizeApplicationStatus(req.body.status),
packages: getSelectedApplicationPackages(req.body) packages: getSelectedApplicationPackages(req.body)
}); });

View File

@@ -9,6 +9,7 @@ const PLACEHOLDER_PASSWORD_HASH = 'ui-placeholder-password-hash';
const EMAIL_CONFIRMATION_EXPIRES_MS = Number(process.env.EMAIL_CONFIRMATION_EXPIRES_MS || 1000 * 60 * 60 * 24); const EMAIL_CONFIRMATION_EXPIRES_MS = Number(process.env.EMAIL_CONFIRMATION_EXPIRES_MS || 1000 * 60 * 60 * 24);
let emailConfirmationSchemaPromise; let emailConfirmationSchemaPromise;
let applicationOpenUrlSchemaPromise;
function toDateInputValue(value) { function toDateInputValue(value) {
const date = value ? new Date(value) : new Date(); const date = value ? new Date(value) : new Date();
@@ -185,6 +186,86 @@ async function ensureEmailConfirmationSchema() {
return emailConfirmationSchemaPromise; return emailConfirmationSchemaPromise;
} }
async function ensureApplicationOpenUrlSchema() {
if (!applicationOpenUrlSchemaPromise) {
applicationOpenUrlSchemaPromise = getPool().then((pool) => pool.request().query(`
IF COL_LENGTH(N'dbo.Applications', N'OpenUrl') IS NULL
BEGIN
ALTER TABLE dbo.Applications
ADD OpenUrl NVARCHAR(500) NULL;
END;
EXEC(N'
CREATE OR ALTER VIEW dbo.vw_ApplicationList
AS
SELECT
a.Id,
a.AppCode,
a.AppName,
a.AppVersion,
a.Description,
a.Status,
a.Notes,
a.OpenUrl,
a.CreatedAt,
a.UpdatedAt,
a.CreatedByUserId,
u.Username AS CreatedByUsername,
COUNT_BIG(ap.Id) AS PackageCount
FROM dbo.Applications AS a
INNER JOIN dbo.Users AS u
ON u.Id = a.CreatedByUserId
LEFT JOIN dbo.ApplicationPackages AS ap
ON ap.ApplicationId = a.Id
GROUP BY
a.Id,
a.AppCode,
a.AppName,
a.AppVersion,
a.Description,
a.Status,
a.Notes,
a.OpenUrl,
a.CreatedAt,
a.UpdatedAt,
a.CreatedByUserId,
u.Username;
');
EXEC(N'
CREATE OR ALTER VIEW dbo.vw_ApplicationPackageDetails
AS
SELECT
ap.Id,
ap.ApplicationId,
a.AppCode,
a.AppName,
a.AppVersion,
a.OpenUrl AS AppOpenUrl,
ap.PackageId,
p.PackageCode,
p.PackageName,
p.PackageType,
ap.SelectedVersionId,
pv.Version AS SelectedVersion,
pv.FilePath,
pv.DockerImage,
ap.AddedAt,
ap.Notes
FROM dbo.ApplicationPackages AS ap
INNER JOIN dbo.Applications AS a
ON a.Id = ap.ApplicationId
INNER JOIN dbo.Packages AS p
ON p.Id = ap.PackageId
LEFT JOIN dbo.PackageVersions AS pv
ON pv.Id = ap.SelectedVersionId;
');
`));
}
return applicationOpenUrlSchemaPromise;
}
function normalizePackageStatus(isActive) { function normalizePackageStatus(isActive) {
return isActive ? 'Active' : 'Archived'; return isActive ? 'Active' : 'Archived';
} }
@@ -242,6 +323,7 @@ function mapApplicationRow(row) {
createdAt: formatDate(row.CreatedAt), createdAt: formatDate(row.CreatedAt),
createdBy: row.CreatedByUsername || '', createdBy: row.CreatedByUsername || '',
notes: row.Notes || row.Description || '', notes: row.Notes || row.Description || '',
openUrl: row.OpenUrl || '',
packageCount: Number(row.PackageCount || 0), packageCount: Number(row.PackageCount || 0),
packages: [] packages: []
}; };
@@ -251,6 +333,7 @@ function mapApplicationPackageRow(row) {
return { return {
id: String(row.Id), id: String(row.Id),
applicationId: String(row.ApplicationId), applicationId: String(row.ApplicationId),
appOpenUrl: row.AppOpenUrl || '',
packageId: String(row.PackageId), packageId: String(row.PackageId),
code: row.PackageCode, code: row.PackageCode,
name: row.PackageName, name: row.PackageName,
@@ -759,6 +842,7 @@ async function getPackageById(id) {
} }
async function listApplications() { async function listApplications() {
await ensureApplicationOpenUrlSchema();
const pool = await getPool(); const pool = await getPool();
const result = await pool.request().query(` const result = await pool.request().query(`
SELECT * SELECT *
@@ -771,6 +855,7 @@ async function listApplications() {
} }
async function listApplicationPackages(applicationId) { async function listApplicationPackages(applicationId) {
await ensureApplicationOpenUrlSchema();
const pool = await getPool(); const pool = await getPool();
const request = pool.request(); const request = pool.request();
let whereClause = ''; let whereClause = '';
@@ -816,6 +901,7 @@ async function attachApplicationPackages(applications) {
} }
async function getApplicationById(id) { async function getApplicationById(id) {
await ensureApplicationOpenUrlSchema();
const pool = await getPool(); const pool = await getPool();
const appResult = await pool.request() const appResult = await pool.request()
.input('Id', sql.NVarChar(100), id) .input('Id', sql.NVarChar(100), id)
@@ -834,12 +920,13 @@ async function getApplicationById(id) {
} }
async function getApplicationManifest(appCode, version, baseUrl) { async function getApplicationManifest(appCode, version, baseUrl) {
await ensureApplicationOpenUrlSchema();
const pool = await getPool(); const pool = await getPool();
const appResult = await pool.request() const appResult = await pool.request()
.input('AppCode', sql.NVarChar(100), String(appCode || '').trim()) .input('AppCode', sql.NVarChar(100), String(appCode || '').trim())
.input('AppVersion', sql.NVarChar(50), String(version || '').trim()) .input('AppVersion', sql.NVarChar(50), String(version || '').trim())
.query(` .query(`
SELECT TOP (1) Id, AppCode, AppName, AppVersion SELECT TOP (1) Id, AppCode, AppName, AppVersion, OpenUrl
FROM dbo.Applications FROM dbo.Applications
WHERE (CONVERT(NVARCHAR(36), Id) = @AppCode OR AppCode = @AppCode) WHERE (CONVERT(NVARCHAR(36), Id) = @AppCode OR AppCode = @AppCode)
AND AppVersion = @AppVersion AND AppVersion = @AppVersion
@@ -911,6 +998,7 @@ async function getApplicationManifest(appCode, version, baseUrl) {
appId: String(appRow.Id), appId: String(appRow.Id),
appName: appRow.AppName, appName: appRow.AppName,
version: appRow.AppVersion, version: appRow.AppVersion,
openUrl: appRow.OpenUrl || '',
architecture: 'amd64', architecture: 'amd64',
components components
}; };
@@ -1199,6 +1287,7 @@ async function deletePackageVersion(packageVersionId) {
} }
async function createApplication(input) { async function createApplication(input) {
await ensureApplicationOpenUrlSchema();
const pool = await getPool(); const pool = await getPool();
const transaction = new sql.Transaction(pool); const transaction = new sql.Transaction(pool);
@@ -1210,12 +1299,13 @@ async function createApplication(input) {
.input('AppName', sql.NVarChar(200), input.appName) .input('AppName', sql.NVarChar(200), input.appName)
.input('AppVersion', sql.NVarChar(50), input.appVersion) .input('AppVersion', sql.NVarChar(50), input.appVersion)
.input('Notes', sql.NVarChar(500), input.notes || null) .input('Notes', sql.NVarChar(500), input.notes || null)
.input('OpenUrl', sql.NVarChar(500), input.openUrl || null)
.input('CreatedByUserId', sql.UniqueIdentifier, input.createdByUserId) .input('CreatedByUserId', sql.UniqueIdentifier, input.createdByUserId)
.input('Status', sql.NVarChar(50), normalizeApplicationStatus(input.status)) .input('Status', sql.NVarChar(50), normalizeApplicationStatus(input.status))
.query(` .query(`
INSERT dbo.Applications (AppCode, AppName, AppVersion, Notes, CreatedByUserId, Status) INSERT dbo.Applications (AppCode, AppName, AppVersion, Notes, OpenUrl, CreatedByUserId, Status)
OUTPUT inserted.Id OUTPUT inserted.Id
VALUES (@AppCode, @AppName, @AppVersion, @Notes, @CreatedByUserId, @Status); VALUES (@AppCode, @AppName, @AppVersion, @Notes, @OpenUrl, @CreatedByUserId, @Status);
`); `);
const applicationId = String(appResult.recordset[0].Id); const applicationId = String(appResult.recordset[0].Id);
@@ -1244,6 +1334,7 @@ async function createApplication(input) {
} }
async function updateApplication(input) { async function updateApplication(input) {
await ensureApplicationOpenUrlSchema();
const pool = await getPool(); const pool = await getPool();
const transaction = new sql.Transaction(pool); const transaction = new sql.Transaction(pool);
@@ -1270,6 +1361,7 @@ async function updateApplication(input) {
.input('AppName', sql.NVarChar(200), input.appName) .input('AppName', sql.NVarChar(200), input.appName)
.input('AppVersion', sql.NVarChar(50), input.appVersion) .input('AppVersion', sql.NVarChar(50), input.appVersion)
.input('Notes', sql.NVarChar(500), input.notes || null) .input('Notes', sql.NVarChar(500), input.notes || null)
.input('OpenUrl', sql.NVarChar(500), input.openUrl || null)
.input('Status', sql.NVarChar(50), normalizeApplicationStatus(input.status)) .input('Status', sql.NVarChar(50), normalizeApplicationStatus(input.status))
.query(` .query(`
UPDATE dbo.Applications UPDATE dbo.Applications
@@ -1277,6 +1369,7 @@ async function updateApplication(input) {
AppName = @AppName, AppName = @AppName,
AppVersion = @AppVersion, AppVersion = @AppVersion,
Notes = @Notes, Notes = @Notes,
OpenUrl = @OpenUrl,
Status = @Status, Status = @Status,
UpdatedAt = SYSUTCDATETIME() UpdatedAt = SYSUTCDATETIME()
OUTPUT inserted.Id OUTPUT inserted.Id

View File

@@ -17,6 +17,7 @@
data-app-name="<%= application.name %>" data-app-name="<%= application.name %>"
data-app-version="<%= application.version %>" data-app-version="<%= application.version %>"
data-app-status="<%= application.status %>" data-app-status="<%= application.status %>"
data-app-open-url="<%= application.openUrl %>"
data-app-notes="<%= application.notes %>" data-app-notes="<%= application.notes %>"
data-app-packages="<%= JSON.stringify(application.packages.map((pkg) => ({ packageId: pkg.packageId, selectedVersionId: pkg.selectedVersionId }))) %>" data-app-packages="<%= JSON.stringify(application.packages.map((pkg) => ({ packageId: pkg.packageId, selectedVersionId: pkg.selectedVersionId }))) %>"
> >
@@ -53,6 +54,7 @@
<div><dt>Package count</dt><dd><%= application.packageCount %></dd></div> <div><dt>Package count</dt><dd><%= application.packageCount %></dd></div>
<div><dt>Created date</dt><dd><%= application.createdAt %></dd></div> <div><dt>Created date</dt><dd><%= application.createdAt %></dd></div>
<div><dt>Created by</dt><dd><%= application.createdBy %></dd></div> <div><dt>Created by</dt><dd><%= application.createdBy %></dd></div>
<div><dt>Open URL</dt><dd class="mono"><%= application.openUrl || '-' %></dd></div>
<div><dt>Status</dt><dd><span class="badge <%= helpers.statusClass(application.status) %>"><%= application.status %></span></dd></div> <div><dt>Status</dt><dd><span class="badge <%= helpers.statusClass(application.status) %>"><%= application.status %></span></dd></div>
</dl> </dl>
</section> </section>
@@ -95,6 +97,7 @@
data-app-name="<%= application.name %>" data-app-name="<%= application.name %>"
data-app-version="<%= application.version %>" data-app-version="<%= application.version %>"
data-app-status="<%= application.status %>" data-app-status="<%= application.status %>"
data-app-open-url="<%= application.openUrl %>"
data-app-notes="<%= application.notes %>" data-app-notes="<%= application.notes %>"
data-app-packages="<%= JSON.stringify(application.packages.map((item) => ({ packageId: item.packageId, selectedVersionId: item.selectedVersionId }))) %>" data-app-packages="<%= JSON.stringify(application.packages.map((item) => ({ packageId: item.packageId, selectedVersionId: item.selectedVersionId }))) %>"
> >

View File

@@ -82,6 +82,7 @@
data-app-name="<%= item.name %>" data-app-name="<%= item.name %>"
data-app-version="<%= item.version %>" data-app-version="<%= item.version %>"
data-app-status="<%= item.status %>" data-app-status="<%= item.status %>"
data-app-open-url="<%= item.openUrl %>"
data-app-notes="<%= item.notes %>" data-app-notes="<%= item.notes %>"
data-app-packages="<%= JSON.stringify(item.packages.map((pkg) => ({ packageId: pkg.packageId, selectedVersionId: pkg.selectedVersionId }))) %>" data-app-packages="<%= JSON.stringify(item.packages.map((pkg) => ({ packageId: pkg.packageId, selectedVersionId: pkg.selectedVersionId }))) %>"
> >

View File

@@ -38,6 +38,10 @@
<span>App name</span> <span>App name</span>
<input type="text" name="appName" required> <input type="text" name="appName" required>
</label> </label>
<label class="form-field full">
<span>Open URL</span>
<input type="text" name="openUrl" placeholder="http://127.0.0.1:5000">
</label>
<label class="form-field full"> <label class="form-field full">
<span>Notes</span> <span>Notes</span>
<textarea name="notes"></textarea> <textarea name="notes"></textarea>

View File

@@ -29,6 +29,10 @@
<span>App name</span> <span>App name</span>
<input type="text" name="appName" required data-edit-app-field="appName"> <input type="text" name="appName" required data-edit-app-field="appName">
</label> </label>
<label class="form-field full">
<span>Open URL</span>
<input type="text" name="openUrl" placeholder="http://127.0.0.1:5000" data-edit-app-field="openUrl">
</label>
<label class="form-field full"> <label class="form-field full">
<span>Notes</span> <span>Notes</span>
<textarea name="notes" data-edit-app-field="notes"></textarea> <textarea name="notes" data-edit-app-field="notes"></textarea>