Files
idea.llm.gitea.repo.docker.…/app/renderer-legacy/app.js
2026-02-27 08:55:41 -06:00

923 lines
29 KiB
JavaScript

// State
let state = {
servers: [],
selectedServerId: null,
localProjects: [],
deployedProjects: [],
runningContainers: [],
currentDiffProject: null,
currentDetailsProject: null,
currentDeployProject: null,
currentDeployDiff: null,
currentLogsProject: null
};
// DOM Elements
const serverSelect = document.getElementById('server-select');
const btnScanServer = document.getElementById('btn-scan-server');
const serverStatus = document.getElementById('server-status');
const projectsBody = document.getElementById('projects-body');
const btnRefresh = document.getElementById('btn-refresh');
const btnSettings = document.getElementById('btn-settings');
// Modals
const serverModal = document.getElementById('server-modal');
const diffModal = document.getElementById('diff-modal');
const logModal = document.getElementById('log-modal');
const detailsModal = document.getElementById('details-modal');
const deployConfirmModal = document.getElementById('deploy-confirm-modal');
const logsModal = document.getElementById('logs-modal');
// Initialize
async function init() {
await loadServers();
await loadLocalProjects();
setupEventListeners();
}
function setupEventListeners() {
btnRefresh.addEventListener('click', () => loadLocalProjects());
btnSettings.addEventListener('click', () => showServerModal());
btnScanServer.addEventListener('click', () => scanServer());
serverSelect.addEventListener('change', (e) => {
state.selectedServerId = e.target.value || null;
btnScanServer.disabled = !state.selectedServerId;
state.deployedProjects = [];
state.runningContainers = [];
renderProjects();
});
// Modal close buttons
document.getElementById('close-server-modal').addEventListener('click', () => hideModal(serverModal));
document.getElementById('close-diff-modal').addEventListener('click', () => hideModal(diffModal));
document.getElementById('close-log-modal').addEventListener('click', () => hideModal(logModal));
document.getElementById('close-details-modal').addEventListener('click', () => hideModal(detailsModal));
document.getElementById('close-deploy-confirm').addEventListener('click', () => hideModal(deployConfirmModal));
document.getElementById('close-logs-modal').addEventListener('click', () => hideModal(logsModal));
// Server form
document.getElementById('btn-save-server').addEventListener('click', saveServer);
// Details modal - init project button
document.getElementById('btn-init-project').addEventListener('click', initCurrentProject);
// Deploy confirmation modal buttons
document.getElementById('btn-deploy-continue').addEventListener('click', executeDeployment);
document.getElementById('btn-deploy-abort').addEventListener('click', () => hideModal(deployConfirmModal));
document.getElementById('btn-deploy-vscode').addEventListener('click', openVSCodeDiff);
// Logs modal - refresh button
document.getElementById('btn-refresh-logs').addEventListener('click', refreshLogs);
}
// Load servers
async function loadServers() {
state.servers = await window.api.getServers();
renderServerSelect();
}
function renderServerSelect() {
serverSelect.innerHTML = '<option value="">-- Select Server --</option>';
for (const server of state.servers) {
const option = document.createElement('option');
option.value = server.id;
option.textContent = `${server.name} (${server.host})`;
serverSelect.appendChild(option);
}
}
// Load local projects
async function loadLocalProjects() {
projectsBody.innerHTML = '<tr><td colspan="6" class="loading">Scanning local projects...</td></tr>';
try {
state.localProjects = await window.api.scanLocalProjects();
renderProjects();
} catch (err) {
projectsBody.innerHTML = `<tr><td colspan="6" class="loading">Error: ${err.message}</td></tr>`;
}
}
// 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 = '<tr><td colspan="6" class="loading">No projects found with Dockerfiles</td></tr>';
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 = `
<td>
<span class="project-name">${name}</span>
${project.path ? `<span class="project-path">${project.path}</span>` : ''}
</td>
<td class="status-cell">
${renderLocalStatus(project)}
</td>
<td class="status-cell">
${renderDeployedStatus(project)}
</td>
<td class="status-cell">
${renderRunningStatus(project)}
</td>
<td>
${renderDiffStatus(project)}
</td>
<td class="actions-cell">
${renderActions(project)}
</td>
`;
projectsBody.appendChild(row);
}
}
function renderLocalStatus(project) {
if (!project.path) {
return '<span class="indicator gray"></span>Not local';
}
// Count missing files
const requiredFiles = getRequiredFiles(project);
const missingCount = requiredFiles.filter(f => !f.present).length;
if (missingCount === 0) {
return `<span class="issues-badge ok" onclick="showDetails('${project.name}')">Ready</span>`;
} else if (missingCount <= 2) {
return `<span class="issues-badge warning" onclick="showDetails('${project.name}')">${missingCount} missing</span>`;
} else {
return `<span class="issues-badge error" onclick="showDetails('${project.name}')">${missingCount} missing</span>`;
}
}
function renderDeployedStatus(project) {
if (!state.selectedServerId) {
return '<span class="indicator gray"></span>Select server';
}
if (project.deployed) {
return '<span class="indicator green"></span>Deployed';
} else {
return '<span class="indicator gray"></span>Not deployed';
}
}
function renderRunningStatus(project) {
if (!state.selectedServerId) {
return '-';
}
if (project.running) {
return `<span class="indicator green"></span>${project.running.status.split(' ')[0]}`;
} else if (project.deployed) {
return '<span class="indicator red"></span>Stopped';
} else {
return '-';
}
}
function renderDiffStatus(project) {
if (!project.deployed || !project.path) {
return '<span class="diff-badge unknown">N/A</span>';
}
// If we have deployed content, we can compare
if (project.deployed.dockerComposeContent && project.hasDockerCompose) {
return `<button class="btn btn-small btn-secondary" onclick="showDiff('${project.name}')">Compare</button>`;
}
return '<span class="diff-badge unknown">?</span>';
}
function renderActions(project) {
const actions = [];
if (project.path && project.hasDockerfile) {
actions.push(`<button class="btn btn-small btn-secondary" onclick="buildTar('${project.path}')">Build</button>`);
}
if (project.path && state.selectedServerId) {
actions.push(`<button class="btn btn-small btn-primary" onclick="deployProject('${project.name}')">Deploy</button>`);
}
// Add logs button for running containers
if (project.running && state.selectedServerId) {
actions.push(`<button class="btn btn-small btn-secondary" onclick="viewLogs('${project.name}')">Logs</button>`);
}
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 = `
<strong>Differences found!</strong> ${filesWithDiff.length} file(s) differ from deployed versions.
<br>Continuing will overwrite the remote files.
${filesWithDiff.some(f => f.status === 'local-missing') ?
'<br><span style="color: #f39c12;">Warning: Some files exist on server but not locally.</span>' : ''}
`;
} else {
summaryEl.className = 'deploy-diff-summary no-diff';
summaryEl.innerHTML = `<strong>No differences found.</strong> 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 += `
<div class="deploy-file-diff">
<h4>
${file.name} ${statusBadge}
<button class="btn btn-small btn-secondary" onclick="pullSingleFile(${fileIndex})" title="Pull from server">Pull</button>
</h4>
<div class="diff-content">
<div class="diff-side">
<div class="diff-side-header">Local</div>
<pre>${escapeHtml(content.local || '(empty)')}</pre>
</div>
<div class="diff-side">
<div class="diff-side-header">Remote</div>
<pre>${escapeHtml(content.remote || '(empty)')}</pre>
</div>
</div>
</div>
`;
} else if (file.status === 'local-missing' && file.remoteContent) {
// File exists on server but not locally
detailsEl.innerHTML += `
<div class="deploy-file-diff">
<h4>
${file.name} ${statusBadge}
<button class="btn btn-small btn-secondary" onclick="pullSingleFile(${fileIndex})" title="Pull from server">Pull</button>
</h4>
<div class="diff-content">
<div class="diff-side">
<div class="diff-side-header">Local</div>
<pre>(not found)</pre>
</div>
<div class="diff-side">
<div class="diff-side-header">Remote</div>
<pre>${escapeHtml(file.sensitive ? maskEnvContent(file.remoteContent) : file.remoteContent)}</pre>
</div>
</div>
</div>
`;
} else if (file.type === 'directory' && file.status !== 'neither') {
// Directory status
detailsEl.innerHTML += `
<div class="deploy-file-diff" style="padding: 12px;">
<h4 style="margin: 0;">
${file.name} ${statusBadge}
${file.status === 'local-missing' || file.status === 'both-exist' ?
`<button class="btn btn-small btn-secondary" onclick="pullSingleFile(${fileIndex})" title="Pull from server">Pull</button>` : ''}
</h4>
</div>
`;
}
}
// Add "Pull All Different" button if there are pullable files
const pullableFiles = files.filter(f =>
(f.status === 'different' || f.status === 'local-missing') && f.remoteContent
);
if (pullableFiles.length > 0) {
detailsEl.innerHTML += `
<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #0f3460;">
<button class="btn btn-secondary" onclick="pullAllDifferent()">
Pull All Different Files (${pullableFiles.length})
</button>
</div>
`;
}
showModal(deployConfirmModal);
}
function getStatusBadge(status) {
switch (status) {
case 'match':
return '<span class="diff-badge match">Match</span>';
case 'different':
return '<span class="diff-badge different">Different</span>';
case 'remote-missing':
return '<span class="diff-badge missing">Not on server</span>';
case 'local-missing':
return '<span class="diff-badge warning" style="background: #f39c12; color: #000;">Only on server</span>';
case 'both-exist':
return '<span class="diff-badge match">Both exist</span>';
case 'neither':
return '<span class="diff-badge unknown">Neither</span>';
default:
return '<span class="diff-badge unknown">Unknown</span>';
}
}
// Pull a single file from server
window.pullSingleFile = async function(fileIndex) {
const diff = state.currentDeployDiff;
const project = state.currentDeployProject;
if (!diff || !project || !diff.files[fileIndex]) return;
const file = diff.files[fileIndex];
showLog(`Pulling ${file.name} from server...`);
const result = await window.api.pullFile({
serverId: state.selectedServerId,
remotePath: file.remotePath,
localPath: file.localPath,
isDirectory: file.type === 'directory'
});
if (result.error) {
appendLog(`\nError: ${result.error}`);
} else {
appendLog(`\nPulled ${file.name} successfully!`);
// Refresh comparison
hideModal(logModal);
await deployProject(project.name);
}
};
// Pull all different files from server
window.pullAllDifferent = async function() {
const diff = state.currentDeployDiff;
const project = state.currentDeployProject;
if (!diff || !project) return;
const filesToPull = (diff.files || []).filter(f =>
(f.status === 'different' || f.status === 'local-missing') &&
(f.remoteContent || f.type === 'directory')
);
if (filesToPull.length === 0) {
alert('No files to pull');
return;
}
showLog(`Pulling ${filesToPull.length} files from server...\n`);
const result = await window.api.pullFiles({
serverId: state.selectedServerId,
files: filesToPull
});
if (result.error) {
appendLog(`\nError: ${result.error}`);
} else {
if (result.pulled && result.pulled.length > 0) {
appendLog(`\nPulled: ${result.pulled.join(', ')}`);
}
if (result.errors && result.errors.length > 0) {
appendLog(`\nErrors:`);
for (const err of result.errors) {
appendLog(`\n ${err.name}: ${err.error}`);
}
}
appendLog('\n\nDone! Refreshing...');
// Refresh
hideModal(logModal);
hideModal(deployConfirmModal);
await loadLocalProjects();
}
};
function maskEnvContent(content) {
if (!content) return null;
// Mask values that look like passwords/secrets
return content.replace(/^([A-Z_]+PASSWORD|[A-Z_]+SECRET|[A-Z_]+KEY|[A-Z_]+TOKEN)=(.+)$/gm, '$1=****');
}
function escapeHtml(text) {
if (!text) return '';
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
async function executeDeployment() {
const project = state.currentDeployProject;
if (!project) return;
hideModal(deployConfirmModal);
showLog(`Deploying ${project.name}...\n`);
const result = await window.api.deployProject({
projectPath: project.path,
serverId: state.selectedServerId,
remotePath: project.remotePath || `~/containers/${project.name}`
});
if (result.error) {
appendLog(`\nError: ${result.error}`);
} else {
// Show uploaded files
if (result.uploadedFiles && result.uploadedFiles.length > 0) {
appendLog(`\nUploaded files:\n - ${result.uploadedFiles.join('\n - ')}`);
}
appendLog(`\n\n${result.message || 'Deployment complete!'}`);
if (result.status) {
appendLog(`\n\nContainer status:\n${result.status}`);
}
if (result.healthy) {
appendLog('\n\nHealth check: PASSED');
} else {
appendLog('\n\nHealth check: PENDING (container may still be starting)');
}
// Refresh server state
await scanServer();
}
state.currentDeployProject = null;
state.currentDeployDiff = null;
}
async function openVSCodeDiff() {
const project = state.currentDeployProject;
const diff = state.currentDeployDiff;
if (!project || !diff) return;
// Open docker-compose.yml diff in VS Code
if (diff.dockerCompose.status === 'different') {
const result = await window.api.openVSCodeDiff({
localPath: `${project.path}/docker-compose.yml`,
serverId: state.selectedServerId,
remoteFilePath: `${project.remotePath || '~/containers/' + project.name}/docker-compose.yml`
});
if (result.error) {
alert(`Error opening VS Code: ${result.error}`);
}
}
}
async function showDiff(projectName) {
const project = state.localProjects.find(p => p.name === projectName);
if (!project) return;
state.currentDiffProject = project;
document.getElementById('diff-project-name').textContent = projectName;
const result = await window.api.compareProject({
projectPath: project.path,
serverId: state.selectedServerId,
remotePath: project.remotePath || `~/containers/${projectName}`
});
if (result.error) {
alert(`Error comparing: ${result.error}`);
return;
}
const diff = result.diff;
// docker-compose status
const statusEl = document.getElementById('diff-compose-status');
if (diff.dockerCompose.status === 'match') {
statusEl.innerHTML = '<span class="diff-badge match">Files match</span>';
} else if (diff.dockerCompose.status === 'different') {
statusEl.innerHTML = '<span class="diff-badge different">Files differ</span>';
} else if (diff.dockerCompose.status === 'remote-missing') {
statusEl.innerHTML = '<span class="diff-badge missing">Not deployed</span>';
} else if (diff.dockerCompose.status === 'local-missing') {
statusEl.innerHTML = '<span class="diff-badge missing">No local file</span>';
} else {
statusEl.innerHTML = '<span class="diff-badge unknown">Unknown</span>';
}
document.getElementById('diff-compose-local').textContent = diff.dockerCompose.localContent || '(not found)';
document.getElementById('diff-compose-remote').textContent = diff.dockerCompose.remoteContent || '(not found)';
// Wire up pull/push buttons
document.getElementById('btn-pull-compose').onclick = () => pullCompose(project);
document.getElementById('btn-push-compose').onclick = () => pushCompose(project);
showModal(diffModal);
}
async function pullCompose(project) {
const result = await window.api.pullFile({
serverId: state.selectedServerId,
remotePath: `${project.remotePath || '~/containers/' + project.name}/docker-compose.yml`,
localPath: `${project.path}/docker-compose.yml`
});
if (result.error) {
alert(`Error: ${result.error}`);
} else {
alert('Pulled docker-compose.yml from server');
hideModal(diffModal);
loadLocalProjects();
}
}
async function pushCompose(project) {
// Deploy just the compose file
await deployProject(project.name);
hideModal(diffModal);
}
// Server management
function showServerModal() {
renderServerList();
clearServerForm();
showModal(serverModal);
}
function renderServerList() {
const listEl = document.getElementById('server-list');
listEl.innerHTML = '';
for (const server of state.servers) {
const item = document.createElement('div');
item.className = 'server-item';
item.innerHTML = `
<div class="server-item-info">
<span class="server-item-name">${server.name}</span>
<span class="server-item-host">${server.host}</span>
${server.useSudo ? '<div class="server-item-badges"><span class="server-sudo-badge">sudo</span></div>' : ''}
</div>
<div>
<button class="btn btn-small btn-secondary" onclick="editServer('${server.id}')">Edit</button>
<button class="btn btn-small btn-secondary" onclick="deleteServer('${server.id}')">Delete</button>
</div>
`;
listEl.appendChild(item);
}
}
function clearServerForm() {
document.getElementById('server-id').value = '';
document.getElementById('server-name').value = '';
document.getElementById('server-host').value = '';
document.getElementById('server-username').value = '';
document.getElementById('server-use-sudo').checked = false;
}
window.editServer = function(serverId) {
const server = state.servers.find(s => s.id === serverId);
if (!server) return;
document.getElementById('server-id').value = server.id;
document.getElementById('server-name').value = server.name;
document.getElementById('server-host').value = server.host;
document.getElementById('server-username').value = server.username || '';
document.getElementById('server-use-sudo').checked = server.useSudo || false;
};
window.deleteServer = async function(serverId) {
if (!confirm('Delete this server?')) return;
await window.api.deleteServer(serverId);
await loadServers();
renderServerList();
};
async function saveServer() {
const server = {
id: document.getElementById('server-id').value || undefined,
name: document.getElementById('server-name').value,
host: document.getElementById('server-host').value,
username: document.getElementById('server-username').value,
useSudo: document.getElementById('server-use-sudo').checked
};
if (!server.name || !server.host) {
alert('Name and host are required');
return;
}
await window.api.saveServer(server);
await loadServers();
renderServerList();
clearServerForm();
}
// Log modal
function showLog(text) {
document.getElementById('log-content').textContent = text;
showModal(logModal);
}
function appendLog(text) {
document.getElementById('log-content').textContent += text;
}
// Modal helpers
function showModal(modal) {
modal.classList.remove('hidden');
}
function hideModal(modal) {
modal.classList.add('hidden');
}
// Required files checker
function getRequiredFiles(project) {
return [
{
name: 'Dockerfile',
description: 'Container build instructions',
present: project.hasDockerfile,
fix: 'Create manually or use: npm run docker-deploy -- init'
},
{
name: 'docker-compose.yml',
description: 'Runtime configuration (ports, env, volumes)',
present: project.hasDockerCompose,
fix: 'Create manually or use: npm run docker-deploy -- init'
},
{
name: 'build-image-tar.ps1',
description: 'Script to build Docker image and create tar',
present: project.hasBuildScript,
fix: 'Use: npm run docker-deploy -- init'
},
{
name: '.dockerignore',
description: 'Files to exclude from Docker image',
present: project.hasDockerIgnore,
fix: 'Create manually or use: npm run docker-deploy -- init'
}
];
}
// Show project details modal
function showDetails(projectName) {
const project = state.localProjects.find(p => p.name === projectName);
if (!project) return;
state.currentDetailsProject = project;
document.getElementById('details-project-name').textContent = projectName;
// Render file checklist
const filesListEl = document.getElementById('details-files-list');
const requiredFiles = getRequiredFiles(project);
filesListEl.innerHTML = requiredFiles.map(file => `
<div class="file-item">
<div class="file-item-icon ${file.present ? 'present' : 'missing'}">
${file.present ? '✓' : '✗'}
</div>
<div class="file-item-info">
<div class="file-item-name">${file.name}</div>
<div class="file-item-desc">${file.description}</div>
</div>
</div>
`).join('');
// Render fix instructions
const fixEl = document.getElementById('details-fix-instructions');
const missingFiles = requiredFiles.filter(f => !f.present);
if (missingFiles.length === 0) {
fixEl.innerHTML = `
<div class="fix-step">
<div class="fix-step-title">All files present!</div>
<p>This project is ready for deployment.</p>
</div>
`;
document.getElementById('btn-init-project').style.display = 'none';
} else {
fixEl.innerHTML = `
<div class="fix-step">
<div class="fix-step-title">Option 1: Use the CLI tool</div>
<p>Run this command to generate missing files:</p>
<div class="fix-step-cmd">cd "${project.path}"<br>npm run docker-deploy -- init .</div>
<div class="fix-step-note">This runs from the docker-deployment-manager repo</div>
</div>
<div class="fix-step">
<div class="fix-step-title">Option 2: Create manually</div>
<p>Missing files:</p>
${missingFiles.map(f => `
<div class="fix-step-cmd">${f.name} - ${f.description}</div>
`).join('')}
</div>
`;
document.getElementById('btn-init-project').style.display = 'block';
}
showModal(detailsModal);
}
// Initialize project via CLI
async function initCurrentProject() {
const project = state.currentDetailsProject;
if (!project) return;
hideModal(detailsModal);
showLog(`Initializing Docker config for ${project.name}...\n\nRunning: npm run docker-deploy -- init "${project.path}" --no-interactive\n\n`);
const result = await window.api.initProject(project.path);
if (result.error) {
appendLog(`\nError: ${result.error}`);
} else {
appendLog(result.output);
appendLog('\n\nInit complete! Refreshing project list...');
await loadLocalProjects();
}
}
// Container logs viewer
async function viewLogs(projectName) {
const project = state.localProjects.find(p => p.name === projectName);
if (!project || !state.selectedServerId) return;
state.currentLogsProject = project;
document.getElementById('logs-container-name').textContent = projectName;
document.getElementById('logs-content').textContent = 'Loading logs...';
showModal(logsModal);
await refreshLogs();
}
async function refreshLogs() {
const project = state.currentLogsProject;
if (!project) return;
const result = await window.api.getContainerLogs({
serverId: state.selectedServerId,
remotePath: project.remotePath || `~/containers/${project.name}`,
lines: 100
});
if (result.error) {
document.getElementById('logs-content').textContent = `Error: ${result.error}`;
} else {
document.getElementById('logs-content').textContent = result.logs || '(no logs)';
}
}
// Global function bindings for inline onclick
window.buildTar = buildTar;
window.deployProject = deployProject;
window.showDiff = showDiff;
window.showDetails = showDetails;
window.viewLogs = viewLogs;
// Start app
init();