diff --git a/frontend/workspace.js b/frontend/workspace.js index fea5702..96f2bc7 100644 --- a/frontend/workspace.js +++ b/frontend/workspace.js @@ -140,6 +140,7 @@ async function streamRuntimeCommand(command, title, progress = 50) { } } if (!ok) setError(`Ошибка: ${title || command}`); + await loadApkArtifacts(false); return ok; } @@ -166,7 +167,7 @@ async function saveGitHubSettings() { function renderGroupedRuntimeButtons() { const container = document.getElementById('runtimeButtons'); if (!container) return; - container.innerHTML = ``; + container.innerHTML = ``; } async function loadRuntimeCommands() { renderGroupedRuntimeButtons(); setStatus('Runtime сценарии готовы', 'done'); } @@ -177,7 +178,7 @@ async function runRuntimeScenario(scenario) { 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 === '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('Диагностика завершена'); } } @@ -197,6 +198,7 @@ async function installLatestApk() { 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() { @@ -212,6 +214,45 @@ async function restartCurrentApp() { 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 = '
APK пока нет. Выполни “Собрать APK”, потом обнови список.
'; + return; + } + box.innerHTML = apks.map(apk => { + const modified = apk.mtime ? new Date(apk.mtime * 1000).toLocaleString() : ''; + const openUrl = encodeURI(apk.open_url); + const downloadUrl = encodeURI(apk.download_url); + return `
${apk.name}${apk.relative_path}${humanFileSize(apk.size_bytes)} · ${modified}
Скачать
`; + }).join(''); +} + +async function loadApkArtifacts(verbose = true) { + const workspace = getWorkspacePath(); + const box = document.getElementById('apkArtifactsList'); + if (!box) return; + if (!workspace) { + box.innerHTML = '
Сначала выбери проект.
'; + if (verbose) setError('Workspace не выбран'); + return; + } + if (verbose) appendLog('📦 Обновляю APK файлы'); + const data = await workspaceApi(`/api/android/apks?workspace=${encodeURIComponent(workspace)}`, {}, 'GET'); + if (data.success) renderApkArtifacts(data.apks || [], data.directory || ''); + else box.innerHTML = '
Не удалось загрузить APK файлы.
'; +} + async function loadProjects() { const container = document.getElementById('projectsList'); if (!container) return; @@ -231,6 +272,7 @@ function openProject(workspace, name = 'project') { appendLog(`Открытие проекта: ${workspace}`); setTaskProgress(100, `Проект выбран: ${name}`, false); setStatus(`Проект готов: ${name}`, 'done'); + loadApkArtifacts(false); } async function openEditProjectModal(workspace) { @@ -254,7 +296,7 @@ async function openEditProjectModal(workspace) { document.getElementById('otaBuild').value = ota.build || ''; document.getElementById('otaNotes').value = ota.notes || ''; toggleOtaFields(); - modal.style.display = 'flex'; + if (modal) modal.style.display = 'flex'; } function closeEditProjectModal() { document.getElementById('editProjectModal').style.display = 'none'; } @@ -304,6 +346,14 @@ async function loadDevices() { setStatus(`Android устройства найдены: ${devices.length}`, 'done'); } +function openAddProjectModal(){document.getElementById('addProjectModal').style.display='flex';} +function closeAddProjectModal(){document.getElementById('addProjectModal').style.display='none';} +function toggleAuthFields(){document.getElementById('authFields').style.display=document.getElementById('repoNeedsAuth').checked?'block':'none';} +function toggleOtaFields(){document.getElementById('otaFields').style.display=document.getElementById('otaEnabled').checked?'block':'none';} +async function syncPubspecVersion(){const workspace=document.getElementById('editWorkspace').value;if(!workspace){appendLog('Workspace не выбран');return;}appendLog('📦 Читаю pubspec.yaml');const data=await workspaceApi(`/api/projects/pubspec-version?workspace=${encodeURIComponent(workspace)}`,{},'GET');if(data.version){document.getElementById('otaVersion').value=data.version;document.getElementById('otaBuild').value=data.build;appendLog(`✅ Pubspec version synced: ${data.full_version}`);}else{appendLog('❌ Не удалось считать pubspec.yaml');}} +function setAnalysisProgress(percent,message){const wrap=document.getElementById('analysisProgressWrap');const bar=document.getElementById('analysisProgressBar');const label=document.getElementById('analysisProgressLabel');if(wrap){wrap.style.display='block';}if(bar){bar.style.width=percent+'%';}if(label){label.innerText=percent+'% — '+message;}appendLog(percent+'% — '+message);} +async function analyzeProject(){const repo_url=document.getElementById('repoUrl').value.trim();if(!repo_url){appendLog('0% — Укажи Git URL');return;}if(document.getElementById('repoNeedsAuth').checked){await saveGitHubSettings();}setAnalysisProgress(5,'Проверяю Git URL');setAnalysisProgress(20,'Готовлю workspace');setAnalysisProgress(35,'Клонирую или обновляю репозиторий');const data=await workspaceApi('/api/projects/analyze',{repo_url});if(data.detail){setAnalysisProgress(100,'Ошибка анализа: '+data.detail);return;}setAnalysisProgress(65,'Репозиторий получен, сканирую структуру');if(data.analysis?.detected_stack?.length){setAnalysisProgress(76,'Стек: '+data.analysis.detected_stack.join(', '));}else{setAnalysisProgress(76,'Стек не определён автоматически');}if(data.analysis?.workspace){setAnalysisProgress(88,'Workspace: '+data.analysis.workspace);document.getElementById('workspacePath').value=data.analysis.workspace;setAnalysisProgress(94,'Регистрирую проект');await registerCurrentProject(repo_url,data.analysis.workspace,data.analysis.detected_stack?.join(', ')||'unknown');}setAnalysisProgress(100,'Проект добавлен');closeAddProjectModal();} + window.addEventListener('load', () => { setStatus('Запуск DevConsole runtime', 'running'); appendLog('DevConsole runtime initialized...'); @@ -311,5 +361,6 @@ window.addEventListener('load', () => { loadDevices(); loadProjects(); loadRuntimeCommands(); + renderApkArtifacts([], 'build/app/outputs/flutter-apk'); appendLog('DevConsole workspace runtime готов'); });