базовые настройки
This commit is contained in:
+232
-678
@@ -1,138 +1,113 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Medods{% endblock %}
|
||||
|
||||
{% block styles %}
|
||||
<link rel="stylesheet" href="/static/css/medods.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<h3 class="mb-4">🔌 Medods API</h3>
|
||||
<!-- Заголовок -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h2 class="mb-1"><i class="bi bi-plug me-2"></i>Medods API</h2>
|
||||
<p class="text-muted mb-0">Управление подключением и запросами к Medods API</p>
|
||||
</div>
|
||||
<div class="badge bg-primary fs-6 px-3 py-2">
|
||||
<i class="bi bi-activity me-1"></i>API Control
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Настройка подключения -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0">Подключение к серверу</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="serverForm">
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-md-9">
|
||||
<label class="form-label">URL адрес сервера</label>
|
||||
<!-- Настройка подключения -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-lg-6 mb-4 mb-lg-0">
|
||||
<div class="card h-100 url-status">
|
||||
<div
|
||||
class="card-header bg-primary bg-opacity-10 border-primary d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0"><i class="bi bi-server me-2"></i>Настройка сервера</h5>
|
||||
<span id="urlCheck" class="status-indicator">
|
||||
{% if data.url %}
|
||||
<span class="badge bg-success"><i class="bi bi-check-circle me-1"></i>Настроен</span>
|
||||
{% else %}
|
||||
<span class="badge bg-warning"><i class="bi bi-exclamation-triangle me-1"></i>Не настроен</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">URL адрес сервера</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light"><i class="bi bi-link-45deg"></i></span>
|
||||
<input type="text" class="form-control" id="server_url" placeholder="https://api.example.com"
|
||||
required>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button type="button" class="btn btn-success w-100" onclick="saveServerUrl()">
|
||||
💾 Сохранить
|
||||
</button>
|
||||
value="{{ data.url or '' }}">
|
||||
</div>
|
||||
<div class="form-text small">Введите полный URL вашего сервера Medods API</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<form id="apiKeyForm" enctype="multipart/form-data">
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-md-9">
|
||||
<label class="form-label">Загрузка API ключа</label>
|
||||
<input type="file" class="form-control" id="api_key_file" accept=".csv" required>
|
||||
<div class="form-text">
|
||||
Файл формата CSV с колонками: identity;secretKey
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button type="button" class="btn btn-primary w-100" onclick="uploadApiKey()">
|
||||
📤 Загрузить apiKey.csv
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<button type="button" class="btn btn-success w-100" onclick="saveServerUrl()" id="saveServerUrlButton">
|
||||
{% if data.url %}
|
||||
<i class="bi bi-arrow-repeat me-2"></i>Обновить URL
|
||||
{% else %}
|
||||
<i class="bi bi-save me-2"></i>Сохранить URL
|
||||
{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Аккордеон с запросами -->
|
||||
<div class="accordion mb-4" id="requestsAccordion">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#requestsCollapse" aria-expanded="false" aria-controls="requestsCollapse">
|
||||
⚙️ Настроенные запросы
|
||||
</button>
|
||||
</h2>
|
||||
<div id="requestsCollapse" class="accordion-collapse collapse" data-bs-parent="#requestsAccordion">
|
||||
<div class="accordion-body">
|
||||
<!-- Форма создания/редактирования запроса -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Добавить/Редактировать запрос</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="requestForm">
|
||||
<input type="hidden" id="requestId">
|
||||
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Название запроса</label>
|
||||
<input type="text" class="form-control" id="title"
|
||||
placeholder="Получить список пользователей" required>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">HTTP метод</label>
|
||||
<select class="form-select" id="method" required>
|
||||
<option value="GET">GET</option>
|
||||
<option value="POST">POST</option>
|
||||
<option value="PUT">PUT</option>
|
||||
<option value="DELETE">DELETE</option>
|
||||
<option value="PATCH">PATCH</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">URL путь</label>
|
||||
<input type="text" class="form-control" id="url_path" placeholder="/users"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Динамические параметры Query -->
|
||||
<div class="mb-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<label class="form-label fw-bold">Параметры Query</label>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary"
|
||||
onclick="addQueryParam()">
|
||||
➕ Добавить параметр
|
||||
</button>
|
||||
</div>
|
||||
<div id="queryParamsContainer">
|
||||
<!-- Поля будут добавляться динамически -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Динамические параметры Payload -->
|
||||
<div class="mb-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<label class="form-label fw-bold">Параметры Payload (для POST/PUT/PATCH)</label>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary"
|
||||
onclick="addPayloadParam()">
|
||||
➕ Добавить параметр
|
||||
</button>
|
||||
</div>
|
||||
<div id="payloadParamsContainer">
|
||||
<!-- Поля будут добавляться динамически -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-success" onclick="saveRequest()">
|
||||
💾 Сохранить запрос
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="resetForm()">
|
||||
🆕 Новый запрос
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100 api-status">
|
||||
<div
|
||||
class="card-header bg-success bg-opacity-10 border-success d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0"><i class="bi bi-key me-2"></i>API ключ</h5>
|
||||
<span id="apiKeyCheck" class="status-indicator">
|
||||
{% if data.apiKey %}
|
||||
<span class="badge bg-success"><i class="bi bi-check-circle me-1"></i>Загружен</span>
|
||||
{% else %}
|
||||
<span class="badge bg-warning"><i class="bi bi-exclamation-triangle me-1"></i>Не загружен</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">CSV файл с ключами</label>
|
||||
<input type="file" class="form-control" id="api_key_file" accept=".csv">
|
||||
<div class="form-text small">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
Файл должен содержать колонки: <code>identity;secretKey</code>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary w-100" onclick="uploadApiKey()" id="uploadApiKeyButton">
|
||||
{% if data.apiKey %}
|
||||
<i class="bi bi-arrow-repeat me-2"></i>Обновить ключ
|
||||
{% else %}
|
||||
<i class="bi bi-upload me-2"></i>Загрузить API ключ
|
||||
{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Список существующих запросов -->
|
||||
<!-- Основной раздел: Запросы -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-dark bg-opacity-10">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h4 class="mb-0"><i class="bi bi-send me-2"></i>Управление запросами</h4>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="newRequest()">
|
||||
<i class="bi bi-plus-circle me-1"></i>Новый запрос
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="row g-0">
|
||||
<!-- Левая колонка: Список запросов -->
|
||||
<div class="col-md-4 border-end">
|
||||
<div class="p-3 border-bottom bg-light">
|
||||
<h5 class="mb-0"><i class="bi bi-list-ul me-2"></i>Сохраненные запросы</h5>
|
||||
</div>
|
||||
<div class="requests-list p-3">
|
||||
<div id="requestsList">
|
||||
<div class="text-center">
|
||||
<!-- Список запросов будет загружен здесь -->
|
||||
<div class="text-center py-4">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Загрузка...</span>
|
||||
</div>
|
||||
@@ -140,575 +115,154 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Раздел выполнения запросов -->
|
||||
<div class="card">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h5 class="mb-0">📥 Выполнение запроса</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="executeForm">
|
||||
<div class="row g-3 align-items-end mb-4">
|
||||
<div class="col-md-10">
|
||||
<label class="form-label">Выберите запрос для выполнения</label>
|
||||
<select class="form-select" id="requestSelect" required>
|
||||
<option value="" disabled selected>Выберите запрос...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="button" class="btn btn-warning w-100" onclick="executeRequest()">
|
||||
🚀 Отправить запрос
|
||||
</button>
|
||||
</div>
|
||||
<!-- Правая колонка: Редактор запроса -->
|
||||
<div class="col-md-8">
|
||||
<div class="p-3 border-bottom bg-light">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-pencil-square me-2"></i>
|
||||
<span id="editorTitle">Создание нового запроса</span>
|
||||
</h5>
|
||||
</div>
|
||||
</form>
|
||||
<div class="p-4">
|
||||
<form id="requestForm">
|
||||
<input type="hidden" id="requestId">
|
||||
|
||||
<!-- Окно с результатом -->
|
||||
<div id="responseSection" style="display: none;">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h6>Результат выполнения:</h6>
|
||||
<button type="button" class="btn btn-sm btn-outline-success" onclick="downloadResponse()">
|
||||
⬇️ Скачать JSON
|
||||
</button>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div id="responseContainer" class="response-container"
|
||||
style="max-height: 500px; overflow-y: auto;">
|
||||
<!-- Ответ будет отображен здесь -->
|
||||
<!-- Основные поля -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold">Название запроса</label>
|
||||
<input type="text" class="form-control form-control-lg" id="title"
|
||||
placeholder="Например: Получить список пользователей" required>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-semibold">HTTP метод</label>
|
||||
<select class="form-select form-select-lg" id="method" required>
|
||||
<option value="GET">GET</option>
|
||||
<option value="POST">POST</option>
|
||||
<option value="PUT">PUT</option>
|
||||
<option value="DELETE">DELETE</option>
|
||||
<option value="PATCH">PATCH</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-semibold">URL путь</label>
|
||||
<input type="text" class="form-control form-control-lg" id="url_path"
|
||||
placeholder="/users" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Query параметры -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-body-secondary">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="fw-semibold">
|
||||
<i class="bi bi-filter me-2"></i>Query параметры
|
||||
</span>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary"
|
||||
onclick="addQueryParam()">
|
||||
<i class="bi bi-plus me-1"></i>Добавить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="queryParamsContainer" class="mb-2">
|
||||
<!-- Параметры будут добавляться сюда -->
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary"
|
||||
onclick="addQueryParam()">
|
||||
<i class="bi bi-plus-circle me-1"></i>Добавить параметр Query
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Payload параметры -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-body-secondary">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="fw-semibold">
|
||||
<i class="bi bi-code me-2"></i>Body параметры
|
||||
</span>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary"
|
||||
onclick="addPayloadParam()">
|
||||
<i class="bi bi-plus me-1"></i>Добавить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="payloadParamsContainer" class="mb-2">
|
||||
<!-- Параметры будут добавляться сюда -->
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary"
|
||||
onclick="addPayloadParam()">
|
||||
<i class="bi bi-plus-circle me-1"></i>Добавить параметр Body
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center align-items-center gap-2 text-muted d-none"
|
||||
id="timestampDiv">
|
||||
<div>
|
||||
<small>Создано: </small><span class="small" id="createdAt"></span>
|
||||
</div>
|
||||
<div>
|
||||
<small>Последнее обновление: </small><span class="small" id="updatedAt"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Кнопки действий -->
|
||||
<div class="d-flex gap-3 justify-content-end pt-3 border-top">
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="resetForm()">
|
||||
<i class="bi bi-x-lg me-1"></i>Сбросить
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveRequest()"
|
||||
id="saveRequestButton">
|
||||
<i class="bi bi-save me-1"></i>Сохранить запрос
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning" onclick="executeCurrentRequest()"
|
||||
id="executeButton" disabled>
|
||||
<i class="bi bi-play-fill me-1"></i>Запустить
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.response-container {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
background-color: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
<!-- Раздел с результатом -->
|
||||
<div class="card fade-in" id="responseCard" style="display: none;">
|
||||
<div class="card-header bg-info bg-opacity-10">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0"><i class="bi bi-arrow-return-right me-2"></i>Результат выполнения запроса</h5>
|
||||
<div>
|
||||
<button type="button" class="btn btn-sm btn-outline-success me-2" onclick="downloadResponse()">
|
||||
<i class="bi bi-download me-1"></i>Скачать JSON
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="toggleResponse()">
|
||||
<i class="bi bi-chevron-up"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body" id="responseBody">
|
||||
<div id="responseContainer" class="response-container">
|
||||
<!-- Ответ будет отображаться здесь -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
.json-key {
|
||||
color: #92278f;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.json-string {
|
||||
color: #3ab54a;
|
||||
}
|
||||
|
||||
.json-number {
|
||||
color: #25aae2;
|
||||
}
|
||||
|
||||
.json-boolean {
|
||||
color: #f98280;
|
||||
}
|
||||
|
||||
.json-null {
|
||||
color: #f1592a;
|
||||
}
|
||||
|
||||
.param-row {
|
||||
margin-bottom: 8px;
|
||||
padding: 8px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid #0d6efd;
|
||||
}
|
||||
|
||||
.accordion-button:not(.collapsed) {
|
||||
background-color: #e7f1ff;
|
||||
color: #0c63e4;
|
||||
}
|
||||
</style>
|
||||
<!-- Всплывающие уведомления -->
|
||||
<div id="alertContainer" style="position: fixed; top: 80px; right: 20px; z-index: 1050;"></div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Сохранение URL сервера
|
||||
async function saveServerUrl() {
|
||||
const serverUrl = document.getElementById('server_url').value;
|
||||
|
||||
if (!serverUrl) {
|
||||
alert('Пожалуйста, введите URL сервера');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/settings/medods_url', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ url: serverUrl })
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('URL сервера сохранен!');
|
||||
} else {
|
||||
const error = await response.text();
|
||||
alert('Ошибка сохранения: ' + error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка:', error);
|
||||
alert('Ошибка сохранения!');
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка API ключа
|
||||
async function uploadApiKey() {
|
||||
const fileInput = document.getElementById('api_key_file');
|
||||
const file = fileInput.files[0];
|
||||
|
||||
if (!file) {
|
||||
alert('Пожалуйста, выберите файл');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const text = await file.text();
|
||||
const lines = text.split('\n');
|
||||
|
||||
if (lines.length < 2) {
|
||||
alert('Файл должен содержать заголовок и данные');
|
||||
return;
|
||||
}
|
||||
|
||||
const headers = lines[0].split(';').map(h => h.trim());
|
||||
if (!headers.includes('identity') || !headers.includes('secretKey')) {
|
||||
alert('Файл должен содержать колонки: identity и secretKey');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {};
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
if (lines[i].trim()) {
|
||||
const values = lines[i].split(';').map(v => v.trim());
|
||||
if (values.length >= 2) {
|
||||
data[values[0]] = values[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch('/settings/medods_apikey', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('API ключ загружен!');
|
||||
fileInput.value = '';
|
||||
} else {
|
||||
const error = await response.text();
|
||||
alert('Ошибка загрузки: ' + error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка:', error);
|
||||
alert('Ошибка загрузки файла!');
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка запросов при раскрытии аккордеона
|
||||
document.getElementById('requestsAccordion').addEventListener('show.bs.collapse', function () {
|
||||
loadRequests();
|
||||
});
|
||||
|
||||
// Загрузка списка запросов
|
||||
async function loadRequests() {
|
||||
try {
|
||||
const response = await fetch('/settings/requests');
|
||||
const requests = await response.json();
|
||||
|
||||
// Обновляем выпадающий список для выполнения
|
||||
const select = document.getElementById('requestSelect');
|
||||
select.innerHTML = '<option value="" disabled selected>Выберите запрос...</option>';
|
||||
requests.forEach(req => {
|
||||
const option = document.createElement('option');
|
||||
option.value = req.id;
|
||||
option.textContent = `${req.id} - ${req.title}`;
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
// Отображаем список запросов
|
||||
const container = document.getElementById('requestsList');
|
||||
container.innerHTML = '';
|
||||
|
||||
if (requests.length === 0) {
|
||||
container.innerHTML = '<div class="alert alert-info">Нет настроенных запросов</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
requests.forEach(request => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card mb-2';
|
||||
card.innerHTML = `
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="mb-1">${request.title}</h6>
|
||||
<small class="text-muted">
|
||||
${request.method} ${request.url_path}
|
||||
</small>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="editRequest(${request.id})">
|
||||
✏️
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="deleteRequest(${request.id})">
|
||||
🗑️
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(card);
|
||||
});
|
||||
|
||||
// Сохраняем запросы для использования
|
||||
window.requestsData = requests;
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки запросов:', error);
|
||||
document.getElementById('requestsList').innerHTML =
|
||||
'<div class="alert alert-danger">Ошибка загрузки запросов</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Добавление параметра Query
|
||||
function addQueryParam(key = '', value = '') {
|
||||
const container = document.getElementById('queryParamsContainer');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'row g-2 param-row';
|
||||
div.innerHTML = `
|
||||
<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="this.parentElement.parentElement.remove()">
|
||||
🗑️
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
}
|
||||
|
||||
// Добавление параметра Payload
|
||||
function addPayloadParam(key = '', value = '') {
|
||||
const container = document.getElementById('payloadParamsContainer');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'row g-2 param-row';
|
||||
div.innerHTML = `
|
||||
<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="this.parentElement.parentElement.remove()">
|
||||
🗑️
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
}
|
||||
|
||||
// Редактирование запроса
|
||||
function editRequest(id) {
|
||||
const request = window.requestsData.find(r => r.id === id);
|
||||
if (!request) return;
|
||||
|
||||
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;
|
||||
|
||||
// Очищаем контейнеры параметров
|
||||
document.getElementById('queryParamsContainer').innerHTML = '';
|
||||
document.getElementById('payloadParamsContainer').innerHTML = '';
|
||||
|
||||
// Добавляем query параметры
|
||||
if (request.query && typeof request.query === 'object') {
|
||||
Object.entries(request.query).forEach(([key, value]) => {
|
||||
addQueryParam(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
// Добавляем payload параметры
|
||||
if (request.payload && typeof request.payload === 'object') {
|
||||
Object.entries(request.payload).forEach(([key, value]) => {
|
||||
addPayloadParam(key, typeof value === 'object' ? JSON.stringify(value) : value);
|
||||
});
|
||||
}
|
||||
|
||||
// Прокручиваем к форме
|
||||
document.getElementById('requestForm').scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
// Сброс формы
|
||||
function resetForm() {
|
||||
document.getElementById('requestForm').reset();
|
||||
document.getElementById('requestId').value = '';
|
||||
document.getElementById('queryParamsContainer').innerHTML = '';
|
||||
document.getElementById('payloadParamsContainer').innerHTML = '';
|
||||
}
|
||||
|
||||
// Сохранение запроса
|
||||
async function saveRequest() {
|
||||
const id = document.getElementById('requestId').value;
|
||||
const title = document.getElementById('title').value;
|
||||
const method = document.getElementById('method').value;
|
||||
const url_path = document.getElementById('url_path').value;
|
||||
|
||||
if (!title || !method || !url_path) {
|
||||
alert('Пожалуйста, заполните все обязательные поля');
|
||||
return;
|
||||
}
|
||||
|
||||
// Собираем query параметры
|
||||
const query = {};
|
||||
document.querySelectorAll('#queryParamsContainer .param-row').forEach(row => {
|
||||
const key = row.querySelector('.param-key').value;
|
||||
const value = row.querySelector('.param-value').value;
|
||||
if (key) query[key] = value;
|
||||
});
|
||||
|
||||
// Собираем payload параметры
|
||||
const payload = {};
|
||||
document.querySelectorAll('#payloadParamsContainer .param-row').forEach(row => {
|
||||
const key = row.querySelector('.param-key').value;
|
||||
const value = row.querySelector('.param-value').value;
|
||||
if (key) {
|
||||
// Пробуем парсить JSON, если это объект
|
||||
try {
|
||||
payload[key] = JSON.parse(value);
|
||||
} catch {
|
||||
payload[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const requestData = {
|
||||
title,
|
||||
method,
|
||||
url_path,
|
||||
query,
|
||||
payload
|
||||
};
|
||||
|
||||
if (id) {
|
||||
requestData.id = parseInt(id);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/settings/requests', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(requestData)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('Запрос сохранен!');
|
||||
resetForm();
|
||||
loadRequests();
|
||||
} else {
|
||||
const error = await response.text();
|
||||
alert('Ошибка сохранения: ' + error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка:', error);
|
||||
alert('Ошибка сохранения!');
|
||||
}
|
||||
}
|
||||
|
||||
// Удаление запроса
|
||||
async function deleteRequest(id) {
|
||||
if (!confirm('Вы уверены, что хотите удалить этот запрос?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/settings/requests/${id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('Запрос удален!');
|
||||
loadRequests();
|
||||
} else {
|
||||
const error = await response.text();
|
||||
alert('Ошибка удаления: ' + error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка:', error);
|
||||
alert('Ошибка удаления!');
|
||||
}
|
||||
}
|
||||
|
||||
// Выполнение запроса
|
||||
async function executeRequest() {
|
||||
const select = document.getElementById('requestSelect');
|
||||
const requestId = select.value;
|
||||
|
||||
if (!requestId) {
|
||||
alert('Пожалуйста, выберите запрос');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/settings/requests', {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ id: parseInt(requestId) })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
displayResponse(data);
|
||||
document.getElementById('responseSection').style.display = 'block';
|
||||
|
||||
// Прокручиваем к результату
|
||||
document.getElementById('responseSection').scrollIntoView({ behavior: 'smooth' });
|
||||
} catch (error) {
|
||||
console.error('Ошибка:', error);
|
||||
displayResponse({ error: error.message });
|
||||
document.getElementById('responseSection').style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// Отображение ответа
|
||||
function displayResponse(data, container = document.getElementById('responseContainer'), level = 0) {
|
||||
container.innerHTML = '';
|
||||
|
||||
function formatValue(value, indent = 0) {
|
||||
const indentStr = ' '.repeat(indent);
|
||||
|
||||
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 = `"${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.paddingLeft = '20px';
|
||||
itemDiv.appendChild(formatValue(item, indent + 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.paddingLeft = '20px';
|
||||
|
||||
const keySpan = document.createElement('span');
|
||||
keySpan.className = 'json-key';
|
||||
keySpan.textContent = `"${key}": `;
|
||||
itemDiv.appendChild(keySpan);
|
||||
|
||||
itemDiv.appendChild(formatValue(val, indent + 1));
|
||||
|
||||
if (index < entries.length - 1) {
|
||||
itemDiv.appendChild(document.createTextNode(','));
|
||||
}
|
||||
|
||||
div.appendChild(itemDiv);
|
||||
});
|
||||
|
||||
div.appendChild(document.createTextNode('}'));
|
||||
return div;
|
||||
}
|
||||
|
||||
return document.createTextNode(String(value));
|
||||
}
|
||||
|
||||
container.appendChild(formatValue(data));
|
||||
window.lastResponse = data;
|
||||
}
|
||||
|
||||
// Скачивание ответа
|
||||
function downloadResponse() {
|
||||
if (!window.lastResponse) return;
|
||||
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const filename = `request_${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 setServerUrlInput(data) {
|
||||
const serverUrlInput = document.getElementById('server_url');
|
||||
if (serverUrlInput && data) {
|
||||
serverUrlInput.value = data.url;
|
||||
serverUrlInput.disabled = true;
|
||||
}
|
||||
}
|
||||
// Инициализация
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Добавляем примеры параметров
|
||||
addQueryParam();
|
||||
addPayloadParam();
|
||||
setServerUrlInput(pageData);
|
||||
});
|
||||
const pageData = {{ data | tojson }};
|
||||
</script>
|
||||
<script src="/static/js/medods.js"></script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user