`;
}
}
// Scan remote server
async function scanServer() {
if (!state.selectedServerId) return;
serverStatus.textContent = 'Connecting...';
serverStatus.className = 'status-badge';
try {
// Get deployed projects
const deployedResult = await window.api.scanServer(state.selectedServerId);
if (deployedResult.error) {
throw new Error(deployedResult.error);
}
state.deployedProjects = deployedResult.deployed || [];
// Get running containers
const containersResult = await window.api.getRunningContainers(state.selectedServerId);
if (containersResult.success) {
state.runningContainers = containersResult.containers || [];
}
serverStatus.textContent = `Connected (${state.deployedProjects.length} deployed)`;
serverStatus.className = 'status-badge connected';
renderProjects();
} catch (err) {
serverStatus.textContent = `Error: ${err.message}`;
serverStatus.className = 'status-badge error';
}
}
// Render projects table
function renderProjects() {
if (state.localProjects.length === 0) {
projectsBody.innerHTML = '
No projects found with Dockerfiles
';
return;
}
// Merge local and deployed data
const projectMap = new Map();
// Add local projects
for (const project of state.localProjects) {
projectMap.set(project.name, {
...project,
deployed: null,
running: null
});
}
// Match with deployed projects
for (const deployed of state.deployedProjects) {
if (projectMap.has(deployed.name)) {
projectMap.get(deployed.name).deployed = deployed;
} else {
// Project exists on server but not locally
projectMap.set(deployed.name, {
name: deployed.name,
path: null,
hasDockerfile: false,
hasDockerCompose: false,
dockerStatus: 'remote-only',
deployed
});
}
}
// Match with running containers
for (const container of state.runningContainers) {
// Try to match container name with project name
for (const [name, project] of projectMap) {
if (container.name.includes(name) || name.includes(container.name)) {
project.running = container;
break;
}
}
}
// Render table rows
projectsBody.innerHTML = '';
for (const [name, project] of projectMap) {
const row = document.createElement('tr');
row.innerHTML = `
${name}
${project.path ? `${project.path}` : ''}
${renderLocalStatus(project)}
${renderDeployedStatus(project)}
${renderRunningStatus(project)}
${renderDiffStatus(project)}
${renderActions(project)}
`;
projectsBody.appendChild(row);
}
}
function renderLocalStatus(project) {
if (!project.path) {
return 'Not local';
}
// Count missing files
const requiredFiles = getRequiredFiles(project);
const missingCount = requiredFiles.filter(f => !f.present).length;
if (missingCount === 0) {
return `Ready`;
} else if (missingCount <= 2) {
return `${missingCount} missing`;
} else {
return `${missingCount} missing`;
}
}
function renderDeployedStatus(project) {
if (!state.selectedServerId) {
return 'Select server';
}
if (project.deployed) {
return 'Deployed';
} else {
return 'Not deployed';
}
}
function renderRunningStatus(project) {
if (!state.selectedServerId) {
return '-';
}
if (project.running) {
return `${project.running.status.split(' ')[0]}`;
} else if (project.deployed) {
return 'Stopped';
} else {
return '-';
}
}
function renderDiffStatus(project) {
if (!project.deployed || !project.path) {
return 'N/A';
}
// If we have deployed content, we can compare
if (project.deployed.dockerComposeContent && project.hasDockerCompose) {
return ``;
}
return '?';
}
function renderActions(project) {
const actions = [];
if (project.path && project.hasDockerfile) {
actions.push(``);
}
if (project.path && state.selectedServerId) {
actions.push(``);
}
// Add logs button for running containers
if (project.running && state.selectedServerId) {
actions.push(``);
}
return actions.join(' ');
}
// Actions
async function buildTar(projectPath) {
showLog('Building Docker image and tar...\n');
const result = await window.api.buildTar(projectPath);
if (result.error) {
appendLog(`\nError: ${result.error}\n${result.stderr || ''}`);
} else {
appendLog(result.output);
appendLog('\n\nBuild complete!');
}
}
async function deployProject(projectName) {
const project = state.localProjects.find(p => p.name === projectName);
if (!project || !state.selectedServerId) return;
state.currentDeployProject = project;
// Step 1: Compare files first
showLog(`Checking for differences before deploy...`);
const comparison = await window.api.compareProject({
projectPath: project.path,
serverId: state.selectedServerId,
remotePath: project.remotePath || `~/containers/${projectName}`
});
hideModal(logModal);
if (comparison.error) {
showLog(`Error comparing files: ${comparison.error}`);
return;
}
state.currentDeployDiff = comparison.diff;
// Step 2: Check if there are differences
const composeStatus = comparison.diff.dockerCompose.status;
const envStatus = comparison.diff.env.status;
const hasDiff = composeStatus === 'different' || envStatus === 'different';
const isNewDeploy = composeStatus === 'remote-missing' && envStatus !== 'different';
if (isNewDeploy) {
// New deployment - no confirmation needed
await executeDeployment();
return;
}
if (hasDiff) {
// Show confirmation modal with differences
showDeployConfirmModal(project, comparison.diff);
return;
}
// No differences - proceed directly
await executeDeployment();
}
function showDeployConfirmModal(project, diff) {
document.getElementById('deploy-confirm-project').textContent = project.name;
// Check all files for differences
const filesWithDiff = (diff.files || []).filter(f =>
f.status === 'different' || f.status === 'local-missing'
);
const hasDiff = filesWithDiff.length > 0;
// Summary
const summaryEl = document.getElementById('deploy-diff-summary');
if (hasDiff) {
summaryEl.className = 'deploy-diff-summary has-diff';
summaryEl.innerHTML = `
Differences found! ${filesWithDiff.length} file(s) differ from deployed versions.
Continuing will overwrite the remote files.
${filesWithDiff.some(f => f.status === 'local-missing') ?
' Warning: Some files exist on server but not locally.' : ''}
`;
} else {
summaryEl.className = 'deploy-diff-summary no-diff';
summaryEl.innerHTML = `No differences found. Local and remote files match.`;
}
// Details - show all files with their status
const detailsEl = document.getElementById('deploy-diff-details');
detailsEl.innerHTML = '';
// Show all compared files
const files = diff.files || [];
for (const file of files) {
const statusBadge = getStatusBadge(file.status);
const fileIndex = files.indexOf(file);
if (file.status === 'different') {
// Show diff for files that are different
const content = file.sensitive ?
{ local: maskEnvContent(file.localContent), remote: maskEnvContent(file.remoteContent) } :
{ local: file.localContent, remote: file.remoteContent };
detailsEl.innerHTML += `
${file.name} ${statusBadge}
Local
${escapeHtml(content.local || '(empty)')}
Remote
${escapeHtml(content.remote || '(empty)')}
`;
} else if (file.status === 'local-missing' && file.remoteContent) {
// File exists on server but not locally
detailsEl.innerHTML += `