let currentBusyTask = null; 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; } function setStatus(message, state = 'idle') { const status = document.getElementById('runtimeStatus'); const dot = document.getElementById('runtimeStatusDot'); if (status) { status.innerText = message; } if (dot) { dot.className = `status-dot ${state}`; } } function setTaskProgress(percent, message, running = true) { const wrap = document.getElementById('taskProgressWrap'); const bar = document.getElementById('taskProgressBar'); const label = document.getElementById('taskProgressLabel'); if (wrap) { wrap.style.display = 'block'; } if (bar) { bar.style.width = `${percent}%`; } if (label) { label.innerText = `${percent}% — ${message}`; } setStatus(message, running ? 'running' : 'done'); } function setBusy(message) { currentBusyTask = message; setStatus(message, 'running'); setTaskProgress(5, message, true); } function setDone(message) { currentBusyTask = null; setTaskProgress(100, message, false); appendLog(`✅ ${message}`); } function setError(message) { currentBusyTask = null; const label = document.getElementById('taskProgressLabel'); if (label) { label.innerText = `Ошибка — ${message}`; } setStatus(message, 'error'); appendLog(`❌ ${message}`); } 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; } function getWorkspacePath() { const input = document.getElementById('workspacePath'); return input ? input.value.trim() : ''; } async function loadSystemStatus() { setStatus('Проверяю состояние DevConsole', 'running'); const data = await workspaceApi('/api/system/status', {}, 'GET'); const status = document.getElementById('githubStatus'); const username = document.getElementById('githubUsername'); if (status && username) { if (data.github?.username) { username.value = data.github.username; } status.innerText = data.github?.token_set ? `GitHub: ${data.github.username || 'user'} / доступ сохранён` : 'GitHub доступ не сохранён'; } setStatus('DevConsole готов', 'done'); } async function saveGitHubSettings() { const username = document.getElementById('githubUsername').value.trim(); const token = document.getElementById('githubToken').value.trim(); if (!username) { setError('Укажите GitHub username'); return; } if (!token) { setError('Укажите GitHub credential'); return; } setBusy('Сохраняю GitHub доступ'); const data = await workspaceApi('/api/settings/github', { username, token }); if (data.success) { document.getElementById('githubToken').value = ''; await loadSystemStatus(); setDone('GitHub доступ сохранён'); } else { setError('Не удалось сохранить GitHub доступ'); } } function renderGroupedRuntimeButtons() { const container = document.getElementById('runtimeButtons'); if (!container) { return; } container.innerHTML = ` `; } async function loadRuntimeCommands() { renderGroupedRuntimeButtons(); setStatus('Runtime сценарии готовы', 'done'); } async function executeRuntimeCommand(command, title, progress = 50) { const workspace = getWorkspacePath(); if (!workspace) { setError('Workspace не выбран'); return false; } const taskTitle = title || command; setTaskProgress(progress, taskTitle, true); appendLog(`▶ ${taskTitle}`); 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 || command}` : `❌ Ошибка: ${data.label || command}` ); if (!data.success) { setError(`Ошибка: ${data.label || command}`); } return Boolean(data.success); } async function runRuntimeScenario(scenario) { const workspace = getWorkspacePath(); if (!workspace) { setError('Workspace не выбран'); return; } if (scenario === 'check') { setBusy('Проверка проекта'); appendLog('=== Проверка проекта: git pull + flutter pub get ==='); if (!await executeRuntimeCommand('git_pull', 'Git Pull', 25)) return; if (!await executeRuntimeCommand('flutter_pub_get', 'Flutter Pub Get', 70)) return; setDone('Проверка завершена'); return; } if (scenario === 'run_profile') { if (!getSelectedDevice()) { setError('Устройство не выбрано'); return; } setBusy('Запуск на телефоне'); appendLog('=== Запуск на телефоне в profile режиме ==='); if (!await executeRuntimeCommand('flutter_pub_get', 'Подготовка зависимостей', 30)) return; if (!await executeRuntimeCommand('flutter_run_profile', 'Flutter Run --profile', 75)) return; setDone('Запуск завершён'); return; } if (scenario === 'build') { setBusy('Сборка APK'); appendLog('=== Сборка APK: clean + pub get + release build ==='); if (!await executeRuntimeCommand('flutter_clean', 'Flutter Clean', 20)) return; if (!await executeRuntimeCommand('flutter_pub_get', 'Flutter Pub Get', 45)) return; if (!await executeRuntimeCommand('flutter_build_apk', 'Flutter Build APK', 80)) return; setDone('Сборка APK завершена'); return; } if (scenario === 'install') { setBusy('Установка APK'); appendLog('=== Установка последнего APK на устройство ==='); await installLatestApk(); return; } if (scenario === 'diagnostics') { setBusy('Диагностика Android runtime'); appendLog('=== Диагностика Android runtime ==='); if (!await executeRuntimeCommand('adb_reconnect', 'ADB Reconnect', 40)) return; if (!await executeRuntimeCommand('adb_logcat', 'ADB Logcat Snapshot', 80)) return; setDone('Диагностика завершена'); } } async function runRuntimeCommand(command) { await executeRuntimeCommand(command, command); } async function installLatestApk() { const workspace = getWorkspacePath(); setTaskProgress(40, 'Отправляю APK на устройство', true); 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); } if (data.success) { setDone('APK установлен'); } else { setError('Ошибка установки APK'); } } async function restartCurrentApp() { const workspace = getWorkspacePath(); const packageName = document.getElementById('packageName').value; if (!packageName) { setError('Укажите package name'); return; } setBusy('Перезапускаю приложение'); 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); } if (data.success) { setDone('Приложение перезапущено'); } else { setError('Ошибка перезапуска приложения'); } } async function loadProjects() { const container = document.getElementById('projectsList'); if (!container) { return; } setStatus('Загружаю список проектов', 'running'); appendLog('Загрузка списка проектов'); const data = await workspaceApi('/api/projects/list', {}, 'GET'); const projects = data.projects || []; if (projects.length === 0) { container.innerHTML = 'Проекты отсутствуют'; setStatus('Проекты отсутствуют', 'idle'); return; } container.innerHTML = projects.map(project => `