api-keys
This commit is contained in:
117
api-client.js
117
api-client.js
@@ -445,17 +445,15 @@ module.exports = function(app, db, upload) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/client/tasks/:taskId/copy - Скопировать задачу в целевой сервис
|
* POST /api/client/tasks/:taskId/sync - Синхронизировать задачу с целевым сервисом
|
||||||
*/
|
*/
|
||||||
router.post('/api/client/tasks/:taskId/copy', requireAuth, async (req, res) => {
|
router.post('/api/client/tasks/:taskId/sync', requireAuth, async (req, res) => {
|
||||||
const { taskId } = req.params;
|
const { taskId } = req.params;
|
||||||
const {
|
const {
|
||||||
target_connection_id,
|
target_connection_id,
|
||||||
target_api_url,
|
target_api_url,
|
||||||
target_api_key,
|
target_api_key,
|
||||||
new_assignees,
|
sync_files = true
|
||||||
due_date,
|
|
||||||
copy_files = true
|
|
||||||
} = req.body;
|
} = req.body;
|
||||||
const { connection_id } = req.query;
|
const { connection_id } = req.query;
|
||||||
const userId = req.session.user.id;
|
const userId = req.session.user.id;
|
||||||
@@ -506,19 +504,69 @@ module.exports = function(app, db, upload) {
|
|||||||
|
|
||||||
const sourceTask = sourceResponse.data.task;
|
const sourceTask = sourceResponse.data.task;
|
||||||
|
|
||||||
// 2. Создаем копию задачи в целевом сервисе
|
// 2. Проверяем, существует ли задача в целевой системе (поиск по ID)
|
||||||
const newTaskTitle = `Копия: ${sourceTask.title}`;
|
let existingTask = null;
|
||||||
|
try {
|
||||||
|
const checkResponse = await axios.get(
|
||||||
|
`${baseUrl}/api/external/tasks/${taskId}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'X-API-Key': targetKey
|
||||||
|
},
|
||||||
|
timeout: 5000
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (checkResponse.data && checkResponse.data.success) {
|
||||||
|
existingTask = checkResponse.data.task;
|
||||||
|
}
|
||||||
|
} catch (checkError) {
|
||||||
|
// Задача не найдена - это нормально, будем создавать новую
|
||||||
|
console.log('Задача не найдена в целевой системе, будет создана новая');
|
||||||
|
}
|
||||||
|
|
||||||
const taskData = {
|
let result;
|
||||||
title: newTaskTitle,
|
const syncedFiles = [];
|
||||||
description: sourceTask.description || '',
|
const warnings = [];
|
||||||
due_date: due_date || sourceTask.due_date,
|
|
||||||
|
if (existingTask) {
|
||||||
|
// 3. Обновляем существующую задачу
|
||||||
|
const updateData = {
|
||||||
|
title: sourceTask.title,
|
||||||
|
description: sourceTask.description,
|
||||||
|
due_date: sourceTask.due_date,
|
||||||
task_type: sourceTask.task_type || 'regular'
|
task_type: sourceTask.task_type || 'regular'
|
||||||
};
|
};
|
||||||
|
|
||||||
if (new_assignees && new_assignees.length > 0) {
|
const updateResponse = await axios.put(
|
||||||
taskData.assignedUsers = new_assignees;
|
`${baseUrl}/api/external/tasks/${taskId}`,
|
||||||
|
updateData,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'X-API-Key': targetKey,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
timeout: 15000
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!updateResponse.data || !updateResponse.data.success) {
|
||||||
|
return res.status(500).json({
|
||||||
|
error: 'Не удалось обновить задачу в целевом сервисе'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
result = {
|
||||||
|
taskId: taskId,
|
||||||
|
action: 'updated'
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 4. Создаем новую задачу (без префикса "Копия:")
|
||||||
|
const taskData = {
|
||||||
|
title: sourceTask.title,
|
||||||
|
description: sourceTask.description || '',
|
||||||
|
due_date: sourceTask.due_date,
|
||||||
|
task_type: sourceTask.task_type || 'regular'
|
||||||
|
};
|
||||||
|
|
||||||
const createResponse = await axios.post(
|
const createResponse = await axios.post(
|
||||||
`${baseUrl}/api/external/tasks/create`,
|
`${baseUrl}/api/external/tasks/create`,
|
||||||
@@ -538,13 +586,17 @@ module.exports = function(app, db, upload) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const newTaskId = createResponse.data.taskId;
|
result = {
|
||||||
|
taskId: createResponse.data.taskId,
|
||||||
|
action: 'created'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// 3. Копируем файлы, если нужно
|
// 5. Синхронизируем файлы, если нужно
|
||||||
const copiedFiles = [];
|
if (sync_files && sourceTask.files && sourceTask.files.length > 0) {
|
||||||
if (copy_files && sourceTask.files && sourceTask.files.length > 0) {
|
|
||||||
for (const file of sourceTask.files) {
|
for (const file of sourceTask.files) {
|
||||||
try {
|
try {
|
||||||
|
// Скачиваем файл из источника
|
||||||
const fileResponse = await axios({
|
const fileResponse = await axios({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: `${sourceConnection.url}/api/external/tasks/${taskId}/files/${file.id}/download`,
|
url: `${sourceConnection.url}/api/external/tasks/${taskId}/files/${file.id}/download`,
|
||||||
@@ -555,6 +607,7 @@ module.exports = function(app, db, upload) {
|
|||||||
timeout: 30000
|
timeout: 30000
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Загружаем в целевую систему
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('files', Buffer.from(fileResponse.data), {
|
formData.append('files', Buffer.from(fileResponse.data), {
|
||||||
filename: file.filename || file.original_name || 'file',
|
filename: file.filename || file.original_name || 'file',
|
||||||
@@ -562,7 +615,7 @@ module.exports = function(app, db, upload) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const uploadResponse = await axios.post(
|
const uploadResponse = await axios.post(
|
||||||
`${baseUrl}/api/external/tasks/${newTaskId}/files`,
|
`${baseUrl}/api/external/tasks/${result.taskId}/files`,
|
||||||
formData,
|
formData,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
@@ -575,44 +628,46 @@ module.exports = function(app, db, upload) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
copiedFiles.push({
|
syncedFiles.push({
|
||||||
original_name: file.filename || file.original_name,
|
original_name: file.filename || file.original_name,
|
||||||
success: uploadResponse.data && uploadResponse.data.success
|
success: uploadResponse.data && uploadResponse.data.success
|
||||||
});
|
});
|
||||||
} catch (fileError) {
|
} catch (fileError) {
|
||||||
console.error(`❌ Ошибка копирования файла:`, fileError.message);
|
console.error(`❌ Ошибка синхронизации файла:`, fileError.message);
|
||||||
copiedFiles.push({
|
syncedFiles.push({
|
||||||
original_name: file.filename || file.original_name,
|
original_name: file.filename || file.original_name,
|
||||||
success: false,
|
success: false,
|
||||||
error: fileError.message
|
error: fileError.message
|
||||||
});
|
});
|
||||||
|
warnings.push(`Не удалось синхронизировать файл: ${file.filename || file.original_name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { logActivity } = require('./database');
|
const { logActivity } = require('./database');
|
||||||
if (logActivity) {
|
if (logActivity) {
|
||||||
logActivity(0, userId, 'API_CLIENT_COPY_TASK',
|
logActivity(0, userId, 'API_CLIENT_SYNC_TASK',
|
||||||
`Скопирована задача ${taskId} из ${sourceConnection.url} в ${baseUrl}. Новый ID: ${newTaskId}`);
|
`${existingTask ? 'Обновлена' : 'Создана'} задача ${taskId} из ${sourceConnection.url} в ${baseUrl}. ${existingTask ? 'Обновление' : 'Новый ID: ' + result.taskId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: `Задача успешно скопирована${copiedFiles.length > 0 ? `, скопировано файлов: ${copiedFiles.filter(f => f.success).length}` : ''}`,
|
message: `Задача успешно ${existingTask ? 'обновлена' : 'создана'} в целевой системе`,
|
||||||
data: {
|
data: {
|
||||||
|
sync_type: result.action,
|
||||||
original_task_id: taskId,
|
original_task_id: taskId,
|
||||||
new_task_id: newTaskId,
|
target_task_id: result.taskId,
|
||||||
new_task_title: newTaskTitle,
|
|
||||||
target_service: baseUrl,
|
target_service: baseUrl,
|
||||||
copied_files: copiedFiles,
|
synced_files: syncedFiles,
|
||||||
assignees: new_assignees || 'не изменены'
|
assignees: sourceTask.assignments || [],
|
||||||
|
warnings: warnings
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Ошибка копирования задачи:', error.message);
|
console.error('❌ Ошибка синхронизации задачи:', error.message);
|
||||||
|
|
||||||
let errorMessage = 'Ошибка копирования задачи';
|
let errorMessage = 'Ошибка синхронизации задачи';
|
||||||
let statusCode = 500;
|
let statusCode = 500;
|
||||||
|
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
@@ -620,7 +675,7 @@ module.exports = function(app, db, upload) {
|
|||||||
errorMessage = 'Неверный API ключ для целевого сервиса';
|
errorMessage = 'Неверный API ключ для целевого сервиса';
|
||||||
statusCode = 401;
|
statusCode = 401;
|
||||||
} else if (error.response.status === 403) {
|
} else if (error.response.status === 403) {
|
||||||
errorMessage = 'Нет прав для создания задачи в целевом сервисе';
|
errorMessage = 'Нет прав для создания/обновления задачи в целевом сервисе';
|
||||||
statusCode = 403;
|
statusCode = 403;
|
||||||
} else if (error.response.status === 404) {
|
} else if (error.response.status === 404) {
|
||||||
errorMessage = 'Исходная задача не найдена';
|
errorMessage = 'Исходная задача не найдена';
|
||||||
|
|||||||
@@ -457,12 +457,12 @@
|
|||||||
background: #2980b9;
|
background: #2980b9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-copy {
|
.action-sync {
|
||||||
background: #9b59b6;
|
background: #9b59b6;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-copy:hover {
|
.action-sync:hover {
|
||||||
background: #8e44ad;
|
background: #8e44ad;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,7 +634,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-progress, .copy-progress {
|
.upload-progress, .sync-progress {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: #ecf0f1;
|
background: #ecf0f1;
|
||||||
@@ -655,7 +655,7 @@
|
|||||||
transition: width 0.3s;
|
transition: width 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-status {
|
.sync-status {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: #ecf0f1;
|
background: #ecf0f1;
|
||||||
@@ -923,18 +923,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Модальное окно копирования задачи -->
|
<!-- Модальное окно синхронизации задачи -->
|
||||||
<div class="modal" id="copyModal">
|
<div class="modal" id="syncModal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h3>Копирование задачи</h3>
|
<h3>Синхронизация задачи</h3>
|
||||||
<span class="modal-close" onclick="closeCopyModal()">×</span>
|
<span class="modal-close" onclick="closeSyncModal()">×</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-bottom: 20px;">
|
<div style="margin-bottom: 20px;">
|
||||||
<p>Вы копируете задачу: <strong id="copyTaskTitle"></strong></p>
|
<p>Вы синхронизируете задачу: <strong id="syncTaskTitle"></strong></p>
|
||||||
<p style="font-size: 14px; color: #7f8c8d; margin-top: 5px;">
|
<p style="font-size: 14px; color: #7f8c8d; margin-top: 5px;">
|
||||||
Автором новой задачи будете <strong id="currentUserName"></strong>
|
<i class="fas fa-info-circle"></i> Исполнители будут сохранены как в исходной задаче
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -958,36 +958,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group" style="margin-bottom: 15px;">
|
|
||||||
<label>Новые исполнители (ID пользователей через запятую)</label>
|
|
||||||
<input type="text" id="newAssignees" placeholder="123, 456, 789">
|
|
||||||
<small>Оставьте пустым, чтобы сохранить текущих исполнителей</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" style="margin-bottom: 15px;">
|
|
||||||
<label>Новый срок выполнения (необязательно)</label>
|
|
||||||
<input type="datetime-local" id="newDueDate">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" style="margin-bottom: 20px;">
|
<div class="form-group" style="margin-bottom: 20px;">
|
||||||
<label style="display: flex; align-items: center; gap: 10px;">
|
<label style="display: flex; align-items: center; gap: 10px;">
|
||||||
<input type="checkbox" id="copyFiles" checked>
|
<input type="checkbox" id="syncFiles" checked>
|
||||||
<span>Копировать файлы</span>
|
<span>Синхронизировать файлы</span>
|
||||||
</label>
|
</label>
|
||||||
|
<small style="display: block; margin-top: 5px; color: #7f8c8d;">
|
||||||
|
<i class="fas fa-exchange-alt"></i> При синхронизации задача будет обновлена в целевой системе,
|
||||||
|
если она там уже существует, или создана новая.
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="copyProgress" style="display: none;">
|
<div id="syncProgress" style="display: none;">
|
||||||
<div style="margin-bottom: 5px;">Копирование: <span id="copyPercent">0%</span></div>
|
<div style="margin-bottom: 5px;">Синхронизация: <span id="syncPercent">0%</span></div>
|
||||||
<div class="progress-bar">
|
<div class="progress-bar">
|
||||||
<div class="progress-fill" id="copyProgressBar" style="width: 0%;"></div>
|
<div class="progress-fill" id="syncProgressBar" style="width: 0%;"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="copyStatus" class="copy-status"></div>
|
<div id="syncStatus" class="sync-status"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display: flex; gap: 10px; justify-content: flex-end;">
|
<div style="display: flex; gap: 10px; justify-content: flex-end;">
|
||||||
<button class="btn btn-secondary" onclick="closeCopyModal()">Отмена</button>
|
<button class="btn btn-secondary" onclick="closeSyncModal()">Отмена</button>
|
||||||
<button class="btn btn-success" onclick="copyTask()" id="copyBtn">
|
<button class="btn btn-success" onclick="syncTask()" id="syncBtn">
|
||||||
<i class="fas fa-copy"></i> Копировать задачу
|
<i class="fas fa-sync-alt"></i> Синхронизировать задачу
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1005,8 +998,8 @@
|
|||||||
let pageSize = 50;
|
let pageSize = 50;
|
||||||
let currentTaskId = null;
|
let currentTaskId = null;
|
||||||
let selectedFiles = [];
|
let selectedFiles = [];
|
||||||
let copyTaskId = null;
|
let syncTaskId = null;
|
||||||
let copyTaskTitle = '';
|
let syncTaskTitle = '';
|
||||||
|
|
||||||
// Проверка авторизации
|
// Проверка авторизации
|
||||||
async function checkAuth() {
|
async function checkAuth() {
|
||||||
@@ -1016,7 +1009,6 @@
|
|||||||
|
|
||||||
if (data.user) {
|
if (data.user) {
|
||||||
document.getElementById('userName').textContent = data.user.name || data.user.login;
|
document.getElementById('userName').textContent = data.user.name || data.user.login;
|
||||||
document.getElementById('currentUserName').textContent = data.user.name || data.user.login;
|
|
||||||
} else {
|
} else {
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
}
|
}
|
||||||
@@ -1267,8 +1259,8 @@
|
|||||||
<button class="task-action-btn action-upload" onclick="openUploadModal('${task.id}')">
|
<button class="task-action-btn action-upload" onclick="openUploadModal('${task.id}')">
|
||||||
<i class="fas fa-upload"></i> Файлы
|
<i class="fas fa-upload"></i> Файлы
|
||||||
</button>
|
</button>
|
||||||
<button class="task-action-btn action-copy" onclick="openCopyModal('${task.id}', '${escapeHtml(task.title)}')">
|
<button class="task-action-btn action-sync" onclick="openSyncModal('${task.id}', '${escapeHtml(task.title)}')">
|
||||||
<i class="fas fa-copy"></i> Копировать
|
<i class="fas fa-sync-alt"></i> Синхронизировать
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1509,7 +1501,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Загрузка списка подключений для копирования
|
// Загрузка списка подключений для синхронизации
|
||||||
async function loadTargetConnections() {
|
async function loadTargetConnections() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/client/connections/list');
|
const response = await fetch('/api/client/connections/list');
|
||||||
@@ -1526,32 +1518,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Открыть модальное окно копирования
|
// Открыть модальное окно синхронизации
|
||||||
function openCopyModal(taskId, taskTitle) {
|
function openSyncModal(taskId, taskTitle) {
|
||||||
copyTaskId = taskId;
|
syncTaskId = taskId;
|
||||||
copyTaskTitle = taskTitle;
|
syncTaskTitle = taskTitle;
|
||||||
|
|
||||||
document.getElementById('copyTaskTitle').textContent = taskTitle;
|
document.getElementById('syncTaskTitle').textContent = taskTitle;
|
||||||
|
|
||||||
loadTargetConnections();
|
loadTargetConnections();
|
||||||
|
|
||||||
document.getElementById('copyModal').classList.add('active');
|
document.getElementById('syncModal').classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Закрыть модальное окно копирования
|
// Закрыть модальное окно синхронизации
|
||||||
function closeCopyModal() {
|
function closeSyncModal() {
|
||||||
document.getElementById('copyModal').classList.remove('active');
|
document.getElementById('syncModal').classList.remove('active');
|
||||||
copyTaskId = null;
|
syncTaskId = null;
|
||||||
|
|
||||||
document.getElementById('targetService').value = '';
|
document.getElementById('targetService').value = '';
|
||||||
document.getElementById('newServiceInputs').style.display = 'none';
|
document.getElementById('newServiceInputs').style.display = 'none';
|
||||||
document.getElementById('targetApiUrl').value = '';
|
document.getElementById('targetApiUrl').value = '';
|
||||||
document.getElementById('targetApiKey').value = '';
|
document.getElementById('targetApiKey').value = '';
|
||||||
document.getElementById('newAssignees').value = '';
|
document.getElementById('syncFiles').checked = true;
|
||||||
document.getElementById('newDueDate').value = '';
|
document.getElementById('syncProgress').style.display = 'none';
|
||||||
document.getElementById('copyFiles').checked = true;
|
document.getElementById('syncBtn').disabled = false;
|
||||||
document.getElementById('copyProgress').style.display = 'none';
|
document.getElementById('syncStatus').innerHTML = '';
|
||||||
document.getElementById('copyBtn').disabled = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Переключение между сохраненным и новым сервисом
|
// Переключение между сохраненным и новым сервисом
|
||||||
@@ -1561,14 +1552,12 @@
|
|||||||
targetService === 'new' ? 'block' : 'none';
|
targetService === 'new' ? 'block' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Копирование задачи
|
// Синхронизация задачи
|
||||||
async function copyTask() {
|
async function syncTask() {
|
||||||
if (!copyTaskId) return;
|
if (!syncTaskId) return;
|
||||||
|
|
||||||
const targetService = document.getElementById('targetService').value;
|
const targetService = document.getElementById('targetService').value;
|
||||||
const newAssignees = document.getElementById('newAssignees').value;
|
const syncFiles = document.getElementById('syncFiles').checked;
|
||||||
const newDueDate = document.getElementById('newDueDate').value;
|
|
||||||
const copyFiles = document.getElementById('copyFiles').checked;
|
|
||||||
|
|
||||||
if (!targetService) {
|
if (!targetService) {
|
||||||
showAlert('Выберите целевой сервис', 'warning');
|
showAlert('Выберите целевой сервис', 'warning');
|
||||||
@@ -1598,22 +1587,15 @@
|
|||||||
|
|
||||||
const requestData = {
|
const requestData = {
|
||||||
...targetData,
|
...targetData,
|
||||||
copy_files: copyFiles
|
sync_files: syncFiles
|
||||||
};
|
};
|
||||||
|
|
||||||
if (newAssignees) {
|
document.getElementById('syncProgress').style.display = 'block';
|
||||||
requestData.new_assignees = newAssignees.split(',').map(id => parseInt(id.trim()));
|
document.getElementById('syncBtn').disabled = true;
|
||||||
}
|
document.getElementById('syncStatus').innerHTML = 'Начинаем синхронизацию...';
|
||||||
|
|
||||||
if (newDueDate) {
|
|
||||||
requestData.due_date = new Date(newDueDate).toISOString();
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('copyProgress').style.display = 'block';
|
|
||||||
document.getElementById('copyBtn').disabled = true;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/client/tasks/${copyTaskId}/copy?connection_id=${currentConnectionId}`, {
|
const response = await fetch(`/api/client/tasks/${syncTaskId}/sync?connection_id=${currentConnectionId}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@@ -1627,27 +1609,33 @@
|
|||||||
let progress = 0;
|
let progress = 0;
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
progress += 10;
|
progress += 10;
|
||||||
document.getElementById('copyProgressBar').style.width = progress + '%';
|
document.getElementById('syncProgressBar').style.width = progress + '%';
|
||||||
document.getElementById('copyPercent').textContent = progress + '%';
|
document.getElementById('syncPercent').textContent = progress + '%';
|
||||||
|
|
||||||
if (progress >= 100) {
|
if (progress >= 100) {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
|
|
||||||
let statusText = `✅ Задача скопирована! Новый ID: ${data.data.new_task_id}`;
|
let statusText = `✅ Задача синхронизирована!`;
|
||||||
|
|
||||||
if (data.data.assignees && data.data.assignees !== 'не изменены') {
|
if (data.data.sync_type === 'created') {
|
||||||
if (Array.isArray(data.data.assignees)) {
|
statusText += `<br>📋 Создана новая задача в целевой системе. ID: ${data.data.target_task_id}`;
|
||||||
statusText += `<br>👥 Исполнители: ${data.data.assignees.join(', ')}`;
|
} else if (data.data.sync_type === 'updated') {
|
||||||
}
|
statusText += `<br>🔄 Обновлена существующая задача в целевой системе. ID: ${data.data.target_task_id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.data.copied_files && data.data.copied_files.length > 0) {
|
statusText += `<br>👥 Исполнители: сохранены как в исходной задаче`;
|
||||||
const successCount = data.data.copied_files.filter(f => f.success).length;
|
|
||||||
const failCount = data.data.copied_files.filter(f => !f.success).length;
|
if (data.data.assignees && data.data.assignees.length > 0) {
|
||||||
statusText += `<br>📁 Файлы: ${successCount} скопировано, ${failCount} ошибок`;
|
statusText += `<br>👤 Количество исполнителей: ${data.data.assignees.length}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.data.synced_files && data.data.synced_files.length > 0) {
|
||||||
|
const successCount = data.data.synced_files.filter(f => f.success).length;
|
||||||
|
const failCount = data.data.synced_files.filter(f => !f.success).length;
|
||||||
|
statusText += `<br>📁 Файлы: ${successCount} синхронизировано, ${failCount} ошибок`;
|
||||||
|
|
||||||
if (failCount > 0) {
|
if (failCount > 0) {
|
||||||
const errors = data.data.copied_files
|
const errors = data.data.synced_files
|
||||||
.filter(f => !f.success)
|
.filter(f => !f.success)
|
||||||
.map(f => f.original_name)
|
.map(f => f.original_name)
|
||||||
.join(', ');
|
.join(', ');
|
||||||
@@ -1655,24 +1643,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('copyStatus').innerHTML = statusText;
|
if (data.data.warnings && data.data.warnings.length > 0) {
|
||||||
|
statusText += `<br><small style="color: #f39c12;">⚠️ ${data.data.warnings.join('; ')}</small>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('syncStatus').innerHTML = statusText;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
closeCopyModal();
|
closeSyncModal();
|
||||||
showAlert(`Задача скопирована в ${data.data.target_service}`, 'success');
|
showAlert(`Задача синхронизирована с ${data.data.target_service}`, 'success');
|
||||||
|
loadTasks();
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
}, 200);
|
}, 200);
|
||||||
} else {
|
} else {
|
||||||
showAlert(data.error || 'Ошибка копирования задачи', 'danger');
|
showAlert(data.error || 'Ошибка синхронизации задачи', 'danger');
|
||||||
document.getElementById('copyProgress').style.display = 'none';
|
document.getElementById('syncProgress').style.display = 'none';
|
||||||
document.getElementById('copyBtn').disabled = false;
|
document.getElementById('syncBtn').disabled = false;
|
||||||
|
document.getElementById('syncStatus').innerHTML = '';
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка копирования:', error);
|
console.error('Ошибка синхронизации:', error);
|
||||||
showAlert('Ошибка при копировании задачи', 'danger');
|
showAlert('Ошибка при синхронизации задачи', 'danger');
|
||||||
document.getElementById('copyProgress').style.display = 'none';
|
document.getElementById('syncProgress').style.display = 'none';
|
||||||
document.getElementById('copyBtn').disabled = false;
|
document.getElementById('syncBtn').disabled = false;
|
||||||
|
document.getElementById('syncStatus').innerHTML = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user