GitHub(nextgroup2706/ken_nogi)は今後使わず自社Gitea運用に切替え。 NodeSrvは旧リポジトリの履歴を破棄しファイルのみ統合(Dokploy用サービスアカウントは 別途mygit-admin/NodeSrv.gitに履歴あり)。notepmエクスポート(12GB)とPleasanter インストーラzip(208MB)はサイズが大きいため.gitignoreで除外。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
85 lines
2.9 KiB
JavaScript
85 lines
2.9 KiB
JavaScript
const { listComposes, getComposeEnv, getContainers } = require('./dokployClient');
|
|
const { extractPortalMeta } = require('./portalMeta');
|
|
|
|
function escapeHtml(value) {
|
|
return String(value)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"');
|
|
}
|
|
|
|
async function buildCards(environmentId) {
|
|
const composes = await listComposes(environmentId);
|
|
const settled = await Promise.allSettled(
|
|
composes.map(async (c) => {
|
|
const { env, appName } = await getComposeEnv(c.composeId);
|
|
const meta = extractPortalMeta(env, c.name);
|
|
if (!meta) return null;
|
|
let containerState = 'unknown';
|
|
try {
|
|
const containers = await getContainers(appName);
|
|
containerState = containers[0]?.state || 'stopped';
|
|
} catch {
|
|
containerState = 'unknown';
|
|
}
|
|
return { composeId: c.composeId, appName, composeStatus: c.composeStatus, containerState, meta };
|
|
})
|
|
);
|
|
const cards = [];
|
|
settled.forEach((result, i) => {
|
|
if (result.status === 'fulfilled') {
|
|
if (result.value) cards.push(result.value);
|
|
} else {
|
|
cards.push({ error: true, composeId: composes[i].composeId, name: composes[i].name });
|
|
}
|
|
});
|
|
return cards;
|
|
}
|
|
|
|
function renderCard(card) {
|
|
if (card.error) {
|
|
return `<div class="card card-error" data-compose-id="${escapeHtml(card.composeId)}">
|
|
<h3>${escapeHtml(card.name || card.composeId)}</h3>
|
|
<p>状態取得失敗</p>
|
|
</div>`;
|
|
}
|
|
const { composeId, composeStatus, containerState, meta } = card;
|
|
const openLink =
|
|
meta.appType === 'web' && meta.url
|
|
? `<a href="${escapeHtml(meta.url)}" target="_blank" rel="noopener">開く</a>`
|
|
: '';
|
|
const actionButtons =
|
|
meta.appType === 'web'
|
|
? `<button data-action="start" data-compose-id="${escapeHtml(composeId)}">Start</button>
|
|
<button data-action="stop" data-compose-id="${escapeHtml(composeId)}">Stop</button>
|
|
<button data-action="restart" data-compose-id="${escapeHtml(composeId)}">Restart</button>`
|
|
: `<button data-action="trigger" data-compose-id="${escapeHtml(composeId)}">今すぐ実行</button>`;
|
|
return `<div class="card" data-compose-id="${escapeHtml(composeId)}">
|
|
<h3>${escapeHtml(meta.label)}</h3>
|
|
<p>compose: ${escapeHtml(composeStatus)} / container: ${escapeHtml(containerState)}</p>
|
|
${openLink}
|
|
${actionButtons}
|
|
<button data-action="logs" data-compose-id="${escapeHtml(composeId)}">ログ表示</button>
|
|
</div>`;
|
|
}
|
|
|
|
async function renderDashboard(environmentId) {
|
|
const cards = await buildCards(environmentId);
|
|
const cardsHtml = cards.map(renderCard).join('\n');
|
|
return `<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>App Portal</title>
|
|
</head>
|
|
<body>
|
|
<h1>App Portal</h1>
|
|
<div id="cards">${cardsHtml}</div>
|
|
<script src="/portal.js"></script>
|
|
</body>
|
|
</html>`;
|
|
}
|
|
|
|
module.exports = { buildCards, renderCard, renderDashboard };
|