базовые настройки
This commit is contained in:
@@ -1,8 +1,7 @@
|
|||||||
from flask import Flask, request, jsonify
|
from flask import Flask, request, jsonify
|
||||||
from config import Config
|
from config import Config
|
||||||
from db import MedodsAPI, db, VkPost
|
from db import VkAPI, db, MedodsAPI, ApiEndpoint
|
||||||
from scheduler import start_scheduler
|
from scheduler import start_scheduler
|
||||||
from token_utils import generate_token
|
|
||||||
from http_client import send_request
|
from http_client import send_request
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@@ -33,16 +32,28 @@ def index():
|
|||||||
return render_template("index.html")
|
return render_template("index.html")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/medods")
|
@app.route("/medods", methods=["GET"])
|
||||||
def medods():
|
def medods():
|
||||||
medods_api = MedodsAPI.query.first()
|
medods_api = MedodsAPI.query.first()
|
||||||
data = {"url": medods_api.url if medods_api else "none"}
|
data = {}
|
||||||
|
if medods_api:
|
||||||
|
apiKey = False
|
||||||
|
if medods_api.identity and medods_api.secretKey:
|
||||||
|
apiKey = True
|
||||||
|
data = {
|
||||||
|
"url": medods_api.url,
|
||||||
|
"apiKey": apiKey,
|
||||||
|
}
|
||||||
return render_template("medods.html", data=data)
|
return render_template("medods.html", data=data)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/vk")
|
@app.route("/vk")
|
||||||
def vk():
|
def vk():
|
||||||
return render_template("vk.html")
|
vkDB = VkAPI.query.first()
|
||||||
|
data = {}
|
||||||
|
if vkDB:
|
||||||
|
data = {"vk_settings": vkDB.toDict()}
|
||||||
|
return render_template("vk.html", data=data)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/posts")
|
@app.route("/posts")
|
||||||
@@ -57,66 +68,153 @@ def init():
|
|||||||
logger.info("Приложение запущено")
|
logger.info("Приложение запущено")
|
||||||
|
|
||||||
|
|
||||||
@app.route("/settings/medods_url", methods=["POST"])
|
@app.route("/settings/medods", methods=["POST"])
|
||||||
def medods_url():
|
def medods_url():
|
||||||
data = request.json
|
data = request.json
|
||||||
db.session.merge(MedodsAPI(url=data.get("url", "http://10.3.10.10/api/v2/")))
|
|
||||||
db.session.commit()
|
|
||||||
logger.info(data)
|
logger.info(data)
|
||||||
return jsonify({"status": "ok"})
|
apiKey = data.get("apiKey", None)
|
||||||
|
url = data.get("url", None)
|
||||||
|
if url is not None:
|
||||||
|
logger.info("Получен url")
|
||||||
|
try:
|
||||||
|
medodsRecord = MedodsAPI.query.first()
|
||||||
|
medodsRecord.url = url
|
||||||
|
db.session.commit()
|
||||||
|
logger.info("Обновлен url")
|
||||||
|
except Exception:
|
||||||
|
db.session.merge(MedodsAPI(url=url))
|
||||||
|
db.session.commit()
|
||||||
|
logger.info("Добавлен url")
|
||||||
|
|
||||||
|
if apiKey:
|
||||||
|
logger.info("Получены ключи")
|
||||||
|
try:
|
||||||
|
medodsRecord = MedodsAPI.query.first()
|
||||||
|
medodsRecord.identity = apiKey["identity"]
|
||||||
|
medodsRecord.secretKey = apiKey["secretKey"]
|
||||||
|
db.session.commit()
|
||||||
|
logger.info("Обновлены ключи")
|
||||||
|
except Exception:
|
||||||
|
db.session.merge(
|
||||||
|
MedodsAPI(identity=apiKey["identity"], secretKey=apiKey["secretKey"])
|
||||||
|
)
|
||||||
|
db.session.commit()
|
||||||
|
logger.info("Добавлены ключи")
|
||||||
|
return jsonify({"ok": True})
|
||||||
|
# return jsonify({"ok": False}), 400
|
||||||
|
|
||||||
|
|
||||||
@app.route("/settings/medods_apikey", methods=["POST"])
|
@app.route("/settings/requests", methods=["GET", "POST", "PATCH", "DELETE"])
|
||||||
def medods_apikey():
|
|
||||||
data = request.json
|
|
||||||
logger.info(data)
|
|
||||||
return jsonify({"status": "ok"})
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/settings/requests", methods=["GET", "POST", "PATCH"])
|
|
||||||
def get_requests():
|
def get_requests():
|
||||||
|
requestData = (
|
||||||
|
request.json if request.method in ["POST", "PATCH", "DELETE"] else None
|
||||||
|
)
|
||||||
match request.method:
|
match request.method:
|
||||||
|
case "DELETE":
|
||||||
|
logger.info("Удален запрос")
|
||||||
|
logger.info(requestData)
|
||||||
|
return jsonify({"status": "ok"})
|
||||||
case "POST":
|
case "POST":
|
||||||
logger.info("Добавлен новый запрос")
|
logger.info("Добавлен/обновлен запрос")
|
||||||
logger.info(request.json)
|
if "id" in requestData:
|
||||||
|
logger.info("Обновлен запрос")
|
||||||
|
try:
|
||||||
|
db.session.execute(
|
||||||
|
db.update(ApiEndpoint)
|
||||||
|
.where(ApiEndpoint.id == requestData["id"])
|
||||||
|
.values(
|
||||||
|
method=requestData.get("method"),
|
||||||
|
title=requestData.get("title"),
|
||||||
|
url_path=requestData.get("url_path"),
|
||||||
|
payload=requestData.get("payload", {}),
|
||||||
|
query_params=requestData.get("query_params", {}),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
db.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при обновлении запроса: {e}")
|
||||||
|
return jsonify({"status": "ok"})
|
||||||
|
else:
|
||||||
|
logger.info("Добавлен запрос")
|
||||||
|
try:
|
||||||
|
db.session.merge(ApiEndpoint(**requestData))
|
||||||
|
db.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при добавлении запроса: {e}")
|
||||||
return jsonify({"status": "ok"})
|
return jsonify({"status": "ok"})
|
||||||
|
|
||||||
case "PATCH":
|
case "PATCH":
|
||||||
logger.info("Обновлен запрос")
|
logger.info("Выполнен запрос")
|
||||||
logger.info(request.json)
|
requestParams = ApiEndpoint.query.filter_by(id=requestData["id"]).first()
|
||||||
return jsonify({"status": "ok"})
|
medodsDB = MedodsAPI.query.first()
|
||||||
|
baseUrl = medodsDB.url
|
||||||
|
response = send_request(
|
||||||
|
requestParams.method,
|
||||||
|
f"{baseUrl}{requestParams.url_path}",
|
||||||
|
requestParams.payload,
|
||||||
|
requestParams.query_params,
|
||||||
|
)
|
||||||
|
exitData = {}
|
||||||
|
try:
|
||||||
|
exitData = response.json()
|
||||||
|
except:
|
||||||
|
exitData = response.text
|
||||||
|
return jsonify(exitData)
|
||||||
|
|
||||||
case "GET":
|
case "GET":
|
||||||
logger.info("Получен список запросов")
|
logger.info("Получен список запросов")
|
||||||
|
|
||||||
|
requestsDB = ApiEndpoint.query.all()
|
||||||
|
requestsList = [r.toDict() for r in requestsDB]
|
||||||
|
logger.info(requestsList)
|
||||||
return jsonify(
|
return jsonify(
|
||||||
[
|
{
|
||||||
{
|
"status": "ok",
|
||||||
"id": 1,
|
"requests": requestsList,
|
||||||
"method": "GET",
|
}
|
||||||
"title": "Получить список пользователей",
|
|
||||||
"url_path": "/users",
|
|
||||||
"payload": {},
|
|
||||||
"query": {"limit": 10, "offset": 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"method": "POST",
|
|
||||||
"title": "Добавить пост в очередь",
|
|
||||||
"url_path": "/scheduler",
|
|
||||||
"payload": {
|
|
||||||
"text": "Текст поста",
|
|
||||||
"image": "path/to/image.jpg",
|
|
||||||
},
|
|
||||||
"query": {},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
logger.error("Неверный метод запроса")
|
logger.error("Неверный метод запроса")
|
||||||
return jsonify({"status": "error"})
|
return jsonify({"status": "error"}), 405
|
||||||
|
|
||||||
|
|
||||||
@app.route("/token", methods=["GET"])
|
@app.route("/settings/vk", methods=["POST"])
|
||||||
def token():
|
def settings_vk():
|
||||||
return jsonify({"token": generate_token()})
|
requestData = request.json
|
||||||
|
logger.info(requestData)
|
||||||
|
if "id" in requestData:
|
||||||
|
logger.info("Обновлен запрос")
|
||||||
|
try:
|
||||||
|
db.session.execute(
|
||||||
|
db.update(VkAPI)
|
||||||
|
.where(VkAPI.id == requestData["id"])
|
||||||
|
.values(
|
||||||
|
group_id=(
|
||||||
|
int(requestData.get("group_id", 0))
|
||||||
|
if requestData.get("group_id")
|
||||||
|
else 0
|
||||||
|
),
|
||||||
|
access_token=requestData.get("access_token"),
|
||||||
|
base_photo_url=(
|
||||||
|
int(requestData.get("base_photo_url", 0))
|
||||||
|
if requestData.get("base_photo_url")
|
||||||
|
else 0
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
db.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при обновлении запроса: {e}")
|
||||||
|
return jsonify({"status": "ok"})
|
||||||
|
else:
|
||||||
|
logger.info("Добавлен запрос")
|
||||||
|
try:
|
||||||
|
db.session.merge(VkAPI(**requestData))
|
||||||
|
db.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при добавлении запроса: {e}")
|
||||||
|
return jsonify({"status": "ok"})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/request", methods=["POST"])
|
@app.route("/request", methods=["POST"])
|
||||||
@@ -128,16 +226,5 @@ def make_request():
|
|||||||
return jsonify({"status": response.status_code})
|
return jsonify({"status": response.status_code})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/vk/post", methods=["POST"])
|
|
||||||
def add_post():
|
|
||||||
data = request.json
|
|
||||||
post = VkPost(
|
|
||||||
text=data["text"], image_path=data.get("image"), publish_at=data["publish_at"]
|
|
||||||
)
|
|
||||||
db.session.add(post)
|
|
||||||
db.session.commit()
|
|
||||||
return jsonify({"status": "ok"})
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(debug=True)
|
app.run(debug=True)
|
||||||
|
|||||||
@@ -4,28 +4,64 @@ from datetime import datetime
|
|||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
|
||||||
class HttpRequestLog(db.Model):
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
method = db.Column(db.String(10))
|
|
||||||
url = db.Column(db.Text)
|
|
||||||
request_data = db.Column(db.Text)
|
|
||||||
response_code = db.Column(db.Integer)
|
|
||||||
response_body = db.Column(db.Text)
|
|
||||||
created_at = db.Column(db.DateTime, default=datetime.now)
|
|
||||||
|
|
||||||
|
|
||||||
class VkPost(db.Model):
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
text = db.Column(db.Text)
|
|
||||||
image_path = db.Column(db.Text)
|
|
||||||
publish_at = db.Column(db.DateTime)
|
|
||||||
published = db.Column(db.Boolean, default=False)
|
|
||||||
|
|
||||||
|
|
||||||
class MedodsAPI(db.Model):
|
class MedodsAPI(db.Model):
|
||||||
|
__tablename__ = "medods_api"
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
url = db.Column(db.Text)
|
url = db.Column(db.Text)
|
||||||
identity = db.Column(db.Text)
|
identity = db.Column(db.Text)
|
||||||
secretKey = db.Column(db.Text)
|
secretKey = db.Column(db.Text)
|
||||||
created_at = db.Column(db.DateTime, default=datetime.now)
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
||||||
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
|
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
|
||||||
|
|
||||||
|
|
||||||
|
class ApiEndpoint(db.Model):
|
||||||
|
__tablename__ = "api_endpoints"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
|
||||||
|
method = db.Column(db.String(10), nullable=False) # GET / POST
|
||||||
|
title = db.Column(db.String(255), nullable=False) # Человеческое описание
|
||||||
|
url_path = db.Column(db.String(255), nullable=False) # /users, /scheduler
|
||||||
|
|
||||||
|
payload = db.Column(db.JSON, default=dict) # Тело запроса
|
||||||
|
query_params = db.Column(db.JSON, default=dict) # Query-параметры
|
||||||
|
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
||||||
|
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<ApiEndpoint {self.method} {self.url_path}>"
|
||||||
|
|
||||||
|
def toDict(self):
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"method": self.method,
|
||||||
|
"title": self.title,
|
||||||
|
"url_path": self.url_path,
|
||||||
|
"payload": self.payload,
|
||||||
|
"query_params": self.query_params,
|
||||||
|
"created_at": self.created_at.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
"updated_at": self.updated_at.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class VkAPI(db.Model):
|
||||||
|
__tablename__ = "vk_settings"
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
group_id = db.Column(db.Integer)
|
||||||
|
access_token = db.Column(db.Text)
|
||||||
|
base_photo_url = db.Column(db.Integer)
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
||||||
|
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
|
||||||
|
|
||||||
|
def toDict(self):
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"group_id": self.group_id,
|
||||||
|
"access_token": self.access_token,
|
||||||
|
"base_photo_url": self.base_photo_url,
|
||||||
|
"created_at": self.created_at.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
"updated_at": self.updated_at.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
}
|
||||||
|
|||||||
+20
-13
@@ -1,20 +1,27 @@
|
|||||||
import requests
|
import requests
|
||||||
from db import db, HttpRequestLog
|
|
||||||
import json
|
from token_utils import generate_token
|
||||||
|
|
||||||
|
|
||||||
def send_request(method, url, data=None, headers=None):
|
def send_request(method, url, json_data=None, params=None):
|
||||||
response = requests.request(method=method, url=url, json=data, headers=headers)
|
from app import logger
|
||||||
|
|
||||||
log = HttpRequestLog(
|
headers = {"Content-Type": "application/json"}
|
||||||
method=method,
|
bearer_token = generate_token()
|
||||||
url=url,
|
if bearer_token:
|
||||||
request_data=json.dumps(data, ensure_ascii=False),
|
headers["Authorization"] = f"Bearer {bearer_token}"
|
||||||
response_code=response.status_code,
|
logger.info(headers)
|
||||||
response_body=response.text,
|
|
||||||
)
|
|
||||||
|
|
||||||
db.session.add(log)
|
try:
|
||||||
db.session.commit()
|
response = requests.request(
|
||||||
|
method=method, url=url, params=params, json=json_data, headers=headers
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при выполнении запроса: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info(response.status_code)
|
||||||
|
logger.info(response.headers)
|
||||||
|
# logger.info(response.text)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
body {
|
||||||
|
padding-top: 64px; /* чтобы fixed navbar не перекрывал контент */
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .nav-link.active {
|
||||||
|
color: #fff !important;
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/* Стили для разделов */
|
||||||
|
.url-status {
|
||||||
|
border-left: 4px solid #0dcaf0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-status {
|
||||||
|
border-left: 4px solid #198754;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Стили для списка запросов */
|
||||||
|
.requests-list {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.request-item {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.request-item:hover {
|
||||||
|
background-color: rgba(13, 110, 253, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.request-item.active {
|
||||||
|
background-color: rgba(13, 110, 253, 0.1);
|
||||||
|
border-left-color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Метод запроса */
|
||||||
|
.method-badge {
|
||||||
|
font-size: 0.75em;
|
||||||
|
min-width: 60px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Стили для JSON */
|
||||||
|
.json-key { color: #005cc5; font-weight: 600; }
|
||||||
|
.json-string { color: #032f62; }
|
||||||
|
.json-number { color: #e36209; }
|
||||||
|
.json-boolean { color: #6f42c1; }
|
||||||
|
.json-null { color: #d73a49; }
|
||||||
|
|
||||||
|
/* Параметры */
|
||||||
|
.param-row {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.param-row:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Кнопки действий */
|
||||||
|
.btn-action {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Респонс контейнер */
|
||||||
|
.response-container {
|
||||||
|
font-family: 'SF Mono', Monaco, 'Cascadia Code', Consolas, 'Courier New', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.response-pre {
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Статус индикаторы */
|
||||||
|
.status-indicator {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Анимации */
|
||||||
|
.fade-in {
|
||||||
|
animation: fadeIn 0.3s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(-10px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/* Стили статуса */
|
||||||
|
.status-badge {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Карточки настроек */
|
||||||
|
.setting-card {
|
||||||
|
border-left: 4px solid #007bff;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-card:hover {
|
||||||
|
box-shadow: 0 0.5rem 1rem rgba(0, 123, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-card .card-header {
|
||||||
|
background-color: rgba(0, 123, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Иконки VK */
|
||||||
|
.vk-icon {
|
||||||
|
color: #0077FF;
|
||||||
|
/* Официальный цвет VK */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Анимации */
|
||||||
|
.fade-in {
|
||||||
|
animation: fadeIn 0.3s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Уведомления */
|
||||||
|
.alert-fixed {
|
||||||
|
position: fixed;
|
||||||
|
top: 80px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 1050;
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chrome, Edge, Safari */
|
||||||
|
input[type=number]::-webkit-outer-spin-button,
|
||||||
|
input[type=number]::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
input[type=number] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
@@ -0,0 +1,668 @@
|
|||||||
|
// Глобальные переменные
|
||||||
|
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('/settings/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('/settings/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('/settings/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('/settings/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(`/settings/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('/settings/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 showAlert(type, message) {
|
||||||
|
const alertContainer = document.getElementById('alertContainer');
|
||||||
|
|
||||||
|
const alert = document.createElement('div');
|
||||||
|
alert.className = `alert alert-${type} alert-dismissible fade show shadow`;
|
||||||
|
alert.style.minWidth = '300px';
|
||||||
|
alert.style.maxWidth = '400px';
|
||||||
|
|
||||||
|
alert.innerHTML = `
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<i class="bi ${getAlertIcon(type)} me-2"></i>
|
||||||
|
<div class="flex-grow-1">${message}</div>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
alertContainer.appendChild(alert);
|
||||||
|
|
||||||
|
// Автоматическое удаление через 4 секунды
|
||||||
|
setTimeout(() => {
|
||||||
|
if (alert.parentNode) {
|
||||||
|
alert.classList.remove('show');
|
||||||
|
setTimeout(() => alert.remove(), 150);
|
||||||
|
}
|
||||||
|
}, 4000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAlertIcon(type) {
|
||||||
|
const icons = {
|
||||||
|
'success': 'bi-check-circle-fill',
|
||||||
|
'warning': 'bi-exclamation-triangle-fill',
|
||||||
|
'danger': 'bi-x-circle-fill',
|
||||||
|
'info': 'bi-info-circle-fill'
|
||||||
|
};
|
||||||
|
return icons[type] || 'bi-info-circle-fill';
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+154
@@ -0,0 +1,154 @@
|
|||||||
|
// Инициализация при загрузке страницы
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
updateStatusIndicators();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обновление индикаторов статуса
|
||||||
|
function updateStatusIndicators() {
|
||||||
|
const vkStatus = document.getElementById('vkStatus');
|
||||||
|
const saveButton = document.getElementById('saveButton');
|
||||||
|
|
||||||
|
if (pageData && pageData.vk_settings) {
|
||||||
|
vkStatus.innerHTML = '<span class="badge bg-success"><i class="bi bi-check-circle me-1"></i>Настроено</span>';
|
||||||
|
saveButton.innerHTML = '<i class="bi bi-arrow-repeat me-1"></i>Обновить настройки';
|
||||||
|
saveButton.classList.remove('btn-primary');
|
||||||
|
saveButton.classList.add('btn-outline-primary');
|
||||||
|
} else {
|
||||||
|
vkStatus.innerHTML = '<span class="badge bg-warning"><i class="bi bi-exclamation-triangle me-1"></i>Не настроено</span>';
|
||||||
|
saveButton.innerHTML = '<i class="bi bi-save me-1"></i>Сохранить настройки';
|
||||||
|
saveButton.classList.remove('btn-outline-primary');
|
||||||
|
saveButton.classList.add('btn-primary');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Переключение видимости пароля
|
||||||
|
function togglePassword(inputId) {
|
||||||
|
const input = document.getElementById(inputId);
|
||||||
|
const button = input.nextElementSibling.querySelector('i');
|
||||||
|
|
||||||
|
if (input.type === 'password') {
|
||||||
|
input.type = 'text';
|
||||||
|
button.classList.remove('bi-eye');
|
||||||
|
button.classList.add('bi-eye-slash');
|
||||||
|
} else {
|
||||||
|
input.type = 'password';
|
||||||
|
button.classList.remove('bi-eye-slash');
|
||||||
|
button.classList.add('bi-eye');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сброс формы
|
||||||
|
function resetForm() {
|
||||||
|
document.getElementById('vkForm').reset();
|
||||||
|
updateStatusIndicators();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохранение настроек VK
|
||||||
|
async function saveVkSettings() {
|
||||||
|
const access_token = document.getElementById('access_token').value.trim();
|
||||||
|
const group_id = document.getElementById('group_id').value.trim();
|
||||||
|
const base_photo_url = document.getElementById('base_photo_url').value.trim();
|
||||||
|
|
||||||
|
// Проверка обязательных полей
|
||||||
|
if (!access_token || !group_id) {
|
||||||
|
showAlert('warning', 'Заполните обязательные поля: Access Token и ID сообщества');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка формата group_id (должно быть число)
|
||||||
|
if (!/^\d+$/.test(group_id)) {
|
||||||
|
showAlert('warning', 'ID сообщества должен содержать только цифры');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = {
|
||||||
|
access_token,
|
||||||
|
group_id,
|
||||||
|
base_photo_url: base_photo_url || null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pageData && pageData.vk_settings) {
|
||||||
|
settings.id = pageData.vk_settings.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/settings/vk', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(settings)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
showAlert('success', 'Настройки VK успешно сохранены!');
|
||||||
|
|
||||||
|
updateStatusIndicators();
|
||||||
|
|
||||||
|
// Перезагружаем страницу для отображения обновленных данных
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const error = await response.text();
|
||||||
|
showAlert('danger', 'Ошибка сохранения: ' + error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка:', error);
|
||||||
|
showAlert('danger', 'Ошибка сохранения настроек!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вспомогательные функции для уведомлений
|
||||||
|
function showAlert(type, message) {
|
||||||
|
const alertContainer = document.getElementById('alertContainer');
|
||||||
|
|
||||||
|
// Создаем алерт
|
||||||
|
const alert = document.createElement('div');
|
||||||
|
alert.className = `alert alert-${type} alert-dismissible fade show shadow`;
|
||||||
|
|
||||||
|
// Иконка для типа алерта
|
||||||
|
const icon = getAlertIcon(type);
|
||||||
|
|
||||||
|
alert.innerHTML = `
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<i class="bi ${icon} me-2 fs-5"></i>
|
||||||
|
<div class="flex-grow-1">${message}</div>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
alertContainer.appendChild(alert);
|
||||||
|
|
||||||
|
// Автоматическое удаление через 5 секунд
|
||||||
|
setTimeout(() => {
|
||||||
|
if (alert.parentNode) {
|
||||||
|
alert.classList.remove('show');
|
||||||
|
setTimeout(() => alert.remove(), 150);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAlertIcon(type) {
|
||||||
|
const icons = {
|
||||||
|
'success': 'bi-check-circle-fill',
|
||||||
|
'warning': 'bi-exclamation-triangle-fill',
|
||||||
|
'danger': 'bi-x-circle-fill',
|
||||||
|
'info': 'bi-info-circle-fill'
|
||||||
|
};
|
||||||
|
return icons[type] || 'bi-info-circle-fill';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Дополнительная проверка при вводе данных
|
||||||
|
document.getElementById('group_id').addEventListener('input', function (e) {
|
||||||
|
this.value = this.value.replace(/[^\d]/g, '');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Подсказка для base_photo_url при фокусе
|
||||||
|
document.getElementById('base_photo_url').addEventListener('focus', function () {
|
||||||
|
if (!this.value) {
|
||||||
|
showAlert('info', 'Формат ID фото: photo_id (например: 7236456789)');
|
||||||
|
}
|
||||||
|
});
|
||||||
+49
-33
@@ -3,55 +3,71 @@
|
|||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>{% block title %}Система{% endblock %}</title>
|
<title>{% block title %}Control Panel{% endblock %}</title>
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<style>
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
|
||||||
body {
|
<link href="/static/css/base.css" rel="stylesheet">
|
||||||
padding-top: 60px;
|
{% block styles %}{% endblock %}
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-brand {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top shadow-sm">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="/">⚙️ Control Panel</a>
|
|
||||||
|
|
||||||
<div class="dropdown">
|
<!-- Логотип -->
|
||||||
<button class="btn btn-outline-light dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
<a class="navbar-brand fw-semibold" href="/">
|
||||||
Навигация
|
<i class="bi bi-gear-fill me-1"></i> Control Panel
|
||||||
</button>
|
</a>
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li><a class="dropdown-item" href="/">Главная</a></li>
|
<!-- Кнопка для мобилок -->
|
||||||
<li><a class="dropdown-item" href="/medods">Medods</a></li>
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNavbar">
|
||||||
<li><a class="dropdown-item" href="/vk">VK</a></li>
|
<span class="navbar-toggler-icon"></span>
|
||||||
<li><a class="dropdown-item" href="/posts">Посты</a></li>
|
</button>
|
||||||
|
|
||||||
|
<!-- Меню -->
|
||||||
|
<div class="collapse navbar-collapse" id="mainNavbar">
|
||||||
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if request.path == '/' %}active{% endif %}" href="/">
|
||||||
|
<i class="bi bi-speedometer2 me-1"></i> Главная
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if request.path.startswith('/medods') %}active{% endif %}" href="/medods">
|
||||||
|
<i class="bi bi-diagram-3 me-1"></i> Medods
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if request.path.startswith('/vk') %}active{% endif %}" href="/vk">
|
||||||
|
<i class="bi bi-chat-dots me-1"></i> VK
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if request.path.startswith('/posts') %}active{% endif %}" href="/posts">
|
||||||
|
<i class="bi bi-file-earmark-text me-1"></i> Посты
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<script>
|
<main class="container-fluid pt-2">
|
||||||
const pageData = {{ data | tojson }};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container-fluid">
|
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
</main>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
{% block scripts %}{% endblock %}
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
+232
-678
@@ -1,138 +1,113 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}Medods{% endblock %}
|
{% block title %}Medods{% endblock %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
<link rel="stylesheet" href="/static/css/medods.css">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% 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="row mb-4">
|
||||||
<div class="card-header bg-primary text-white">
|
<div class="col-lg-6 mb-4 mb-lg-0">
|
||||||
<h5 class="mb-0">Подключение к серверу</h5>
|
<div class="card h-100 url-status">
|
||||||
</div>
|
<div
|
||||||
<div class="card-body">
|
class="card-header bg-primary bg-opacity-10 border-primary d-flex justify-content-between align-items-center">
|
||||||
<form id="serverForm">
|
<h5 class="mb-0"><i class="bi bi-server me-2"></i>Настройка сервера</h5>
|
||||||
<div class="row g-3 align-items-end">
|
<span id="urlCheck" class="status-indicator">
|
||||||
<div class="col-md-9">
|
{% if data.url %}
|
||||||
<label class="form-label">URL адрес сервера</label>
|
<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"
|
<input type="text" class="form-control" id="server_url" placeholder="https://api.example.com"
|
||||||
required>
|
value="{{ data.url or '' }}">
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<button type="button" class="btn btn-success w-100" onclick="saveServerUrl()">
|
|
||||||
💾 Сохранить
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-text small">Введите полный URL вашего сервера Medods API</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<button type="button" class="btn btn-success w-100" onclick="saveServerUrl()" id="saveServerUrlButton">
|
||||||
|
{% if data.url %}
|
||||||
<hr class="my-4">
|
<i class="bi bi-arrow-repeat me-2"></i>Обновить URL
|
||||||
|
{% else %}
|
||||||
<form id="apiKeyForm" enctype="multipart/form-data">
|
<i class="bi bi-save me-2"></i>Сохранить URL
|
||||||
<div class="row g-3 align-items-end">
|
{% endif %}
|
||||||
<div class="col-md-9">
|
</button>
|
||||||
<label class="form-label">Загрузка API ключа</label>
|
</div>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Аккордеон с запросами -->
|
<div class="col-lg-6">
|
||||||
<div class="accordion mb-4" id="requestsAccordion">
|
<div class="card h-100 api-status">
|
||||||
<div class="accordion-item">
|
<div
|
||||||
<h2 class="accordion-header">
|
class="card-header bg-success bg-opacity-10 border-success d-flex justify-content-between align-items-center">
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
<h5 class="mb-0"><i class="bi bi-key me-2"></i>API ключ</h5>
|
||||||
data-bs-target="#requestsCollapse" aria-expanded="false" aria-controls="requestsCollapse">
|
<span id="apiKeyCheck" class="status-indicator">
|
||||||
⚙️ Настроенные запросы
|
{% if data.apiKey %}
|
||||||
</button>
|
<span class="badge bg-success"><i class="bi bi-check-circle me-1"></i>Загружен</span>
|
||||||
</h2>
|
{% else %}
|
||||||
<div id="requestsCollapse" class="accordion-collapse collapse" data-bs-parent="#requestsAccordion">
|
<span class="badge bg-warning"><i class="bi bi-exclamation-triangle me-1"></i>Не загружен</span>
|
||||||
<div class="accordion-body">
|
{% endif %}
|
||||||
<!-- Форма создания/редактирования запроса -->
|
</span>
|
||||||
<div class="card mb-4">
|
</div>
|
||||||
<div class="card-header">
|
<div class="card-body">
|
||||||
<h5 class="mb-0">Добавить/Редактировать запрос</h5>
|
<div class="mb-3">
|
||||||
</div>
|
<label class="form-label fw-semibold">CSV файл с ключами</label>
|
||||||
<div class="card-body">
|
<input type="file" class="form-control" id="api_key_file" accept=".csv">
|
||||||
<form id="requestForm">
|
<div class="form-text small">
|
||||||
<input type="hidden" id="requestId">
|
<i class="bi bi-info-circle me-1"></i>
|
||||||
|
Файл должен содержать колонки: <code>identity;secretKey</code>
|
||||||
<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>
|
</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 id="requestsList">
|
||||||
<div class="text-center">
|
<!-- Список запросов будет загружен здесь -->
|
||||||
|
<div class="text-center py-4">
|
||||||
<div class="spinner-border text-primary" role="status">
|
<div class="spinner-border text-primary" role="status">
|
||||||
<span class="visually-hidden">Загрузка...</span>
|
<span class="visually-hidden">Загрузка...</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -140,575 +115,154 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Раздел выполнения запросов -->
|
<!-- Правая колонка: Редактор запроса -->
|
||||||
<div class="card">
|
<div class="col-md-8">
|
||||||
<div class="card-header bg-info text-white">
|
<div class="p-3 border-bottom bg-light">
|
||||||
<h5 class="mb-0">📥 Выполнение запроса</h5>
|
<h5 class="mb-0">
|
||||||
</div>
|
<i class="bi bi-pencil-square me-2"></i>
|
||||||
<div class="card-body">
|
<span id="editorTitle">Создание нового запроса</span>
|
||||||
<form id="executeForm">
|
</h5>
|
||||||
<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>
|
</div>
|
||||||
</form>
|
<div class="p-4">
|
||||||
|
<form id="requestForm">
|
||||||
|
<input type="hidden" id="requestId">
|
||||||
|
|
||||||
<!-- Окно с результатом -->
|
<!-- Основные поля -->
|
||||||
<div id="responseSection" style="display: none;">
|
<div class="row g-3 mb-4">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="col-md-6">
|
||||||
<h6>Результат выполнения:</h6>
|
<label class="form-label fw-semibold">Название запроса</label>
|
||||||
<button type="button" class="btn btn-sm btn-outline-success" onclick="downloadResponse()">
|
<input type="text" class="form-control form-control-lg" id="title"
|
||||||
⬇️ Скачать JSON
|
placeholder="Например: Получить список пользователей" required>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
<div class="col-md-3">
|
||||||
<div class="card">
|
<label class="form-label fw-semibold">HTTP метод</label>
|
||||||
<div class="card-body">
|
<select class="form-select form-select-lg" id="method" required>
|
||||||
<div id="responseContainer" class="response-container"
|
<option value="GET">GET</option>
|
||||||
style="max-height: 500px; overflow-y: auto;">
|
<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>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<!-- Раздел с результатом -->
|
||||||
.response-container {
|
<div class="card fade-in" id="responseCard" style="display: none;">
|
||||||
font-family: 'Courier New', monospace;
|
<div class="card-header bg-info bg-opacity-10">
|
||||||
font-size: 14px;
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
line-height: 1.4;
|
<h5 class="mb-0"><i class="bi bi-arrow-return-right me-2"></i>Результат выполнения запроса</h5>
|
||||||
background-color: #f8f9fa;
|
<div>
|
||||||
padding: 15px;
|
<button type="button" class="btn btn-sm btn-outline-success me-2" onclick="downloadResponse()">
|
||||||
border-radius: 5px;
|
<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;
|
<div id="alertContainer" style="position: fixed; top: 80px; right: 20px; z-index: 1050;"></div>
|
||||||
font-weight: bold;
|
{% endblock %}
|
||||||
}
|
|
||||||
|
|
||||||
.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>
|
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
// Сохранение URL сервера
|
const pageData = {{ data | tojson }};
|
||||||
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);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
<script src="/static/js/medods.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
+170
-15
@@ -1,22 +1,177 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}VK{% endblock %}
|
{% block title %}VK{% endblock %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
<link rel="stylesheet" href="/static/css/vk.css">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h3 class="mb-4">📣 VK</h3>
|
<!-- Заголовок -->
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<div class="card">
|
<div>
|
||||||
<div class="card-body">
|
<h2 class="mb-1"><i class="bi bi-megaphone vk-icon me-2"></i>VK Настройки</h2>
|
||||||
<div class="mb-3">
|
<p class="text-muted mb-0">Настройки для работы с VK API и сообществом</p>
|
||||||
<label class="form-label">Access Token группы</label>
|
</div>
|
||||||
<input class="form-control">
|
<div class="badge bg-primary fs-6 px-3 py-2">
|
||||||
</div>
|
<i class="bi bi-bell me-1"></i>Социальная сеть
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">ID сообщества</label>
|
|
||||||
<input class="form-control">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn btn-primary">Сохранить</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Настройки VK -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8 mx-auto">
|
||||||
|
<div class="card setting-card fade-in">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0"><i class="bi bi-gear vk-icon me-2"></i>Настройки VK</h5>
|
||||||
|
<span id="vkStatus" class="status-badge">
|
||||||
|
{% if data.vk_settings %}
|
||||||
|
<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">
|
||||||
|
<form id="vkForm">
|
||||||
|
<!-- Access Token группы -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label fw-semibold">
|
||||||
|
<i class="bi bi-key vk-icon me-1"></i>Access Token группы
|
||||||
|
</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text bg-light"><i class="bi bi-shield-lock"></i></span>
|
||||||
|
<input type="password" class="form-control" id="access_token"
|
||||||
|
placeholder="Введите Access Token вашей группы"
|
||||||
|
value="{{ data.vk_settings.access_token if data.vk_settings else '' }}">
|
||||||
|
<button class="btn btn-outline-secondary" type="button"
|
||||||
|
onclick="togglePassword('access_token')">
|
||||||
|
<i class="bi bi-eye"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-text small">
|
||||||
|
<i class="bi bi-info-circle me-1"></i>
|
||||||
|
Access Token с правами: groups, wall
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ID сообщества -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label fw-semibold">
|
||||||
|
<i class="bi bi-people vk-icon me-1"></i>ID сообщества
|
||||||
|
</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text bg-light"><i class="bi bi-hash"></i></span>
|
||||||
|
<input type="number" class="form-control" id="group_id" placeholder="Например: 123456789"
|
||||||
|
step="1" min="0" value="{{ data.vk_settings.group_id if data.vk_settings else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-text small">
|
||||||
|
<i class="bi bi-info-circle me-1"></i>
|
||||||
|
Числовой ID вашего сообщества VK (без знака минус)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ID Базового фото -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label fw-semibold">
|
||||||
|
<i class="bi bi-image vk-icon me-1"></i>ID Базового фото
|
||||||
|
</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text bg-light"><i class="bi bi-image-alt"></i></span>
|
||||||
|
<input type="number" class="form-control" id="base_photo_url"
|
||||||
|
placeholder="Например: 12345689123" step="1" min="0"
|
||||||
|
value="{{ data.vk_settings.base_photo_url if data.vk_settings else '' }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-text small">
|
||||||
|
<i class="bi bi-info-circle me-1"></i>
|
||||||
|
ID фото в формате: <code>photo_id</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Информационная панель -->
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="me-3">
|
||||||
|
<i class="bi bi-lightbulb fs-4"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h6 class="alert-heading">Как получить данные:</h6>
|
||||||
|
<ul class="mb-0 small">
|
||||||
|
<li><strong>Access Token:</strong> Создайте Standalone-приложение в <a
|
||||||
|
href="https://vk.com/apps?act=manage" target="_blank">управлении
|
||||||
|
приложениями VK</a></li>
|
||||||
|
<li><strong>ID сообщества:</strong> Число в адресе сообщества после
|
||||||
|
<code>vk.com/public</code> или <code>vk.com/club</code>
|
||||||
|
</li>
|
||||||
|
<li><strong>ID Базового фото:</strong> Загрузите фото в альбом сообщества и
|
||||||
|
скопируйте ID из адреса фото</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Кнопки действий -->
|
||||||
|
<div class="d-flex gap-3 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="saveVkSettings()" id="saveButton">
|
||||||
|
<i class="bi bi-save me-1"></i>Сохранить настройки
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Информация о текущих настройках -->
|
||||||
|
{% if data.vk_settings %}
|
||||||
|
<div class="card mt-4 fade-in">
|
||||||
|
<div class="card-header bg-success bg-opacity-10 text-success">
|
||||||
|
<h6 class="mb-0"><i class="bi bi-check-circle me-2"></i>Текущие настройки</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card bg-light">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-title"><i class="bi bi-key me-2"></i>Access Token</h6>
|
||||||
|
<p class="card-text small text-truncate" id="tokenPreview">
|
||||||
|
{{ data.vk_settings.access_token[:15] }}...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card bg-light">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-title"><i class="bi bi-people me-2"></i>ID сообщества</h6>
|
||||||
|
<p class="card-text">-{{ data.vk_settings.group_id }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card bg-light">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-title"><i class="bi bi-image me-2"></i>ID Базового фото</h6>
|
||||||
|
<p class="card-text">photo-{{ data.vk_settings.group_id }}_{{
|
||||||
|
data.vk_settings.base_photo_url }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Контейнер для уведомлений -->
|
||||||
|
<div id="alertContainer" class="alert-fixed"></div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
const pageData = {{ data | tojson }};
|
||||||
|
</script>
|
||||||
|
<script src="/static/js/vk.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
+7
-3
@@ -2,6 +2,8 @@ import csv
|
|||||||
import jwt
|
import jwt
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from db import MedodsAPI
|
||||||
|
|
||||||
|
|
||||||
def load_api_key(csv_path="apiKey.csv"):
|
def load_api_key(csv_path="apiKey.csv"):
|
||||||
with open(csv_path, newline="", encoding="utf-8") as f:
|
with open(csv_path, newline="", encoding="utf-8") as f:
|
||||||
@@ -10,10 +12,12 @@ def load_api_key(csv_path="apiKey.csv"):
|
|||||||
|
|
||||||
|
|
||||||
def generate_token():
|
def generate_token():
|
||||||
data = load_api_key()
|
medodsDB = MedodsAPI.query.first()
|
||||||
|
if not medodsDB:
|
||||||
|
return None
|
||||||
|
|
||||||
identity = data["identity"]
|
identity = medodsDB.identity
|
||||||
secret = data["secret"]
|
secret = medodsDB.secretKey
|
||||||
|
|
||||||
iat = int(time.time())
|
iat = int(time.time())
|
||||||
exp = iat + 60 # <= 64 сек
|
exp = iat + 60 # <= 64 сек
|
||||||
|
|||||||
Reference in New Issue
Block a user