Files
2025-12-23 01:12:10 +03:00

630 lines
23 KiB
JavaScript

// Глобальные переменные
let currentRequestId = null;
let requestsData = [];
// Инициализация при загрузке страницы
document.addEventListener('DOMContentLoaded', function () {
loadRequests();
updateStatusIndicators();
addQueryParam();
addPayloadParam();
});
// Обновление индикаторов статуса
function updateStatusIndicators() {
const urlCheck = document.getElementById('urlCheck');
const apiKeyCheck = document.getElementById('apiKeyCheck');
const saveServerUrlButton = document.getElementById('saveServerUrlButton');
const uploadApiKeyButton = document.getElementById('uploadApiKeyButton');
// URL индикатор
if (pageData && pageData.url) {
urlCheck.innerHTML = '<span class="badge bg-success"><i class="bi bi-check-circle me-1"></i>Настроен</span>';
saveServerUrlButton.innerHTML = '<i class="bi bi-arrow-repeat me-2"></i>Обновить URL';
saveServerUrlButton.classList.remove('btn-success');
saveServerUrlButton.classList.add('btn-outline-success');
} else {
urlCheck.innerHTML = '<span class="badge bg-warning"><i class="bi bi-exclamation-triangle me-1"></i>Не настроен</span>';
saveServerUrlButton.innerHTML = '<i class="bi bi-save me-2"></i>Сохранить URL';
saveServerUrlButton.classList.remove('btn-outline-success');
saveServerUrlButton.classList.add('btn-success');
}
// API Key индикатор
if (pageData && pageData.apiKey) {
apiKeyCheck.innerHTML = '<span class="badge bg-success"><i class="bi bi-check-circle me-1"></i>Загружен</span>';
uploadApiKeyButton.innerHTML = '<i class="bi bi-arrow-repeat me-2"></i>Обновить ключ';
uploadApiKeyButton.classList.remove('btn-primary');
uploadApiKeyButton.classList.add('btn-outline-primary');
} else {
apiKeyCheck.innerHTML = '<span class="badge bg-warning"><i class="bi bi-exclamation-triangle me-1"></i>Не загружен</span>';
uploadApiKeyButton.innerHTML = '<i class="bi bi-upload me-2"></i>Загрузить API ключ';
uploadApiKeyButton.classList.remove('btn-outline-primary');
uploadApiKeyButton.classList.add('btn-primary');
}
}
// Сохранение URL сервера
async function saveServerUrl() {
const serverUrl = document.getElementById('server_url').value.trim();
if (!serverUrl) {
showAlert('warning', 'Введите URL сервера');
return;
}
try {
const response = await fetch('/api/medods', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: serverUrl })
});
if (response.ok) {
window.pageData = window.pageData || {};
window.pageData.url = serverUrl;
showAlert('success', 'URL сервера сохранен!');
updateStatusIndicators();
} else {
const error = await response.text();
showAlert('danger', 'Ошибка: ' + error);
}
} catch (error) {
console.error('Ошибка:', error);
showAlert('danger', 'Ошибка сохранения!');
}
}
// Загрузка API ключа
async function uploadApiKey() {
const fileInput = document.getElementById('api_key_file');
const file = fileInput.files[0];
if (!file) {
showAlert('warning', 'Выберите CSV файл');
return;
}
try {
const text = await file.text();
const lines = text.trim().split('\n');
if (lines.length < 2) {
showAlert('warning', 'Файл должен содержать минимум 2 строки');
return;
}
const headers = lines[0].split(';').map(h => h.trim());
if (!headers.includes('identity') || !headers.includes('secretKey')) {
showAlert('warning', 'Файл должен содержать колонки: identity и secretKey');
return;
}
const keyInfo = lines[1].split(';').map(h => h.trim());
const apiKey = {};
for (let i = 0; i < headers.length; i++) {
apiKey[headers[i]] = keyInfo[i];
}
const response = await fetch('/api/medods', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ apiKey })
});
if (response.ok) {
window.pageData = window.pageData || {};
window.pageData.apiKey = apiKey;
showAlert('success', 'API ключ загружен!');
fileInput.value = '';
updateStatusIndicators();
} else {
const error = await response.text();
showAlert('danger', 'Ошибка: ' + error);
}
} catch (error) {
console.error('Ошибка:', error);
showAlert('danger', 'Ошибка загрузки файла!');
}
}
// Загрузка списка запросов
async function loadRequests() {
try {
const response = await fetch('/api/requests');
const data = await response.json();
requestsData = data.requests ? data.requests : [];
renderRequestsList();
} catch (error) {
console.error('Ошибка загрузки запросов:', error);
showAlert('danger', 'Ошибка загрузки запросов');
}
}
// Отображение списка запросов
function renderRequestsList() {
const container = document.getElementById('requestsList');
if (requestsData.length === 0) {
container.innerHTML = `
<div class="alert alert-info text-center">
<i class="bi bi-info-circle me-2"></i>
Нет сохраненных запросов<br>
<small>Нажмите "Новый запрос" для создания</small>
</div>
`;
return;
}
let html = '<div class="list-group list-group-flush">';
requestsData.forEach(request => {
const methodClass = getMethodClass(request.method);
const isActive = currentRequestId === request.id;
html += `
<div class="list-group-item request-item ${isActive ? 'active' : ''}"
onclick="editRequest(${request.id})">
<div class="d-flex justify-content-between align-items-start">
<div class="flex-grow-1 me-3">
<div class="d-flex align-items-center mb-1">
<span class="badge ${methodClass} method-badge me-2">${request.method}</span>
<strong class="text-dark">${request.title}</strong>
</div>
<small class="text-muted d-block">${request.url_path}</small>
</div>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-success btn-action"
onclick="event.stopPropagation(); executeRequest(${request.id})"
title="Запустить запрос">
<i class="bi bi-play"></i>
</button>
<button class="btn btn-outline-danger btn-action"
onclick="event.stopPropagation(); deleteRequest(${request.id})"
title="Удалить запрос">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
</div>
`;
});
html += '</div>';
container.innerHTML = html;
}
// Получение класса для метода
function getMethodClass(method) {
const classes = {
'GET': 'bg-primary',
'POST': 'bg-success',
'PUT': 'bg-warning text-dark',
'DELETE': 'bg-danger',
'PATCH': 'bg-info'
};
return classes[method] || 'bg-secondary';
}
// Создание нового запроса
function newRequest() {
resetForm();
document.getElementById('editorTitle').textContent = 'Создание нового запроса';
document.getElementById('saveRequestButton').innerHTML = '<i class="bi bi-save me-1"></i>Сохранить запрос';
document.getElementById('executeButton').disabled = true;
currentRequestId = null;
updateActiveItem();
// Скролл к редактору
document.querySelector('.col-md-8').scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// Редактирование запроса
function editRequest(id) {
const request = requestsData.find(r => r.id === id);
if (!request) return;
currentRequestId = id;
document.getElementById('editorTitle').textContent = `Редактирование: ${request.title}`;
document.getElementById('saveRequestButton').innerHTML = '<i class="bi bi-save me-1"></i>Обновить запрос';
document.getElementById('executeButton').disabled = false;
// Заполняем поля формы
document.getElementById('requestId').value = request.id;
document.getElementById('title').value = request.title;
document.getElementById('method').value = request.method;
document.getElementById('url_path').value = request.url_path;
// Очищаем и заполняем query параметры
document.getElementById('queryParamsContainer').innerHTML = '';
if (request.query_params && typeof request.query_params === 'object' && Object.keys(request.query_params).length > 0) {
Object.entries(request.query_params).forEach(([key, value]) => {
addQueryParam(key, value);
});
} else {
addQueryParam();
}
// Очищаем и заполняем payload параметры
document.getElementById('payloadParamsContainer').innerHTML = '';
if (request.payload && typeof request.payload === 'object' && Object.keys(request.payload).length > 0) {
Object.entries(request.payload).forEach(([key, value]) => {
addPayloadParam(key, typeof value === 'object' ? JSON.stringify(value) : value);
});
} else {
addPayloadParam();
}
// Даты создания и обновления
document.getElementById('timestampDiv').classList.remove('d-none');
document.getElementById('createdAt').textContent = request.created_at;
document.getElementById('updatedAt').textContent = request.updated_at;
updateActiveItem();
// Скролл к редактору
document.querySelector('.col-md-8').scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// Обновление активного элемента в списке
function updateActiveItem() {
document.querySelectorAll('.request-item').forEach(item => {
item.classList.remove('active');
});
if (currentRequestId) {
const activeItem = document.querySelector(`[onclick="editRequest(${currentRequestId})"]`);
if (activeItem) {
activeItem.classList.add('active');
}
}
}
// Добавление параметра Query
function addQueryParam(key = '', value = '') {
const container = document.getElementById('queryParamsContainer');
const paramId = Date.now() + Math.random();
const html = `
<div class="param-row" id="param-${paramId}">
<div class="row g-2 align-items-center">
<div class="col-md-5">
<input type="text" class="form-control param-key"
placeholder="Ключ параметра" value="${key}">
</div>
<div class="col-md-5">
<input type="text" class="form-control param-value"
placeholder="Значение" value="${value}">
</div>
<div class="col-md-2">
<button type="button" class="btn btn-sm btn-outline-danger w-100"
onclick="document.getElementById('param-${paramId}').remove()">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', html);
}
// Добавление параметра Payload
function addPayloadParam(key = '', value = '') {
const container = document.getElementById('payloadParamsContainer');
const paramId = Date.now() + Math.random();
const html = `
<div class="param-row" id="payload-${paramId}">
<div class="row g-2 align-items-center">
<div class="col-md-5">
<input type="text" class="form-control param-key"
placeholder="Ключ параметра" value="${key}">
</div>
<div class="col-md-5">
<input type="text" class="form-control param-value"
placeholder="Значение" value="${value}">
</div>
<div class="col-md-2">
<button type="button" class="btn btn-sm btn-outline-danger w-100"
onclick="document.getElementById('payload-${paramId}').remove()">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', html);
}
// Сброс формы
function resetForm() {
document.getElementById('requestForm').reset();
document.getElementById('requestId').value = '';
document.getElementById('queryParamsContainer').innerHTML = '';
document.getElementById('payloadParamsContainer').innerHTML = '';
addQueryParam();
addPayloadParam();
currentRequestId = null;
document.getElementById('editorTitle').textContent = 'Создание нового запроса';
document.getElementById('saveRequestButton').innerHTML = '<i class="bi bi-save me-1"></i>Сохранить запрос';
document.getElementById('executeButton').disabled = true;
updateActiveItem();
}
// Сохранение запроса
async function saveRequest() {
const id = document.getElementById('requestId').value;
const title = document.getElementById('title').value.trim();
const method = document.getElementById('method').value;
const url_path = document.getElementById('url_path').value.trim();
if (!title || !method || !url_path) {
showAlert('warning', 'Заполните все обязательные поля');
return;
}
// Собираем query параметры
const query_params = {};
document.querySelectorAll('#queryParamsContainer .param-row').forEach(row => {
const key = row.querySelector('.param-key').value.trim();
const value = row.querySelector('.param-value').value.trim();
if (key) query_params[key] = value;
});
// Собираем payload параметры
const payload = {};
document.querySelectorAll('#payloadParamsContainer .param-row').forEach(row => {
const key = row.querySelector('.param-key').value.trim();
const value = row.querySelector('.param-value').value.trim();
if (key) {
try {
payload[key] = JSON.parse(value);
} catch {
payload[key] = value;
}
}
});
const requestData = { title, method, url_path, query_params, payload };
if (id) requestData.id = parseInt(id);
try {
const response = await fetch('/api/requests', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestData)
});
if (response.ok) {
showAlert('success', 'Запрос успешно сохранен!');
await loadRequests();
if (id) editRequest(parseInt(id));
} else {
const error = await response.text();
showAlert('danger', 'Ошибка сохранения: ' + error);
}
} catch (error) {
console.error('Ошибка:', error);
showAlert('danger', 'Ошибка сохранения запроса!');
}
}
// Удаление запроса
async function deleteRequest(id) {
if (!confirm('Вы уверены, что хотите удалить этот запрос?')) return;
try {
const response = await fetch(`/api/requests/${id}`, {
method: 'DELETE'
});
if (response.ok) {
showAlert('success', 'Запрос удален!');
if (currentRequestId === id) resetForm();
await loadRequests();
} else {
const error = await response.text();
showAlert('danger', 'Ошибка удаления: ' + error);
}
} catch (error) {
console.error('Ошибка:', error);
showAlert('danger', 'Ошибка удаления запроса!');
}
}
// Выполнение текущего запроса
async function executeCurrentRequest() {
if (!currentRequestId) {
showAlert('warning', 'Сначала выберите или создайте запрос');
return;
}
await executeRequest(currentRequestId);
}
// Выполнение запроса по ID
async function executeRequest(id) {
try {
showLoader(true);
const response = await fetch('/api/requests', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: parseInt(id) })
});
const data = await response.json();
displayResponse(data);
showResponseSection();
} catch (error) {
console.error('Ошибка:', error);
displayResponse({ error: error.message });
showResponseSection();
} finally {
showLoader(false);
}
}
// Отображение ответа
function displayResponse(data) {
const container = document.getElementById('responseContainer');
container.innerHTML = '';
// Создаем пре для лучшего отображения
const pre = document.createElement('pre');
pre.className = 'response-pre';
pre.appendChild(formatJson(data, 0));
container.appendChild(pre);
window.lastResponse = data;
}
// Форматирование JSON
function formatJson(data, indent) {
const fragment = document.createDocumentFragment();
function format(value, depth) {
const indentStr = ' '.repeat(depth);
if (value === null) {
const span = document.createElement('span');
span.className = 'json-null';
span.textContent = 'null';
return span;
} else if (typeof value === 'boolean') {
const span = document.createElement('span');
span.className = 'json-boolean';
span.textContent = value.toString();
return span;
} else if (typeof value === 'number') {
const span = document.createElement('span');
span.className = 'json-number';
span.textContent = value;
return span;
} else if (typeof value === 'string') {
const span = document.createElement('span');
span.className = 'json-string';
span.textContent = JSON.stringify(value);
return span;
} else if (Array.isArray(value)) {
if (value.length === 0) {
return document.createTextNode('[]');
}
const div = document.createElement('div');
div.appendChild(document.createTextNode('['));
value.forEach((item, index) => {
const itemDiv = document.createElement('div');
itemDiv.style.marginLeft = '20px';
itemDiv.appendChild(format(item, depth + 1));
if (index < value.length - 1) {
itemDiv.appendChild(document.createTextNode(','));
}
div.appendChild(itemDiv);
});
div.appendChild(document.createTextNode(']'));
return div;
} else if (typeof value === 'object') {
const entries = Object.entries(value);
if (entries.length === 0) {
return document.createTextNode('{}');
}
const div = document.createElement('div');
div.appendChild(document.createTextNode('{'));
entries.forEach(([key, val], index) => {
const itemDiv = document.createElement('div');
itemDiv.style.marginLeft = '20px';
const keySpan = document.createElement('span');
keySpan.className = 'json-key';
keySpan.textContent = JSON.stringify(key) + ': ';
itemDiv.appendChild(keySpan);
itemDiv.appendChild(format(val, depth + 1));
if (index < entries.length - 1) {
itemDiv.appendChild(document.createTextNode(','));
}
div.appendChild(itemDiv);
});
div.appendChild(document.createTextNode('}'));
return div;
}
return document.createTextNode(String(value));
}
fragment.appendChild(format(data, 0));
return fragment;
}
// Показать раздел с ответом
function showResponseSection() {
const responseCard = document.getElementById('responseCard');
responseCard.style.display = 'block';
// Анимация появления
responseCard.classList.add('fade-in');
// Скролл к результату
setTimeout(() => {
responseCard.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}, 100);
}
// Переключение видимости ответа
function toggleResponse() {
const responseBody = document.getElementById('responseBody');
const toggleBtn = document.querySelector('#responseCard .bi-chevron-up');
if (responseBody.style.display === 'none') {
responseBody.style.display = 'block';
toggleBtn.classList.remove('bi-chevron-down');
toggleBtn.classList.add('bi-chevron-up');
} else {
responseBody.style.display = 'none';
toggleBtn.classList.remove('bi-chevron-up');
toggleBtn.classList.add('bi-chevron-down');
}
}
// Скачивание ответа
function downloadResponse() {
if (!window.lastResponse) {
showAlert('warning', 'Нет данных для скачивания');
return;
}
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const filename = `medods_response_${timestamp}.json`;
const jsonStr = JSON.stringify(window.lastResponse, null, 2);
const blob = new Blob([jsonStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function showLoader(show) {
const executeButton = document.getElementById('executeButton');
if (show) {
executeButton.innerHTML = '<i class="bi bi-hourglass-split me-1"></i>Выполняется...';
executeButton.disabled = true;
} else {
executeButton.innerHTML = '<i class="bi bi-play-fill me-1"></i>Запустить';
executeButton.disabled = !currentRequestId;
}
}