function appendLog(message) { const logs = document.getElementById('logs'); if (!logs) { return; } const time = new Date().toLocaleTimeString(); logs.innerText += `\n[${time}] ${message}`; logs.scrollTop = logs.scrollHeight; } async function workspaceApi(url, payload = {}, method = 'POST') { appendLog(`API запрос: ${url}`); const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: method === 'GET' ? undefined : JSON.stringify(payload) }); const data = await response.json().catch(() => ({success: false, detail: 'Пустой ответ сервера'})); if (!response.ok) { appendLog(`Ошибка API ${response.status}: ${data.detail || 'unknown error'}`); } return data; } function getSelectedDevice() { const select = document.getElementById('deviceSelect'); if (!select) { return null; } return select.value || null; } async function loadSystemStatus() { const data = await workspaceApi('/api/system/status', {}, 'GET'); const status = document.getElementById('githubStatus'); const username = document.getElementById('githubUsername'); if (!status || !username) { return; } if (data.github?.username) { username.value = data.github.username; } status.innerText = data.github?.token_set ? `GitHub: ${data.github.username || 'user'} / token сохранён` : 'GitHub token не сохранён'; } async function saveGitHubSettings() { const username = document.getElementById('githubUsername').value.trim(); const token = document.getElementById('githubToken').value.trim(); if (!username) { appendLog('Укажите GitHub username'); return; } if (!token) { appendLog('Укажите GitHub token'); return; } const data = await workspaceApi('/api/settings/github', { username, token }); if (data.success) { document.getElementById('githubToken').value = ''; appendLog('GitHub доступ сохранён'); await loadSystemStatus(); } } async function loadRuntimeCommands() { const container = document.getElementById('runtimeButtons'); if (!container) { return; } const data = await workspaceApi('/api/runtime/commands', {}, 'GET'); const commands = data.commands || []; container.innerHTML = commands.map(command => ` `).join(''); } async function runRuntimeCommand(command) { const workspace = document.getElementById('workspacePath').value; if (!workspace) { appendLog('Workspace не выбран'); return; } appendLog(`Запуск runtime команды: ${command}`); const data = await workspaceApi('/api/runtime/command', { workspace, command, device: getSelectedDevice() }); if (data.result?.stdout) { appendLog(data.result.stdout); } if (data.result?.stderr) { appendLog(data.result.stderr); } appendLog(data.success ? `Команда выполнена: ${data.label}` : `Ошибка runtime команды: ${data.label || command}` ); } async function installLatestApk() { const workspace = document.getElementById('workspacePath').value; const data = await workspaceApi('/api/runtime/install-latest-apk', { workspace, device: getSelectedDevice() }); if (data.result?.stdout) { appendLog(data.result.stdout); } if (data.result?.stderr) { appendLog(data.result.stderr); } } async function restartCurrentApp() { const workspace = document.getElementById('workspacePath').value; const packageName = document.getElementById('packageName').value; if (!packageName) { appendLog('Укажите package name'); return; } const data = await workspaceApi('/api/runtime/restart-app', { workspace, device: getSelectedDevice(), package_name: packageName }); if (data.result?.stdout) { appendLog(data.result.stdout); } if (data.result?.stderr) { appendLog(data.result.stderr); } } async function loadProjects() { const container = document.getElementById('projectsList'); if (!container) { return; } appendLog('Загрузка списка проектов'); const data = await workspaceApi('/api/projects/list', {}, 'GET'); const projects = data.projects || []; if (projects.length === 0) { container.innerHTML = 'Проекты отсутствуют'; return; } container.innerHTML = projects.map(project => `