From 1ab02d6a5a2dfcda19cd07493883ffcbda496f5e 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 02:05:01 +0900 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BA=D1=83=20APK=20?= =?UTF-8?q?=D0=B8=D0=B7=20artifacts=20panel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/workspace.js | 367 +----------------------------------------- 1 file changed, 1 insertion(+), 366 deletions(-) diff --git a/frontend/workspace.js b/frontend/workspace.js index 96f2bc7..311c8dd 100644 --- a/frontend/workspace.js +++ b/frontend/workspace.js @@ -1,366 +1 @@ -let currentBusyTask = null; -let collectedErrors = []; -let selectedDeviceSerial = null; - -function appendLog(message) { - const systemLog = document.getElementById('systemLog'); - const time = new Date().toLocaleTimeString(); - const line = `[${time}] ${message}`; - if (systemLog) { - systemLog.innerText += systemLog.innerText ? `\n${line}` : line; - requestAnimationFrame(() => { systemLog.scrollTop = systemLog.scrollHeight; }); - return; - } - const logs = document.getElementById('logs'); - if (!logs) return; - logs.innerText += `\n${line}`; - requestAnimationFrame(() => { logs.scrollTop = logs.scrollHeight; }); -} - -function appendRuntimeLog(message) { - const logs = document.getElementById('logs'); - if (!logs) return; - const time = new Date().toLocaleTimeString(); - logs.innerText += logs.innerText ? `\n[${time}] ${message}` : `[${time}] ${message}`; - requestAnimationFrame(() => { logs.scrollTop = logs.scrollHeight; }); - collectErrorsFromText(message); -} - -function clearRuntimeOutput() { - const logs = document.getElementById('logs'); - if (logs) logs.innerText = ''; - collectedErrors = []; - renderErrors(); -} - -function collectErrorsFromText(text) { - if (!text) return; - 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(); - if (cleaned && !collectedErrors.includes(cleaned)) collectedErrors.push(cleaned); - }); - renderErrors(); -} - -function renderErrors() { - const box = document.getElementById('errorsBox'); - const count = document.getElementById('errorCount'); - if (!box || !count) return; - if (collectedErrors.length === 0) { - box.innerText = 'Здесь появятся ошибки из Flutter, Gradle, ADB, API и stderr.'; - count.innerText = 'Ошибок пока нет'; - return; - } - box.innerText = collectedErrors.join('\n'); - count.innerText = `Ошибок: ${collectedErrors.length}`; - requestAnimationFrame(() => { box.scrollTop = box.scrollHeight; }); -} - -function copyErrorsForAI() { - navigator.clipboard.writeText(collectedErrors.join('\n') || 'Ошибок пока нет'); - appendLog('Ошибки скопированы в буфер'); -} - -function clearErrors() { - collectedErrors = []; - renderErrors(); - appendLog('Панель ошибок очищена'); -} - -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; setTaskProgress(5, message, true); appendLog(message); } -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(); - if (text.includes('resolving dependencies')) setTaskProgress(20, 'Разбор зависимостей Flutter', true); - else if (text.includes('downloading packages')) setTaskProgress(35, 'Загрузка пакетов', true); - 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('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 data = await response.json().catch(() => ({success: false, detail: 'Пустой ответ сервера'})); - if (!response.ok) appendLog(`Ошибка API ${response.status}: ${data.detail || 'unknown error'}`); - return data; -} - -async function streamRuntimeCommand(command, title, progress = 50) { - const workspace = getWorkspacePath(); - if (!workspace) { setError('Workspace не выбран'); return false; } - clearRuntimeOutput(); - setTaskProgress(progress, title || command, true); - appendLog(`▶ ${title || command} live`); - appendRuntimeLog(`▶ ${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}`); appendRuntimeLog(`❌ Не удалось открыть 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; - buffer += decoder.decode(value, {stream: true}); - const lines = buffer.split('\n'); - buffer = lines.pop() || ''; - for (const raw of lines) { - if (!raw.trim()) continue; - let event; - try { event = JSON.parse(raw); } catch (_) { appendRuntimeLog(raw); continue; } - if (event.type === 'line') { appendRuntimeLog(event.message); updateProgressFromLine(event.message); } - else if (event.type === 'error') { ok = false; appendRuntimeLog(`❌ ${event.message}`); setError(event.message); } - else if (event.type === 'publish') { const message = event.success ? '✅ OTA публикация выполнена' : `OTA публикация пропущена: ${event.result?.message || ''}`; appendRuntimeLog(message); appendLog(message); } - else if (event.type === 'done') { ok = Number(event.returncode) === 0; appendRuntimeLog(ok ? `✅ Выполнено: ${title || command}` : `❌ Ошибка: ${title || command} exit ${event.returncode}`); } - } - } - if (!ok) setError(`Ошибка: ${title || command}`); - await loadApkArtifacts(false); - return ok; -} - -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}`); -} - -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; } - const data = await workspaceApi('/api/settings/github', {username, token}); - 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 = ``; -} - -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 завершена'); await loadApkArtifacts(false); 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('Диагностика завершена'); } -} - -async function stopRuntimeCommand() { - const data = await workspaceApi('/api/runtime/stop', {send_confirm: true}); - appendLog(data.success ? '🛑 Команда остановлена' : 'Не удалось остановить команду'); -} - -async function installLatestApk() { - const workspace = getWorkspacePath(); - clearRuntimeOutput(); - setTaskProgress(40, 'Отправляю APK на устройство', true); - appendLog('▶ Install latest APK'); - appendRuntimeLog('▶ Install latest APK'); - const data = await workspaceApi('/api/runtime/install-latest-apk', {workspace, device: getSelectedDevice()}); - if (data.result?.stdout) appendRuntimeLog(data.result.stdout); - if (data.result?.stderr) appendRuntimeLog(data.result.stderr); - if (data.success) setDone('APK установлен'); else setError('Ошибка установки APK'); - await loadApkArtifacts(false); -} - -async function restartCurrentApp() { - const workspace = getWorkspacePath(); - const packageName = document.getElementById('packageName').value; - if (!packageName) { setError('Укажите package name'); return; } - clearRuntimeOutput(); - appendLog(`▶ Restart app: ${packageName}`); - appendRuntimeLog(`▶ Restart app: ${packageName}`); - const data = await workspaceApi('/api/runtime/restart-app', {workspace, device: getSelectedDevice(), package_name: packageName}); - if (data.result?.stdout) appendRuntimeLog(data.result.stdout); - if (data.result?.stderr) appendRuntimeLog(data.result.stderr); - if (data.success) setDone('Приложение перезапущено'); else setError('Ошибка перезапуска приложения'); -} - -function humanFileSize(bytes) { - const value = Number(bytes || 0); - if (value < 1024) return `${value} B`; - if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`; - return `${(value / 1024 / 1024).toFixed(1)} MB`; -} - -function renderApkArtifacts(apks, directory) { - const box = document.getElementById('apkArtifactsList'); - const meta = document.getElementById('apkArtifactsMeta'); - if (!box) return; - if (meta) meta.innerText = directory || 'build/app/outputs/flutter-apk'; - if (!apks || apks.length === 0) { - box.innerHTML = '