From 0fa89f661389f1a6095436e8a9de5a7408013032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80?= <78488229+viktor138irk@users.noreply.github.com> Date: Tue, 12 May 2026 01:42:27 +0900 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D0=B8=D0=BB=20OTA=20=D0=BC=D0=BE=D0=B4=D0=B0=D0=BB=D0=BA=D1=83?= =?UTF-8?q?=20=D0=BA=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA?= =?UTF-8?q?=D0=B0=D0=BC=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/workspace.js | 270 +++++++++++++++--------------------------- 1 file changed, 94 insertions(+), 176 deletions(-) diff --git a/frontend/workspace.js b/frontend/workspace.js index 084ff1c..37abf1b 100644 --- a/frontend/workspace.js +++ b/frontend/workspace.js @@ -1,10 +1,10 @@ let currentBusyTask = null; let collectedErrors = []; +let selectedDeviceSerial = null; function appendLog(message) { const logs = document.getElementById('logs'); if (!logs) return; - const time = new Date().toLocaleTimeString(); logs.innerText += `\n[${time}] ${message}`; requestAnimationFrame(() => { logs.scrollTop = logs.scrollHeight; }); @@ -13,11 +13,7 @@ function appendLog(message) { function collectErrorsFromText(text) { if (!text) return; - const patterns = [ - /error/i, /failed/i, /failure/i, /exception/i, /fatal/i, - /gradle task assemble.*failed/i, /could not/i, /cannot/i, - /what went wrong/i, /execution failed/i, /❌/ - ]; + const patterns = [/error/i, /failed/i, /failure/i, /exception/i, /fatal/i, /could not/i, /cannot/i, /what went wrong/i, /execution failed/i, /❌/]; const lines = String(text).split('\n').filter(line => patterns.some(pattern => pattern.test(line))); lines.forEach(line => { const cleaned = line.trim(); @@ -41,8 +37,7 @@ function renderErrors() { } function copyErrorsForAI() { - const text = collectedErrors.join('\n'); - navigator.clipboard.writeText(text || 'Ошибок пока нет'); + navigator.clipboard.writeText(collectedErrors.join('\n') || 'Ошибок пока нет'); appendLog('Ошибки скопированы в буфер'); } @@ -69,25 +64,9 @@ function setTaskProgress(percent, message, running = true) { 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}`); -} +function setBusy(message) { currentBusyTask = message; setTaskProgress(5, message, true); } +function setDone(message) { currentBusyTask = null; setTaskProgress(100, message, false); appendLog(`✅ ${message}`); } +function setError(message) { currentBusyTask = null; setStatus(message, 'error'); appendLog(`❌ ${message}`); } function updateProgressFromLine(line) { const text = line.toLowerCase(); @@ -96,18 +75,14 @@ function updateProgressFromLine(line) { else if (text.includes('got dependencies')) setTaskProgress(65, 'Зависимости готовы', true); else if (text.includes('running gradle task')) setTaskProgress(50, 'Gradle собирает приложение', true); else if (text.includes('built ') || text.includes('app-release.apk')) setTaskProgress(95, 'APK собран', true); + else if (text.includes('ota publish')) setTaskProgress(92, 'OTA публикация', true); else if (text.includes('installing') || text.includes('performing streamed install')) setTaskProgress(70, 'Установка на устройство', true); - else if (text.includes('syncing files')) setTaskProgress(80, 'Синхронизация файлов на телефоне', true); else if (text.includes('flutter run key commands')) setTaskProgress(90, 'Приложение запущено, Flutter подключён', true); } 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 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; @@ -115,30 +90,15 @@ async function workspaceApi(url, payload = {}, method = 'POST') { async function streamRuntimeCommand(command, title, progress = 50) { const workspace = getWorkspacePath(); - if (!workspace) { - setError('Workspace не выбран'); - return false; - } - + if (!workspace) { setError('Workspace не выбран'); return false; } setTaskProgress(progress, title || command, true); appendLog(`▶ ${title || command} live`); - - const response = await fetch('/api/runtime/command-stream', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({workspace, command, device: getSelectedDevice()}) - }); - - if (!response.ok || !response.body) { - setError(`Не удалось открыть live stream: ${title || command}`); - return false; - } - + const response = await fetch('/api/runtime/command-stream', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({workspace, command, device: getSelectedDevice()}) }); + if (!response.ok || !response.body) { setError(`Не удалось открыть live stream: ${title || command}`); return false; } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; let ok = true; - while (true) { const {value, done} = await reader.read(); if (done) break; @@ -149,121 +109,58 @@ async function streamRuntimeCommand(command, title, progress = 50) { if (!raw.trim()) continue; let event; try { event = JSON.parse(raw); } catch (_) { appendLog(raw); continue; } - if (event.type === 'line') { - appendLog(event.message); - updateProgressFromLine(event.message); - } else if (event.type === 'error') { - ok = false; - setError(event.message); - } else if (event.type === 'done') { - ok = Number(event.returncode) === 0; - appendLog(ok ? `✅ Выполнено: ${title || command}` : `❌ Ошибка: ${title || command} exit ${event.returncode}`); - } + if (event.type === 'line') { appendLog(event.message); updateProgressFromLine(event.message); } + else if (event.type === 'error') { ok = false; setError(event.message); } + else if (event.type === 'publish') { appendLog(event.success ? '✅ OTA публикация выполнена' : `OTA публикация пропущена: ${event.result?.message || ''}`); } + else if (event.type === 'done') { ok = Number(event.returncode) === 0; appendLog(ok ? `✅ Выполнено: ${title || command}` : `❌ Ошибка: ${title || command} exit ${event.returncode}`); } } } - if (!ok) setError(`Ошибка: ${title || command}`); return ok; } -function getSelectedDevice() { - const select = document.getElementById('deviceSelect'); - return select ? (select.value || null) : null; +function getSelectedDevice() { return selectedDeviceSerial; } +function getWorkspacePath() { const input = document.getElementById('workspacePath'); return input ? input.value.trim() : ''; } +function selectDevice(serial) { + selectedDeviceSerial = serial; + document.querySelectorAll('.device-card').forEach(card => card.classList.toggle('selected', card.dataset.serial === serial)); + appendLog(`📱 Выбрано устройство: ${serial}`); } -function getWorkspacePath() { - const input = document.getElementById('workspacePath'); - return input ? input.value.trim() : ''; -} - -async function loadSystemStatus() { - setStatus('Проверяю состояние DevConsole', 'running'); - await workspaceApi('/api/system/status', {}, 'GET'); - setStatus('DevConsole готов', 'done'); -} +async function loadSystemStatus() { setStatus('Проверяю состояние DevConsole', 'running'); await workspaceApi('/api/system/status', {}, 'GET'); setStatus('DevConsole готов', 'done'); } async function saveGitHubSettings() { const username = document.getElementById('githubUsername')?.value.trim() || ''; const token = document.getElementById('githubToken')?.value.trim() || ''; if (!username) { setError('Укажите GitHub login'); return; } if (!token) { setError('Укажите GitHub key'); return; } - setBusy('Сохраняю GitHub доступ'); const data = await workspaceApi('/api/settings/github', {username, token}); - if (data.success) { - const tokenInput = document.getElementById('githubToken'); - if (tokenInput) tokenInput.value = ''; - setDone('GitHub доступ сохранён'); - } else setError('Не удалось сохранить GitHub доступ'); + if (data.success) { const tokenInput = document.getElementById('githubToken'); if (tokenInput) tokenInput.value = ''; appendLog('✅ GitHub доступ сохранён'); } + else setError('Не удалось сохранить GitHub доступ'); } function renderGroupedRuntimeButtons() { const container = document.getElementById('runtimeButtons'); if (!container) return; - container.innerHTML = ` - - - - - - `; + container.innerHTML = ``; } -async function loadRuntimeCommands() { - renderGroupedRuntimeButtons(); - setStatus('Runtime сценарии готовы', 'done'); -} - -async function executeRuntimeCommand(command, title, progress = 50) { - return await streamRuntimeCommand(command, title, progress); -} +async function loadRuntimeCommands() { renderGroupedRuntimeButtons(); setStatus('Runtime сценарии готовы', 'done'); } +async function executeRuntimeCommand(command, title, progress = 50) { return await streamRuntimeCommand(command, title, progress); } async function runRuntimeScenario(scenario) { const workspace = getWorkspacePath(); if (!workspace) { setError('Workspace не выбран'); return; } + if (scenario === 'check') { setBusy('Проверка проекта'); 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('Запуск на телефоне'); 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'); 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'); await installLatestApk(); return; } + if (scenario === 'diagnostics') { setBusy('Диагностика Android runtime'); if (!await executeRuntimeCommand('adb_reconnect', 'ADB Reconnect', 40)) return; if (!await executeRuntimeCommand('adb_logcat', 'ADB Logcat Snapshot', 80)) return; setDone('Диагностика завершена'); } +} - 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 stopRuntimeCommand() { + const data = await workspaceApi('/api/runtime/stop', {send_confirm: true}); + appendLog(data.success ? '🛑 Команда остановлена' : 'Не удалось остановить команду'); } async function installLatestApk() { @@ -279,7 +176,6 @@ 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); @@ -290,21 +186,13 @@ 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 => ` -