Skip to content
Merged
Next Next commit
Add deployment preflight diagnostics
  • Loading branch information
chenyn6185 committed Apr 29, 2026
commit 5db8d88d4f8c1f06a3b9cdc2b905426be7441aeb
30 changes: 30 additions & 0 deletions assets/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -550,9 +550,39 @@ async function loadDeploymentInfo() {
<span class="meta-separator">/</span>
${escapeHtml(dockerText)}
`;
renderDeploymentDiagnostics(deploymentInfo.diagnostics);
updateAppLayoutLabels();
}

function renderDeploymentDiagnostics(diagnostics) {
const el = document.getElementById('deployment-diagnostics');
if (!el) return;
const checks = Array.isArray(diagnostics?.checks) ? diagnostics.checks : [];
if (checks.length === 0) {
el.textContent = '环境检查暂不可用';
return;
}
const statusLabel = diagnostics.status === 'ok'
? '检查通过'
: diagnostics.status === 'warning'
? '存在提醒'
: '需要处理';
el.innerHTML = `
<div class="diagnostics-label">
<span class="diagnostics-dot ${escapeHtml(diagnostics.status || 'unknown')}"></span>
环境检查 · ${escapeHtml(statusLabel)}
</div>
<div class="diagnostics-list">
${checks.map(check => `
<span class="diagnostic-chip ${escapeHtml(check.state || 'unknown')}" title="${escapeHtml(check.detail || '')}">
<span class="diagnostic-chip-dot"></span>
${escapeHtml(check.label || check.id || '')}
</span>
`).join('')}
</div>
`;
}

function getAppDisplayName() {
const appType = (deploymentInfo?.appType || configValues?.APPTYPE || 'CLOUD').toUpperCase();
return appType === 'EDGE' ? 'IoT Edge' : 'IoT Cloud';
Expand Down
90 changes: 90 additions & 0 deletions assets/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -5382,6 +5382,96 @@ body {
margin: 0 2px;
}

.deployment-diagnostics {
margin-top: 10px;
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
color: #64748B;
font-size: 12px;
line-height: 1.35;
}

.diagnostics-label {
display: inline-flex;
align-items: center;
gap: 7px;
color: #334155;
font-weight: 850;
white-space: nowrap;
}

.diagnostics-dot,
.diagnostic-chip-dot {
width: 8px;
height: 8px;
border-radius: 999px;
flex-shrink: 0;
}

.diagnostics-dot.ok,
.diagnostic-chip.ok .diagnostic-chip-dot {
background: #10B981;
box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.12);
}

.diagnostics-dot.warning,
.diagnostic-chip.warning .diagnostic-chip-dot {
background: #F59E0B;
box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.12);
}

.diagnostics-dot.error,
.diagnostics-dot.unknown,
.diagnostic-chip.error .diagnostic-chip-dot,
.diagnostic-chip.unknown .diagnostic-chip-dot {
background: #EF4444;
box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.12);
}

.diagnostics-list {
min-width: 0;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 7px;
}

.diagnostic-chip {
min-height: 24px;
padding: 0 9px;
display: inline-flex;
align-items: center;
gap: 7px;
border-radius: 999px;
border: 1px solid #D9E2EC;
background: #FFFFFF;
color: #475569;
font-size: 11px;
font-weight: 800;
white-space: nowrap;
}

.diagnostic-chip.ok {
border-color: #A7F3D0;
background: #F0FDF4;
color: #047857;
}

.diagnostic-chip.warning {
border-color: #FDE68A;
background: #FFFBEB;
color: #B45309;
}

.diagnostic-chip.error,
.diagnostic-chip.unknown {
border-color: #FECACA;
background: #FEF2F2;
color: #B91C1C;
}

.overview-row {
display: grid;
grid-template-columns: 76px minmax(0, 1fr);
Expand Down
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
<div class="section-kicker">Deployment</div>
<div class="deployment-title">部署控制台</div>
<div id="deployment-meta" class="section-subtitle deployment-meta">正在识别部署环境...</div>
<div id="deployment-diagnostics" class="deployment-diagnostics">正在检查运行环境...</div>
<div class="deployment-overview">
<div id="plan-summary" class="plan-summary">正在分析配置依赖...</div>
</div>
Expand Down
82 changes: 81 additions & 1 deletion tb-config-src.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,85 @@ const logStreamService = createLogStreamService({
});
let serviceComposeConfigBuilder = null;

function buildDiagnosticItem(id, label, ok, detail, severity = 'error') {
return {
id,
label,
state: ok ? 'ok' : severity,
detail
};
}

function buildDeploymentDiagnostics() {
const dockerMessage = dockerRuntime.readyMessage();
const socketMounted = fs.existsSync('/var/run/docker.sock') || os.platform() === 'win32';
const serviceDefs = listServiceDefinitions();
const existingServices = serviceDefs.filter(service => service.exists);
const appDef = getServiceDefinition(getPackageServiceId());
const checks = [
buildDiagnosticItem(
'app-root',
'安装包目录',
fs.existsSync(APP_ROOT),
fs.existsSync(APP_ROOT) ? APP_ROOT : `目录不存在:${APP_ROOT}`
),
buildDiagnosticItem(
'app-env',
'业务配置',
fs.existsSync(ENV_FILE_PATH),
fs.existsSync(ENV_FILE_PATH) ? ENV_FILE_PATH : `未找到 .env:${ENV_FILE_PATH}`
),
buildDiagnosticItem(
'yaml-config',
'YAML 模板',
!!YAML_CONFIG_PATH && fs.existsSync(YAML_CONFIG_PATH),
YAML_CONFIG_PATH && fs.existsSync(YAML_CONFIG_PATH) ? YAML_CONFIG_PATH : '未找到 YAML 模板,首次补全配置可能不完整。',
'warning'
),
buildDiagnosticItem(
'docker-socket',
'Docker Socket',
socketMounted,
socketMounted ? '/var/run/docker.sock 已挂载' : '未挂载 /var/run/docker.sock,无法控制宿主机 Docker。'
),
buildDiagnosticItem(
'docker-compose',
'Docker Compose',
!dockerMessage,
dockerMessage || `${dockerRuntime.dockerComposeCmd || 'docker compose'} 可用`
),
buildDiagnosticItem(
'app-compose',
'业务 Compose',
!!appDef?.exists,
appDef?.exists ? appDef.composePath : `未找到 ${appDef?.composePath || '业务 compose'}`
),
buildDiagnosticItem(
'service-compose',
'服务 Compose',
existingServices.length > 0,
`${existingServices.length}/${serviceDefs.length} 个服务 compose 可用`,
'warning'
),
buildDiagnosticItem(
'auth',
'登录保护',
AUTH_REQUIRED,
AUTH_REQUIRED ? '已启用管理口令' : '未配置 CONFIG_MATE_PASSWORD,高权限控制台未受保护。',
'warning'
)
];
const counts = checks.reduce((acc, check) => {
acc[check.state] = (acc[check.state] || 0) + 1;
return acc;
}, { ok: 0, warning: 0, error: 0 });
return {
status: counts.error > 0 ? 'error' : (counts.warning > 0 ? 'warning' : 'ok'),
counts,
checks
};
}

// --- Helper: Check Status ---
function getRunningPid(pidPath = PID_FILE) {
if (!fs.existsSync(pidPath)) return null;
Expand Down Expand Up @@ -1030,7 +1109,8 @@ function startServer() {
socketMounted: fs.existsSync('/var/run/docker.sock') || os.platform() === 'win32',
available: !dockerRuntime.readyMessage(),
message: dockerRuntime.readyMessage()
}
},
diagnostics: buildDeploymentDiagnostics()
}, headers);
return;
}
Expand Down
Loading