Добавил визуальные статусы выполнения процессов
Этот коммит содержится в:
+141
-98
@@ -1,3 +1,5 @@
|
|||||||
|
let currentBusyTask = null;
|
||||||
|
|
||||||
function appendLog(message) {
|
function appendLog(message) {
|
||||||
const logs = document.getElementById('logs');
|
const logs = document.getElementById('logs');
|
||||||
|
|
||||||
@@ -11,6 +13,61 @@ function appendLog(message) {
|
|||||||
logs.scrollTop = logs.scrollHeight;
|
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') {
|
async function workspaceApi(url, payload = {}, method = 'POST') {
|
||||||
appendLog(`API запрос: ${url}`);
|
appendLog(`API запрос: ${url}`);
|
||||||
|
|
||||||
@@ -47,21 +104,22 @@ function getWorkspacePath() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadSystemStatus() {
|
async function loadSystemStatus() {
|
||||||
|
setStatus('Проверяю состояние DevConsole', 'running');
|
||||||
const data = await workspaceApi('/api/system/status', {}, 'GET');
|
const data = await workspaceApi('/api/system/status', {}, 'GET');
|
||||||
const status = document.getElementById('githubStatus');
|
const status = document.getElementById('githubStatus');
|
||||||
const username = document.getElementById('githubUsername');
|
const username = document.getElementById('githubUsername');
|
||||||
|
|
||||||
if (!status || !username) {
|
if (status && username) {
|
||||||
return;
|
if (data.github?.username) {
|
||||||
|
username.value = data.github.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
status.innerText = data.github?.token_set
|
||||||
|
? `GitHub: ${data.github.username || 'user'} / доступ сохранён`
|
||||||
|
: 'GitHub доступ не сохранён';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.github?.username) {
|
setStatus('DevConsole готов', 'done');
|
||||||
username.value = data.github.username;
|
|
||||||
}
|
|
||||||
|
|
||||||
status.innerText = data.github?.token_set
|
|
||||||
? `GitHub: ${data.github.username || 'user'} / token сохранён`
|
|
||||||
: 'GitHub token не сохранён';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveGitHubSettings() {
|
async function saveGitHubSettings() {
|
||||||
@@ -69,15 +127,17 @@ async function saveGitHubSettings() {
|
|||||||
const token = document.getElementById('githubToken').value.trim();
|
const token = document.getElementById('githubToken').value.trim();
|
||||||
|
|
||||||
if (!username) {
|
if (!username) {
|
||||||
appendLog('Укажите GitHub username');
|
setError('Укажите GitHub username');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
appendLog('Укажите GitHub token');
|
setError('Укажите GitHub credential');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setBusy('Сохраняю GitHub доступ');
|
||||||
|
|
||||||
const data = await workspaceApi('/api/settings/github', {
|
const data = await workspaceApi('/api/settings/github', {
|
||||||
username,
|
username,
|
||||||
token
|
token
|
||||||
@@ -85,8 +145,10 @@ async function saveGitHubSettings() {
|
|||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
document.getElementById('githubToken').value = '';
|
document.getElementById('githubToken').value = '';
|
||||||
appendLog('GitHub доступ сохранён');
|
|
||||||
await loadSystemStatus();
|
await loadSystemStatus();
|
||||||
|
setDone('GitHub доступ сохранён');
|
||||||
|
} else {
|
||||||
|
setError('Не удалось сохранить GitHub доступ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,17 +170,20 @@ function renderGroupedRuntimeButtons() {
|
|||||||
|
|
||||||
async function loadRuntimeCommands() {
|
async function loadRuntimeCommands() {
|
||||||
renderGroupedRuntimeButtons();
|
renderGroupedRuntimeButtons();
|
||||||
|
setStatus('Runtime сценарии готовы', 'done');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function executeRuntimeCommand(command, title) {
|
async function executeRuntimeCommand(command, title, progress = 50) {
|
||||||
const workspace = getWorkspacePath();
|
const workspace = getWorkspacePath();
|
||||||
|
|
||||||
if (!workspace) {
|
if (!workspace) {
|
||||||
appendLog('Workspace не выбран');
|
setError('Workspace не выбран');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
appendLog(`▶ ${title || command}`);
|
const taskTitle = title || command;
|
||||||
|
setTaskProgress(progress, taskTitle, true);
|
||||||
|
appendLog(`▶ ${taskTitle}`);
|
||||||
|
|
||||||
const data = await workspaceApi('/api/runtime/command', {
|
const data = await workspaceApi('/api/runtime/command', {
|
||||||
workspace,
|
workspace,
|
||||||
@@ -139,6 +204,10 @@ async function executeRuntimeCommand(command, title) {
|
|||||||
: `❌ Ошибка: ${data.label || command}`
|
: `❌ Ошибка: ${data.label || command}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!data.success) {
|
||||||
|
setError(`Ошибка: ${data.label || command}`);
|
||||||
|
}
|
||||||
|
|
||||||
return Boolean(data.success);
|
return Boolean(data.success);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,51 +215,55 @@ async function runRuntimeScenario(scenario) {
|
|||||||
const workspace = getWorkspacePath();
|
const workspace = getWorkspacePath();
|
||||||
|
|
||||||
if (!workspace) {
|
if (!workspace) {
|
||||||
appendLog('Workspace не выбран');
|
setError('Workspace не выбран');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scenario === 'check') {
|
if (scenario === 'check') {
|
||||||
|
setBusy('Проверка проекта');
|
||||||
appendLog('=== Проверка проекта: git pull + flutter pub get ===');
|
appendLog('=== Проверка проекта: git pull + flutter pub get ===');
|
||||||
if (!await executeRuntimeCommand('git_pull', 'Git Pull')) return;
|
if (!await executeRuntimeCommand('git_pull', 'Git Pull', 25)) return;
|
||||||
await executeRuntimeCommand('flutter_pub_get', 'Flutter Pub Get');
|
if (!await executeRuntimeCommand('flutter_pub_get', 'Flutter Pub Get', 70)) return;
|
||||||
appendLog('=== Проверка завершена ===');
|
setDone('Проверка завершена');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scenario === 'run_profile') {
|
if (scenario === 'run_profile') {
|
||||||
appendLog('=== Запуск на телефоне в profile режиме ===');
|
|
||||||
if (!getSelectedDevice()) {
|
if (!getSelectedDevice()) {
|
||||||
appendLog('Устройство не выбрано');
|
setError('Устройство не выбрано');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!await executeRuntimeCommand('flutter_pub_get', 'Подготовка зависимостей')) return;
|
setBusy('Запуск на телефоне');
|
||||||
await executeRuntimeCommand('flutter_run_profile', 'Flutter Run --profile');
|
appendLog('=== Запуск на телефоне в profile режиме ===');
|
||||||
appendLog('=== Запуск завершён ===');
|
if (!await executeRuntimeCommand('flutter_pub_get', 'Подготовка зависимостей', 30)) return;
|
||||||
|
if (!await executeRuntimeCommand('flutter_run_profile', 'Flutter Run --profile', 75)) return;
|
||||||
|
setDone('Запуск завершён');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scenario === 'build') {
|
if (scenario === 'build') {
|
||||||
|
setBusy('Сборка APK');
|
||||||
appendLog('=== Сборка APK: clean + pub get + release build ===');
|
appendLog('=== Сборка APK: clean + pub get + release build ===');
|
||||||
if (!await executeRuntimeCommand('flutter_clean', 'Flutter Clean')) return;
|
if (!await executeRuntimeCommand('flutter_clean', 'Flutter Clean', 20)) return;
|
||||||
if (!await executeRuntimeCommand('flutter_pub_get', 'Flutter Pub Get')) return;
|
if (!await executeRuntimeCommand('flutter_pub_get', 'Flutter Pub Get', 45)) return;
|
||||||
await executeRuntimeCommand('flutter_build_apk', 'Flutter Build APK');
|
if (!await executeRuntimeCommand('flutter_build_apk', 'Flutter Build APK', 80)) return;
|
||||||
appendLog('=== Сборка завершена ===');
|
setDone('Сборка APK завершена');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scenario === 'install') {
|
if (scenario === 'install') {
|
||||||
|
setBusy('Установка APK');
|
||||||
appendLog('=== Установка последнего APK на устройство ===');
|
appendLog('=== Установка последнего APK на устройство ===');
|
||||||
await installLatestApk();
|
await installLatestApk();
|
||||||
appendLog('=== Установка завершена ===');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scenario === 'diagnostics') {
|
if (scenario === 'diagnostics') {
|
||||||
|
setBusy('Диагностика Android runtime');
|
||||||
appendLog('=== Диагностика Android runtime ===');
|
appendLog('=== Диагностика Android runtime ===');
|
||||||
await executeRuntimeCommand('adb_reconnect', 'ADB Reconnect');
|
if (!await executeRuntimeCommand('adb_reconnect', 'ADB Reconnect', 40)) return;
|
||||||
await executeRuntimeCommand('adb_logcat', 'ADB Logcat Snapshot');
|
if (!await executeRuntimeCommand('adb_logcat', 'ADB Logcat Snapshot', 80)) return;
|
||||||
appendLog('=== Диагностика завершена ===');
|
setDone('Диагностика завершена');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,6 +274,8 @@ async function runRuntimeCommand(command) {
|
|||||||
async function installLatestApk() {
|
async function installLatestApk() {
|
||||||
const workspace = getWorkspacePath();
|
const workspace = getWorkspacePath();
|
||||||
|
|
||||||
|
setTaskProgress(40, 'Отправляю APK на устройство', true);
|
||||||
|
|
||||||
const data = await workspaceApi('/api/runtime/install-latest-apk', {
|
const data = await workspaceApi('/api/runtime/install-latest-apk', {
|
||||||
workspace,
|
workspace,
|
||||||
device: getSelectedDevice()
|
device: getSelectedDevice()
|
||||||
@@ -214,7 +289,11 @@ async function installLatestApk() {
|
|||||||
appendLog(data.result.stderr);
|
appendLog(data.result.stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
appendLog(data.success ? '✅ APK установлен' : '❌ Ошибка установки APK');
|
if (data.success) {
|
||||||
|
setDone('APK установлен');
|
||||||
|
} else {
|
||||||
|
setError('Ошибка установки APK');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function restartCurrentApp() {
|
async function restartCurrentApp() {
|
||||||
@@ -222,10 +301,12 @@ async function restartCurrentApp() {
|
|||||||
const packageName = document.getElementById('packageName').value;
|
const packageName = document.getElementById('packageName').value;
|
||||||
|
|
||||||
if (!packageName) {
|
if (!packageName) {
|
||||||
appendLog('Укажите package name');
|
setError('Укажите package name');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setBusy('Перезапускаю приложение');
|
||||||
|
|
||||||
const data = await workspaceApi('/api/runtime/restart-app', {
|
const data = await workspaceApi('/api/runtime/restart-app', {
|
||||||
workspace,
|
workspace,
|
||||||
device: getSelectedDevice(),
|
device: getSelectedDevice(),
|
||||||
@@ -239,6 +320,12 @@ async function restartCurrentApp() {
|
|||||||
if (data.result?.stderr) {
|
if (data.result?.stderr) {
|
||||||
appendLog(data.result.stderr);
|
appendLog(data.result.stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
setDone('Приложение перезапущено');
|
||||||
|
} else {
|
||||||
|
setError('Ошибка перезапуска приложения');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadProjects() {
|
async function loadProjects() {
|
||||||
@@ -248,6 +335,7 @@ async function loadProjects() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setStatus('Загружаю список проектов', 'running');
|
||||||
appendLog('Загрузка списка проектов');
|
appendLog('Загрузка списка проектов');
|
||||||
|
|
||||||
const data = await workspaceApi('/api/projects/list', {}, 'GET');
|
const data = await workspaceApi('/api/projects/list', {}, 'GET');
|
||||||
@@ -256,28 +344,35 @@ async function loadProjects() {
|
|||||||
|
|
||||||
if (projects.length === 0) {
|
if (projects.length === 0) {
|
||||||
container.innerHTML = 'Проекты отсутствуют';
|
container.innerHTML = 'Проекты отсутствуют';
|
||||||
|
setStatus('Проекты отсутствуют', 'idle');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
container.innerHTML = projects.map(project => `
|
container.innerHTML = projects.map(project => `
|
||||||
<div style="padding:10px;margin-bottom:10px;background:#1d2430;border-radius:10px;cursor:pointer" onclick="openProject('${project.workspace}')">
|
<div class="project-card" onclick="openProject('${project.workspace}', '${project.name || 'project'}')">
|
||||||
<strong>${project.name}</strong><br>
|
<strong>${project.name}</strong><br>
|
||||||
<small>${project.stack || 'unknown stack'}</small>
|
<small>${project.stack || 'unknown stack'}</small><br>
|
||||||
|
<small>${project.workspace}</small>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|
||||||
|
setStatus(`Проекты загружены: ${projects.length}`, 'done');
|
||||||
}
|
}
|
||||||
|
|
||||||
function openProject(workspace) {
|
function openProject(workspace, name = 'project') {
|
||||||
document.getElementById('workspacePath').value = workspace;
|
document.getElementById('workspacePath').value = workspace;
|
||||||
|
|
||||||
appendLog(`Открытие проекта: ${workspace}`);
|
appendLog(`Открытие проекта: ${workspace}`);
|
||||||
|
setTaskProgress(100, `Проект выбран: ${name}`, false);
|
||||||
loadWorkspaceTree();
|
setStatus(`Проект готов: ${name}`, 'done');
|
||||||
|
appendLog(`✅ Workspace готов к runtime-командам: ${workspace}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function registerCurrentProject(repoUrl, workspace, stack = 'unknown') {
|
async function registerCurrentProject(repoUrl, workspace, stack = 'unknown') {
|
||||||
const name = repoUrl.split('/').pop().replace('.git', '');
|
const name = repoUrl.split('/').pop().replace('.git', '');
|
||||||
|
|
||||||
|
setTaskProgress(90, `Регистрирую проект: ${name}`, true);
|
||||||
|
|
||||||
await workspaceApi('/api/projects/register', {
|
await workspaceApi('/api/projects/register', {
|
||||||
name,
|
name,
|
||||||
repo_url: repoUrl,
|
repo_url: repoUrl,
|
||||||
@@ -294,71 +389,15 @@ async function loadWorkspaceTree() {
|
|||||||
const workspace = getWorkspacePath();
|
const workspace = getWorkspacePath();
|
||||||
|
|
||||||
if (!workspace) {
|
if (!workspace) {
|
||||||
appendLog('Workspace path пустой');
|
setError('Workspace path пустой');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
appendLog(`Загрузка workspace: ${workspace}`);
|
setDone(`Workspace выбран: ${workspace}`);
|
||||||
|
|
||||||
const data = await workspaceApi('/api/files/tree', {
|
|
||||||
path: workspace
|
|
||||||
});
|
|
||||||
|
|
||||||
renderTree(data.tree || []);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderTree(tree, level = 0) {
|
|
||||||
const container = document.getElementById('fileTree');
|
|
||||||
|
|
||||||
if (level === 0) {
|
|
||||||
container.innerHTML = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
tree.forEach(node => {
|
|
||||||
const item = document.createElement('div');
|
|
||||||
|
|
||||||
item.className = 'tree-node';
|
|
||||||
item.style.paddingLeft = `${level * 16}px`;
|
|
||||||
|
|
||||||
if (node.type === 'directory') {
|
|
||||||
item.innerHTML = `📁 ${node.name}`;
|
|
||||||
container.appendChild(item);
|
|
||||||
|
|
||||||
renderTree(node.children || [], level + 1);
|
|
||||||
} else {
|
|
||||||
item.innerHTML = `📄 ${node.name}`;
|
|
||||||
item.onclick = () => openFile(node.path);
|
|
||||||
container.appendChild(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openFile(path) {
|
|
||||||
appendLog(`Открытие файла: ${path}`);
|
|
||||||
|
|
||||||
const data = await workspaceApi('/api/files/read', {
|
|
||||||
path
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('editorPath').innerText = path;
|
|
||||||
document.getElementById('editor').value = data.content || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveCurrentFile() {
|
|
||||||
const path = document.getElementById('editorPath').innerText;
|
|
||||||
const content = document.getElementById('editor').value;
|
|
||||||
|
|
||||||
appendLog(`Сохранение файла: ${path}`);
|
|
||||||
|
|
||||||
const data = await workspaceApi('/api/files/save', {
|
|
||||||
path,
|
|
||||||
content
|
|
||||||
});
|
|
||||||
|
|
||||||
appendLog(data.success ? 'Файл успешно сохранен' : 'Ошибка сохранения файла');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadDevices() {
|
async function loadDevices() {
|
||||||
|
setStatus('Ищу Android устройства', 'running');
|
||||||
appendLog('Обновление списка Android устройств');
|
appendLog('Обновление списка Android устройств');
|
||||||
|
|
||||||
const data = await workspaceApi('/api/android/devices');
|
const data = await workspaceApi('/api/android/devices');
|
||||||
@@ -379,6 +418,7 @@ async function loadDevices() {
|
|||||||
if (devices.length === 0) {
|
if (devices.length === 0) {
|
||||||
container.innerHTML = 'Устройства не подключены';
|
container.innerHTML = 'Устройства не подключены';
|
||||||
select.innerHTML = '<option value="">Нет устройств</option>';
|
select.innerHTML = '<option value="">Нет устройств</option>';
|
||||||
|
setStatus('Android устройства не подключены', 'idle');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,9 +437,12 @@ async function loadDevices() {
|
|||||||
|
|
||||||
return `<option value="${id}">${id}</option>`;
|
return `<option value="${id}">${id}</option>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
|
setStatus(`Android устройства найдены: ${devices.length}`, 'done');
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
|
setStatus('Запуск DevConsole runtime', 'running');
|
||||||
loadSystemStatus();
|
loadSystemStatus();
|
||||||
loadDevices();
|
loadDevices();
|
||||||
loadProjects();
|
loadProjects();
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user