Updates
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"python.analysis.extraPaths": [
|
||||||
|
"./app/source"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,28 +1,23 @@
|
|||||||
#!env/bin/python3.10
|
#!env/bin/python3.10
|
||||||
# -*- coding: UTF-8 -*-
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
#активация FLASK
|
# активация FLASK
|
||||||
#from flask import Flask
|
from flask import Flask, send_from_directory
|
||||||
|
|
||||||
|
from flask import jsonify # для генерации JSON, не актуально
|
||||||
from flask import Flask, request, send_from_directory
|
|
||||||
|
|
||||||
from flask import jsonify #для генерации JSON, не актуально
|
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
|
|
||||||
#from flask import redirect, url_for
|
# from flask import redirect, url_for
|
||||||
|
|
||||||
|
|
||||||
#Активируем сжатие данных
|
# Активируем сжатие данных
|
||||||
from flask_compress import Compress
|
from flask_compress import Compress
|
||||||
compress = Compress()
|
compress = Compress()
|
||||||
app = Flask(__name__, static_url_path='', static_folder='')
|
app = Flask(__name__, static_url_path='', static_folder='')
|
||||||
compress.init_app(app)
|
compress.init_app(app)
|
||||||
|
|
||||||
|
|
||||||
|
# Статика
|
||||||
|
|
||||||
#Статика
|
|
||||||
|
|
||||||
@app.route('/favicon.ico')
|
@app.route('/favicon.ico')
|
||||||
def favicon():
|
def favicon():
|
||||||
@@ -34,6 +29,7 @@ def favicon():
|
|||||||
def send_from_statics(path):
|
def send_from_statics(path):
|
||||||
return send_from_directory('static', path)
|
return send_from_directory('static', path)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/temp/<path:path>')
|
@app.route('/temp/<path:path>')
|
||||||
def send_from_temp(path):
|
def send_from_temp(path):
|
||||||
return send_from_directory('temp', path)
|
return send_from_directory('temp', path)
|
||||||
@@ -43,20 +39,19 @@ def send_from_temp(path):
|
|||||||
CORS(app)
|
CORS(app)
|
||||||
|
|
||||||
# sanity check route
|
# sanity check route
|
||||||
|
|
||||||
|
|
||||||
@app.route('/ping', methods=['GET'])
|
@app.route('/ping', methods=['GET'])
|
||||||
def ping_pong():
|
def ping_pong():
|
||||||
return jsonify('pong!')
|
return jsonify('pong!')
|
||||||
|
|
||||||
#@app.route('/<path:path>')
|
# @app.route('/<path:path>')
|
||||||
#def static_file(path):
|
# def static_file(path):
|
||||||
# return app.send_static_file(path)#
|
# return app.send_static_file(path)#
|
||||||
|
|
||||||
#активируем файл конфигурации
|
|
||||||
|
# активируем файл конфигурации
|
||||||
app.config.from_object('config')
|
app.config.from_object('config')
|
||||||
|
|
||||||
#активируем Вьювер
|
# активируем Вьювер
|
||||||
from app import views
|
from app import views
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
29
app/pages/rustdesk/config.ini
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
[ACCESS]
|
||||||
|
# Проверка интерфейса пользователя
|
||||||
|
# 1 Администратор
|
||||||
|
# 2 Оператор
|
||||||
|
# 3 Врач-эксперт
|
||||||
|
# 4 Агент
|
||||||
|
# 5 Специалист
|
||||||
|
# 6 Сортировщик
|
||||||
|
# 7 Диспетчер
|
||||||
|
# 8 Страховой представитель
|
||||||
|
# 9 Сотрудник МФЦ
|
||||||
|
# 10 Ведущий специалист
|
||||||
|
# Пример: UserInterFace = 1,10 (без пробелов между запятыми)
|
||||||
|
UserInterFace = 0
|
||||||
|
|
||||||
|
# Минимальный уровень доступа пользователя от 0
|
||||||
|
# 0 - без проверки
|
||||||
|
# 1 - чтение
|
||||||
|
# 2 - чтение и запись
|
||||||
|
# 3 - полный доступ
|
||||||
|
AccessOMS = 0
|
||||||
|
AccessLPU = 0
|
||||||
|
AccessUOG = 0
|
||||||
|
AccessENP = 0
|
||||||
|
AccessSMS = 0
|
||||||
|
AccessPostCard = 0
|
||||||
|
AccessPhone = 0
|
||||||
|
AccessDial = 0
|
||||||
|
AccessAnkets = 0
|
||||||
41
app/pages/rustdesk/script.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// Автор: ELForcer
|
||||||
|
// Дата оптимизации: 21.04.2023
|
||||||
|
const RootComponent = {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
//Цвета
|
||||||
|
M_Red: false,
|
||||||
|
M_Green: false,
|
||||||
|
M_Blue: false,
|
||||||
|
M_Yellow: false,
|
||||||
|
|
||||||
|
//Модальные переменные
|
||||||
|
ModalTitleThread: "",
|
||||||
|
ModalTitle2: "Запуск",
|
||||||
|
ModalBodyText2: "Пожалуйста, подождите...",
|
||||||
|
ModalBodyThread: "",
|
||||||
|
ThreadMessage: "",
|
||||||
|
ThreadVars: {
|
||||||
|
message: '',
|
||||||
|
HTML: "",
|
||||||
|
Started: false,
|
||||||
|
},
|
||||||
|
}; //return
|
||||||
|
}, //data
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
methods: {
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
}, // END metods
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
mounted() {
|
||||||
|
ThreadVars = this.ThreadVars;
|
||||||
|
}, // mounted()
|
||||||
|
}; // Vue
|
||||||
|
|
||||||
|
//Монтируем Vue как vueapp начиная с тега #vueapp
|
||||||
|
vueapp = Vue.createApp(RootComponent).mount("#vueapp");
|
||||||
61
app/pages/rustdesk/source.py
Executable file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Главное меню
|
||||||
|
Дата последней оптимизации: 21.04.2023
|
||||||
|
"""
|
||||||
|
|
||||||
|
from app import app # чтение из config.py
|
||||||
|
from flask import request # получение данных Cookie, GET и POST
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Подключаем свои библиотеки
|
||||||
|
sys.path.append("app/source")
|
||||||
|
sys.path.append("app/pages")
|
||||||
|
import API_Common as APIC
|
||||||
|
|
||||||
|
# Глобальные Переменные
|
||||||
|
CurPath: str = "app/pages"
|
||||||
|
NameModule = "rustdesk"
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# MAIN
|
||||||
|
###############################################################################
|
||||||
|
def Main(SessionID):
|
||||||
|
|
||||||
|
# Объявление переменных
|
||||||
|
Var = {"Title": "Главное меню", "ProgName": app.config['PROGNAME']}
|
||||||
|
Modal_Vars = {}
|
||||||
|
|
||||||
|
###
|
||||||
|
TemplateData = ""
|
||||||
|
ScriptBody = ""
|
||||||
|
|
||||||
|
# Генерируем HTML
|
||||||
|
TemplateName = os.path.join(str(CurPath), str(NameModule), 'template.htm')
|
||||||
|
if (os.path.exists(TemplateName) == True):
|
||||||
|
with open(TemplateName) as fp:
|
||||||
|
TemplateData = fp.read()
|
||||||
|
fp.close()
|
||||||
|
|
||||||
|
# Подгружаем ява скрипт для вставки его в тело шаблона
|
||||||
|
ScriptName = os.path.join(CurPath, NameModule, 'script.js')
|
||||||
|
if (os.path.exists(ScriptName) == True):
|
||||||
|
with open(ScriptName) as fp:
|
||||||
|
ScriptBody = fp.read()
|
||||||
|
fp.close()
|
||||||
|
|
||||||
|
# Подставляем переменные в HTML
|
||||||
|
TemplateData = TemplateData = TemplateData.replace('}}', '}}')
|
||||||
|
TemplateData = TemplateData.replace('{{head}}', APIC.UserHead(Var)) # Подключаем раздел head со скриптами и css
|
||||||
|
TemplateData = TemplateData.replace('{{UserHeader}}', APIC.UserHeader(SessionID, Var)) # Рисуем шапку на странице
|
||||||
|
TemplateData = TemplateData.replace('{{Modals}}', APIC.Modals(Modal_Vars)) # Модальные окна
|
||||||
|
TemplateData = TemplateData.replace('{{ThreadVars_message}}', APIC.ThreadVars_Message()) # Сообщения от потока
|
||||||
|
TemplateData = TemplateData.replace('{{version}}', app.config['VERSION']) # Версия Сервера КМС-ИК
|
||||||
|
TemplateData = TemplateData.replace('{{IPServer}}', request.host.split(":")[0]) # Версия Сервера КМС-ИК
|
||||||
|
TemplateData = TemplateData.replace('{{ScriptBody}}', ScriptBody) # Скрипты. Например, Vue.JS
|
||||||
|
|
||||||
|
return TemplateData # Отправляем обработанный шаблон
|
||||||
84
app/pages/rustdesk/template.htm
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
{{head}}
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{{UserHeader}}
|
||||||
|
|
||||||
|
<p>1. Зайти и скачать дистрибутив с сайта <a href="https://rustdesk.com/" target="_blank">RustDesk</a> или скачать:
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://github.com/rustdesk/rustdesk/releases/download/1.2.3-1/rustdesk-1.2.3-1-x86_64.exe">дистрибутив для Windows v1.2.3 x64</a></li>
|
||||||
|
<li><a href="https://github.com/rustdesk/rustdesk/releases/download/1.2.3-1/rustdesk-1.2.3-x86_64.deb">дистрибутив для Linux (Mint, Ubuntu, Debian) v1.2.3 x64</a></li>
|
||||||
|
<li><a target="_blank" href="https://github.com/rustdesk/rustdesk/releases">С репозитория GitHub</a>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>2. Напротив ID нажать на троеточие;</p>
|
||||||
|
<p><img src="/static/image/rustdesk/rd1.png"></p>
|
||||||
|
<p>3. Выбрать "Сеть"</p>
|
||||||
|
<p><img src="/static/image/rustdesk/rd2.png"></p>
|
||||||
|
<p>4. Выбрать "Нажать "Разблокировать сетевые настройки"</p>
|
||||||
|
<p><img src="/static/image/rustdesk/rd3.png"></p>
|
||||||
|
|
||||||
|
<p>Может потребоваться ввод пароля администратора</p>
|
||||||
|
<p><img src="/static/image/rustdesk/rd4.png"></p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="inputS1">5. Скопировать код настроек:</label>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col">
|
||||||
|
<input id="inputS1" type="text" class="form-control" value="==Qfi0zb4UmM2cWWIl2S1sWY0EXdzVmS5QEWDdEUtJmbLVjNihzdB1mSxl0cKZ3ViojI5V2aiwiIiojIpBXYiwiI1JnLyV2Yy9mZsVmI6ISehxWZyJCLiUncuIXZjJ3bmxWZiojI0N3boJye" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<button type="button" class="btn btn-secondary" id="copyS1">Скопировать</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
7. Вернитесь в закладку "Главная" и сообщите ваш ID. Дождитесь подключения к Вашему рабочему столу.
|
||||||
|
</p>
|
||||||
|
<p><img src="/static/image/rustdesk/rd6.png"></p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Запрос на подключение будет выглядеть так:
|
||||||
|
</p>
|
||||||
|
<p><img src="/static/image/rustdesk/rd8.png"></p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Принятое подключение будет выглядеть так:
|
||||||
|
</p>
|
||||||
|
<p><img src="/static/image/rustdesk/rd9.png"></p>
|
||||||
|
<script type="text/javascript">
|
||||||
|
/* сохраняем текстовое поле в переменную text */
|
||||||
|
|
||||||
|
var TextS1 = document.getElementById("inputS1");
|
||||||
|
|
||||||
|
|
||||||
|
/* сохраняем кнопку в переменную btn */
|
||||||
|
|
||||||
|
var btnS1 = document.getElementById("copyS1");
|
||||||
|
|
||||||
|
|
||||||
|
/* вызываем функцию при нажатии на кнопку */
|
||||||
|
|
||||||
|
|
||||||
|
btnS1.onclick = function () {
|
||||||
|
TextS1.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<!-- {{UserBottom}} -->
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
32
app/source/API_App.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
"""
|
||||||
|
Нестандартные процедуры, которые использует только это приложение
|
||||||
|
Дата последней оптимизации: 07.06.2022
|
||||||
|
"""
|
||||||
|
import uuid # Уникальные имена
|
||||||
|
import os # Работа с ОС
|
||||||
|
import shutil # Копирование файлов
|
||||||
|
import time # работа с временем
|
||||||
|
import datetime # Работа с датой
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from app import app # чтение из config.py
|
||||||
|
|
||||||
|
sys.path.append("app/source")
|
||||||
|
import API_Common as APIC
|
||||||
|
|
||||||
|
|
||||||
|
NameModule = "API_App"
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
def UserHeader(SessionID: str, Vars: dict) -> str:
|
||||||
|
"""Вызывается из API_Common"""
|
||||||
|
# print(str(Vars["request"].url.split('/')))
|
||||||
|
request = Vars["request"]
|
||||||
|
CurStr = Vars["CurStr"]
|
||||||
|
|
||||||
|
Link = ""
|
||||||
|
LinkReport = ""
|
||||||
|
|
||||||
|
return CurStr
|
||||||
1176
app/source/API_Common.py
Normal file
1084
app/source/OLD_API_Common.py
Normal file
138
app/source/OLD_Pages.py
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Дата оптимизации: 09.06.2022
|
||||||
|
Дата тестирования: 14.06.2022
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Активация FLASK
|
||||||
|
from app import app # чтение из config.py
|
||||||
|
from flask import render_template # использование шаблонов
|
||||||
|
# from flask import request # получение данных Cookie, GET и POST
|
||||||
|
from flask import jsonify # ответ в формате JSON
|
||||||
|
# from flask import redirect # Код страницы и перенаравление
|
||||||
|
# import json
|
||||||
|
import sys
|
||||||
|
import configparser
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
# Подключаем свои библиотеки
|
||||||
|
sys.path.append("app/source")
|
||||||
|
import API_Common as APIC
|
||||||
|
import API_App
|
||||||
|
# import API_MSSQL
|
||||||
|
# import API_App
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Проверка сессии
|
||||||
|
###############################################################################
|
||||||
|
def CheckSession(SessionID):
|
||||||
|
if SessionID is not None:
|
||||||
|
if (SessionID != ""):
|
||||||
|
CurDataSession: APIC.DataSession = APIC.CheckSession(SessionID, True)
|
||||||
|
if (CurDataSession.status == False): return False
|
||||||
|
|
||||||
|
CurSessionID = CurDataSession.sessionid
|
||||||
|
|
||||||
|
# Если нет в базе такой сессии, то заного авторизуемся
|
||||||
|
if (CurSessionID == ""): return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# POST
|
||||||
|
###############################################################################
|
||||||
|
def API(SessionID, CurPage):
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read("app/pages/" + CurPage + "/config.ini")
|
||||||
|
|
||||||
|
VarArray = {}
|
||||||
|
if (CheckSession(SessionID) == False) and config["ACCESS"]["NeedAuth"] == 1:
|
||||||
|
return "Требуется повторная авторизация. Нажмите F5.", 401
|
||||||
|
|
||||||
|
if (CurPage != ""):
|
||||||
|
spec = importlib.util.spec_from_file_location("module.name", f"app/pages/{CurPage}/source.py")
|
||||||
|
CurPage = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(CurPage)
|
||||||
|
return CurPage.API(SessionID)
|
||||||
|
|
||||||
|
VarArray.update({'status': 'Неизвестная операция'})
|
||||||
|
return jsonify(VarArray)
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# MAIN
|
||||||
|
###############################################################################
|
||||||
|
def Main(SessionID, CurPage):
|
||||||
|
# Объявление переменных
|
||||||
|
Var = {"Title": "", "ProgName": app.config['PROGNAME']}
|
||||||
|
Modal_Vars = {}
|
||||||
|
|
||||||
|
S = APIC.CheckSession(SessionID, False)
|
||||||
|
|
||||||
|
# Получаем уровень доступа
|
||||||
|
if (S.userid not in ("-1", "")):
|
||||||
|
UserData: API_MSSQL.Result = API_App.GetDataUserByID(APIC.ReadDataSession(SessionID, "userid"))
|
||||||
|
|
||||||
|
# Создаем страничку
|
||||||
|
try:
|
||||||
|
if (CurPage is None):
|
||||||
|
return render_template('Pages.htm', head=APIC.UserHead(Var) # Подключаем раздел head со скриптами и css
|
||||||
|
, UserHeader=APIC.UserHeader(SessionID, Var) # Рисуем шапку на странице
|
||||||
|
, Modals=APIC.Modals(Modal_Vars) # Модальные окна
|
||||||
|
, ThreadVars_message=APIC.ThreadVars_Message() #
|
||||||
|
, version=app.config['VERSION'] #
|
||||||
|
, R_category="{{R.category}}" #
|
||||||
|
, R_name="{{R.name}}" #
|
||||||
|
, R_description="{{R.description}}" #
|
||||||
|
, R_autor="{{R.autor}}" #
|
||||||
|
, R_tags="{{R.tags}}" #
|
||||||
|
, CurTag="{{CurTag}}" #
|
||||||
|
)
|
||||||
|
|
||||||
|
# Если вошли в режиме Установщика то проверку не проверяем
|
||||||
|
if (S.userid != "-1"):
|
||||||
|
# Проверка прав на модуль
|
||||||
|
if (os.path.isfile("app/pages/" + CurPage + "/config.ini") == False):
|
||||||
|
return render_template('error_403.htm', head=APIC.UserHead(Var) # Подключаем раздел head со скриптами и css
|
||||||
|
, UserHeader=APIC.UserErrorHeader(SessionID, Var) # Рисуем шапку на странице
|
||||||
|
, ThreadVars_message=f"Не удалось получить настройки для страницы {CurPage}. Похоже, эта страница на стадии разработки")
|
||||||
|
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read("app/pages/" + CurPage + "/config.ini")
|
||||||
|
|
||||||
|
if (config["ACCESS"]["UserInterFace"] not in ("0", "")):
|
||||||
|
if (
|
||||||
|
str(UserData.GetRecord(0, "UserInterFace")) not in config["ACCESS"]["UserInterFace"].split(',')
|
||||||
|
# or AccessOMS < int(config["ACCESS"]["AccessOMS"])
|
||||||
|
or int(UserData.GetRecord(0, "AccessOMS")) < int(config["ACCESS"]["AccessOMS"])
|
||||||
|
or int(UserData.GetRecord(0, "AccessLPU")) < int(config["ACCESS"]["AccessLPU"])
|
||||||
|
or int(UserData.GetRecord(0, "AccessUOG")) < int(config["ACCESS"]["AccessUOG"])
|
||||||
|
or int(UserData.GetRecord(0, "AccessENP")) < int(config["ACCESS"]["AccessENP"])
|
||||||
|
or int(UserData.GetRecord(0, "AccessSMS")) < int(config["ACCESS"]["AccessSMS"])
|
||||||
|
or int(UserData.GetRecord(0, "AccessPostCard")) < int(config["ACCESS"]["AccessPostCard"])
|
||||||
|
or int(UserData.GetRecord(0, "AccessPhone")) < int(config["ACCESS"]["AccessPhone"])
|
||||||
|
or int(UserData.GetRecord(0, "AccessDial")) < int(config["ACCESS"]["AccessDial"])
|
||||||
|
or int(UserData.GetRecord(0, "AccessAnkets")) < int(config["ACCESS"]["AccessAnkets"])
|
||||||
|
|
||||||
|
):
|
||||||
|
return render_template('error_403.htm', head=APIC.UserHead(Var) # Подключаем раздел head со скриптами и css
|
||||||
|
, UserHeader=APIC.UserErrorHeader(SessionID, Var) # Рисуем шапку на странице
|
||||||
|
, ThreadVars_message="Извините, но вам не хватает доступа к этой странице.")
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
spec = importlib.util.spec_from_file_location("module.name", "app/pages/" + CurPage + "/source.py")
|
||||||
|
CurPage = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(CurPage)
|
||||||
|
return CurPage.Main(SessionID)
|
||||||
|
except Exception as E:
|
||||||
|
return render_template('error_500.htm', head=APIC.UserHead(Var) # Подключаем раздел head со скриптами и css
|
||||||
|
, UserHeader=APIC.UserHeader(SessionID, Var) # Рисуем шапку на странице
|
||||||
|
, ThreadVars_message=f"При открытии страницы {CurPage} произошла ошибка: {E}")
|
||||||
127
app/source/Pages.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Дата оптимизации: 09.06.2022
|
||||||
|
Дата тестирования: 14.06.2022
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Активация FLASK
|
||||||
|
from app import app # чтение из config.py
|
||||||
|
from flask import render_template # использование шаблонов
|
||||||
|
# from flask import request # получение данных Cookie, GET и POST
|
||||||
|
from flask import jsonify # ответ в формате JSON
|
||||||
|
# from flask import redirect # Код страницы и перенаравление
|
||||||
|
# import json
|
||||||
|
import sys
|
||||||
|
import configparser
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
# Подключаем свои библиотеки
|
||||||
|
sys.path.append("app/source")
|
||||||
|
import API_Common as APIC
|
||||||
|
import API_App
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Проверка сессии
|
||||||
|
###############################################################################
|
||||||
|
def CheckSession(SessionID):
|
||||||
|
if SessionID is not None:
|
||||||
|
if (SessionID != ""):
|
||||||
|
CurDataSession: APIC.DataSession = APIC.CheckSession(SessionID, True)
|
||||||
|
if (CurDataSession.status == False): return False
|
||||||
|
|
||||||
|
CurSessionID = CurDataSession.sessionid
|
||||||
|
|
||||||
|
# Если нет в базе такой сессии, то заного авторизуемся
|
||||||
|
if (CurSessionID == ""): return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# POST
|
||||||
|
###############################################################################
|
||||||
|
def API(SessionID, CurPage):
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read("app/pages/" + CurPage + "/config.ini")
|
||||||
|
|
||||||
|
VarArray = {}
|
||||||
|
if (CheckSession(SessionID) == False) and config["ACCESS"]["NeedAuth"] == 1:
|
||||||
|
return "Требуется повторная авторизация. Нажмите F5.", 401
|
||||||
|
|
||||||
|
if (CurPage != ""):
|
||||||
|
spec = importlib.util.spec_from_file_location("module.name", f"app/pages/{CurPage}/source.py")
|
||||||
|
CurPage = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(CurPage)
|
||||||
|
return CurPage.API(SessionID)
|
||||||
|
|
||||||
|
VarArray.update({'status': 'Неизвестная операция'})
|
||||||
|
return jsonify(VarArray)
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# MAIN
|
||||||
|
###############################################################################
|
||||||
|
def Main(SessionID, CurPage):
|
||||||
|
# Объявление переменных
|
||||||
|
Var = {"Title": "", "ProgName": app.config['PROGNAME']}
|
||||||
|
Modal_Vars = {}
|
||||||
|
|
||||||
|
S = APIC.CheckSession(SessionID, False)
|
||||||
|
|
||||||
|
# Получаем уровень доступа
|
||||||
|
if (S.userid not in ("-1", "")):
|
||||||
|
pass # Пока не проверяем
|
||||||
|
|
||||||
|
# Создаем страничку
|
||||||
|
try:
|
||||||
|
if (CurPage is None):
|
||||||
|
return render_template('Pages.htm', head=APIC.UserHead(Var) # Подключаем раздел head со скриптами и css
|
||||||
|
, UserHeader=APIC.UserHeader(SessionID, Var) # Рисуем шапку на странице
|
||||||
|
, Modals=APIC.Modals(Modal_Vars) # Модальные окна
|
||||||
|
, ThreadVars_message=APIC.ThreadVars_Message() #
|
||||||
|
, version=app.config['VERSION'] #
|
||||||
|
, R_category="{{R.category}}" #
|
||||||
|
, R_name="{{R.name}}" #
|
||||||
|
, R_description="{{R.description}}" #
|
||||||
|
, R_autor="{{R.autor}}" #
|
||||||
|
, R_tags="{{R.tags}}" #
|
||||||
|
, CurTag="{{CurTag}}" #
|
||||||
|
)
|
||||||
|
|
||||||
|
# Если вошли в режиме Установщика то проверку не проверяем
|
||||||
|
if (S.userid != "-1"):
|
||||||
|
# Проверка прав на модуль
|
||||||
|
if (os.path.isfile("app/pages/" + CurPage + "/config.ini") == False):
|
||||||
|
return render_template('error_403.htm', head=APIC.UserHead(Var) # Подключаем раздел head со скриптами и css
|
||||||
|
, UserHeader=APIC.UserErrorHeader(SessionID, Var) # Рисуем шапку на странице
|
||||||
|
, ThreadVars_message=f"Не удалось получить настройки для страницы {CurPage}. Похоже, эта страница на стадии разработки")
|
||||||
|
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read("app/pages/" + CurPage + "/config.ini")
|
||||||
|
|
||||||
|
if (config["ACCESS"]["UserInterFace"] not in ("0", "")):
|
||||||
|
if (
|
||||||
|
|
||||||
|
|
||||||
|
):
|
||||||
|
return render_template('error_403.htm', head=APIC.UserHead(Var) # Подключаем раздел head со скриптами и css
|
||||||
|
, UserHeader=APIC.UserErrorHeader(SessionID, Var) # Рисуем шапку на странице
|
||||||
|
, ThreadVars_message="Извините, но вам не хватает доступа к этой странице.")
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
spec = importlib.util.spec_from_file_location("module.name", "app/pages/" + CurPage + "/source.py")
|
||||||
|
CurPage = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(CurPage)
|
||||||
|
return CurPage.Main(SessionID)
|
||||||
|
except Exception as E:
|
||||||
|
return render_template('error_500.htm', head=APIC.UserHead(Var) # Подключаем раздел head со скриптами и css
|
||||||
|
, UserHeader=APIC.UserHeader(SessionID, Var) # Рисуем шапку на странице
|
||||||
|
, ThreadVars_message=f"При открытии страницы {CurPage} произошла ошибка: {E}")
|
||||||
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 32 KiB |
BIN
app/static/image/rustdesk/rd5.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
app/static/image/rustdesk/rd6.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
app/static/image/rustdesk/rd8.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
app/static/image/rustdesk/rd9.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
18
app/templates/block_head.htm
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{{ProgName}} - {{Title}}</title>
|
||||||
|
<link rel="stylesheet" href="/static/node_modules/bootstrap/dist/css/bootstrap.css">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
|
||||||
|
|
||||||
|
.spoiler {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mywindow2 {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
17
app/templates/error_403.htm
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
{{head|safe}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{UserHeader|safe}}
|
||||||
|
<p align="center"><img src="/static/image/stop_403.png" height="480"></p>
|
||||||
|
<br>
|
||||||
|
<div class="alert alert-danger" role="alert" align="center">
|
||||||
|
{{ThreadVars_message}}
|
||||||
|
</div>
|
||||||
|
<p align="center">
|
||||||
|
<a href="javascript:history.back();"><button class="btn btn-outline-primary">⬅ Назад</button></a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
17
app/templates/error_404.htm
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
{{head|safe}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{UserHeader|safe}}
|
||||||
|
<p align="center"><img src="/static/image/stop_403.png" height="480"></p>
|
||||||
|
<br>
|
||||||
|
<div class="alert alert-danger" role="alert" align="center">
|
||||||
|
{{ThreadVars_message}}
|
||||||
|
</div>
|
||||||
|
<p align="center">
|
||||||
|
<a href="javascript:history.back();"><button class="btn btn-outline-primary">⬅ Назад</button></a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
17
app/templates/error_500.htm
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
{{head|safe}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{UserHeader|safe}}
|
||||||
|
<p align="center"><img src="/static/image/stop_500.webp" height="480"></p>
|
||||||
|
<br>
|
||||||
|
<div class="alert alert-danger" role="alert" align="center">
|
||||||
|
{{ThreadVars_message}}
|
||||||
|
</div>
|
||||||
|
<p align="center">
|
||||||
|
<a href="javascript:history.back();"><button class="btn btn-outline-primary">⬅ Назад</button></a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
<!DOCTYPE HTML>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>{{ProgName}} - {{Title}}</title>
|
|
||||||
<link rel="stylesheet" href="/static/node_modules/bootstrap/dist/css/bootstrap.css">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
|
|
||||||
|
|
||||||
<style type="text/css">
|
|
||||||
|
|
||||||
|
|
||||||
.spoiler {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mywindow2 {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body >
|
|
||||||
{{UserHeader|safe}}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<p>1. Зайти и скачать дистрибутив с сайта <a href="https://rustdesk.com/" target="_blank">RustDesk</a> или скачать:
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://github.com/rustdesk/rustdesk/releases/download/1.2.0/rustdesk-1.2.0-windows_x64.zip">дистрибутив для Windows v1.2.0 x64</a></li>
|
|
||||||
<li><a href="https://github.com/rustdesk/rustdesk/releases/download/1.2.0/rustdesk-1.2.0.deb">дистрибутив для Linux (Mint, Ubuntu, Debian) v1.2.0 x64</a></li>
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>2. Напротив ID нажать на троеточие и выбрать пункт “ID/Сервер ретрансляции”
|
|
||||||
<p><img src="/static/image/rustdesk/rd2.png"></p>
|
|
||||||
<p>3. Указать следующие настройки:<br>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="inputS1">ID-сервер:</label>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="col">
|
|
||||||
<input id="inputS1" type="text" class="form-control" value="elforcer.ru" readonly>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<button type="button" class="btn btn-secondary" id="copyS1">Скопировать</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="inputS2">Сервер ретрансляции:</label>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="col">
|
|
||||||
<input id="inputS2" type="text" class="form-control" value="elforcer.ru" readonly>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<button type="button" class="btn btn-secondary" id="copyS2">Скопировать</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Key:</label>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="col">
|
|
||||||
<input id="inputTextKey" class="form-control" type="text" value="WvJsIqJmAw8b65KnbmPGCXD9Jesuq4ak5KiHYg62e8o=" readonly>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
<button type="button" class="btn btn-secondary" id="copyKey">Скопировать</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</p>
|
|
||||||
<p><img src="/static/image/rustdesk/rd3.png"></p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
4. Сообщить ваш ID для подключения.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
5. Нажать "Принять" в появившимся верхнем правом углу.
|
|
||||||
</p>
|
|
||||||
<p><img src="/static/image/rustdesk/rd3.png"></p>
|
|
||||||
<script type="text/javascript">
|
|
||||||
/* сохраняем текстовое поле в переменную text */
|
|
||||||
var TextKey = document.getElementById("inputTextKey");
|
|
||||||
var TextS1 = document.getElementById("inputS1");
|
|
||||||
var TextS2 = document.getElementById("inputS2");
|
|
||||||
|
|
||||||
/* сохраняем кнопку в переменную btn */
|
|
||||||
var btnKey = document.getElementById("copyKey");
|
|
||||||
var btnS1 = document.getElementById("copyS1");
|
|
||||||
var btnS2 = document.getElementById("copyS2");
|
|
||||||
|
|
||||||
/* вызываем функцию при нажатии на кнопку */
|
|
||||||
btnKey.onclick = function () {
|
|
||||||
TextKey.select();
|
|
||||||
document.execCommand("copy");
|
|
||||||
}
|
|
||||||
|
|
||||||
btnS1.onclick = function () {
|
|
||||||
TextS1.select();
|
|
||||||
document.execCommand("copy");
|
|
||||||
}
|
|
||||||
|
|
||||||
btnS2.onclick = function () {
|
|
||||||
TextS2.select();
|
|
||||||
document.execCommand("copy");
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{{UserBottom|safe}}
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
52
app/views.py
@@ -5,14 +5,19 @@
|
|||||||
from app import app # чтение из config.py, роуты
|
from app import app # чтение из config.py, роуты
|
||||||
from flask import render_template # использование шаблонов
|
from flask import render_template # использование шаблонов
|
||||||
from flask import request # получение данных Cookie, GET и POST
|
from flask import request # получение данных Cookie, GET и POST
|
||||||
from flask import redirect # Код страницы и перенаравление
|
|
||||||
|
|
||||||
import os # чтение куки и работа с файлами и папками
|
import os # чтение куки и работа с файлами и папками
|
||||||
|
import sys # Подключаем свои библиотеки
|
||||||
|
|
||||||
|
sys.path.append("app/source")
|
||||||
|
import Pages # Страницы сайта
|
||||||
|
import API_Common as APIC
|
||||||
|
|
||||||
# Глобальные переменные
|
# Глобальные переменные
|
||||||
ProgName = app.config['PROGNAME']
|
ProgName = app.config['PROGNAME']
|
||||||
version = app.config['VERSION']
|
version = app.config['VERSION']
|
||||||
|
appname = app.config['SHMNAME']
|
||||||
|
"""
|
||||||
LevelA = ""
|
LevelA = ""
|
||||||
Flag = False
|
Flag = False
|
||||||
FIO = ""
|
FIO = ""
|
||||||
@@ -23,10 +28,10 @@ CurSessionID = ""
|
|||||||
|
|
||||||
|
|
||||||
S = ['0', '1', '2', '3']
|
S = ['0', '1', '2', '3']
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(413)
|
@app.errorhandler(413)
|
||||||
def request_entity_too_large(error):
|
def request_entity_too_large(error):
|
||||||
return 'Превышен максимальный размер файла', 413
|
return 'Превышен максимальный размер файла', 413
|
||||||
@@ -38,22 +43,6 @@ def page_not_found(e):
|
|||||||
return """Здесь нет того, чего ты ищешь... <br><a href ="/"> Вернутся на главную</a> """, 404
|
return """Здесь нет того, чего ты ищешь... <br><a href ="/"> Вернутся на главную</a> """, 404
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(403)
|
|
||||||
def access_denided(e):
|
|
||||||
# Получаем куки и сессию
|
|
||||||
|
|
||||||
SessionID = request.cookies.get('SessionID')
|
|
||||||
print(SessionID)
|
|
||||||
|
|
||||||
if SessionID is None or SessionID == "": # если нет сессии
|
|
||||||
return redirect("/login", code=302)
|
|
||||||
else:
|
|
||||||
HTML = """Похоже, сюда можно только администраторам. <br><a href ="/"> Вернутся на главную</a> """
|
|
||||||
return render_template('index.htm', UserHeader=APIC.UserHeader(SessionID, "<font color=red>Доступ запрещен</font>", ProgName), HTML=HTML)
|
|
||||||
|
|
||||||
# return render_template('404.html'), 404
|
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Добавляем роуты
|
# Добавляем роуты
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
@@ -67,13 +56,18 @@ def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
########################################################################
|
||||||
@app.route('/rustdesk')
|
# rustdesk
|
||||||
def rustdesk():
|
########################################################################
|
||||||
Title = "RustDesk"
|
@app.route('/rustdesk', methods=['GET', 'POST'])
|
||||||
return render_template('rustdesk.htm',
|
def route_rustdesk():
|
||||||
UserHeader=UserHeader(Title, ProgName), version=version, Title=Title, ProgName=ProgName
|
# Получаем куки и сессию
|
||||||
)
|
SessionID = request.cookies.get(appname + '_SessionID')
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
return Pages.API(SessionID, "rustdesk") # Если POST запрос
|
||||||
|
else:
|
||||||
|
return Pages.Main(SessionID, "rustdesk") # Если GET запрос
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -98,8 +92,8 @@ def portfolio():
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
def UserHeader(Title, ProgName):
|
def UserHeader(Title, ProgName):
|
||||||
CurStr = ""
|
CurStr = ""
|
||||||
if (os.path.exists("app/templates/_header") == True):
|
if (os.path.exists("app/templates/block_header.htm") == True):
|
||||||
file = open("app/templates/_header", "r")
|
file = open("app/templates/block_header.htm", "r")
|
||||||
|
|
||||||
CurStr = file.read()
|
CurStr = file.read()
|
||||||
file.close()
|
file.close()
|
||||||
|
|||||||
@@ -20,22 +20,22 @@ CheckPython3()
|
|||||||
{
|
{
|
||||||
echo ""
|
echo ""
|
||||||
echo "============================================================="
|
echo "============================================================="
|
||||||
echo "Python 3.10.12"
|
echo "Python 3.10.13"
|
||||||
echo "============================================================="
|
echo "============================================================="
|
||||||
echo "Проверяем наличие Python3 в системе "
|
echo "Проверяем наличие Python3 в системе "
|
||||||
T=$(python3 -V | grep 3.10.12)
|
T=$(python3 -V | grep 3.10.13)
|
||||||
if [ "$T" == "" ];
|
if [ "$T" == "" ];
|
||||||
then
|
then
|
||||||
echo "Устанавливаем зависимости для Python 3.10 "
|
echo "Устанавливаем зависимости для Python 3.10 "
|
||||||
sudo apt-get build-dep python3.10
|
sudo apt-get build-dep python3.10
|
||||||
|
|
||||||
echo "Скачиваем Python 3.10.12 "
|
echo "Скачиваем Python 3.10.13 "
|
||||||
cd /tmp/
|
cd /tmp/
|
||||||
wget -c https://www.python.org/ftp/python/3.10.12/Python-3.10.12.tgz
|
wget -c https://www.python.org/ftp/python/3.10.13/Python-3.10.13.tgz
|
||||||
|
|
||||||
echo "Распаковываем архив... "
|
echo "Распаковываем архив... "
|
||||||
tar -xvzf Python-3.10.12.tgz
|
tar -xvzf Python-3.10.13.tgz
|
||||||
cd Python-3.10.12
|
cd Python-3.10.13
|
||||||
|
|
||||||
echo "Настраиваем пакет... "
|
echo "Настраиваем пакет... "
|
||||||
./configure --enable-optimizations
|
./configure --enable-optimizations
|
||||||
@@ -44,15 +44,15 @@ CheckPython3()
|
|||||||
make
|
make
|
||||||
make test
|
make test
|
||||||
|
|
||||||
echo "Устанавливаем Python 3.10.12... "
|
echo "Устанавливаем Python 3.10.13... "
|
||||||
sudo make install
|
sudo make install
|
||||||
else
|
else
|
||||||
echo "Уже установлен Python 3.10.12"
|
echo "Уже установлен Python 3.10.13"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Перепроверяем " $1
|
echo "Перепроверяем " $1
|
||||||
T=$(python3 -V | grep 3.10.12)
|
T=$(python3 -V | grep 3.10.13)
|
||||||
if $T= ""
|
if $T= ""
|
||||||
then
|
then
|
||||||
echo "Не удалось установить " $1
|
echo "Не удалось установить " $1
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ Flask
|
|||||||
Flask-Compress
|
Flask-Compress
|
||||||
Flask-Cors
|
Flask-Cors
|
||||||
gunicorn
|
gunicorn
|
||||||
|
requests
|
||||||
|
|||||||