linkkf 로직수정중

This commit is contained in:
2025-12-25 19:42:32 +09:00
parent 695d26767e
commit af9a38a973
128 changed files with 8711 additions and 1484 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

12
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="linkkf" uuid="8b6bf041-ffab-472b-b603-18b3316bc628">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/data/db/linkkf.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

20
.idea/flaskfarm.iml generated Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.9 (FF)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/lib/framework/templates" />
</list>
</option>
</component>
</module>

View File

@@ -0,0 +1,76 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="HttpUrlsUsage" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredUrls">
<list>
<option value="http://localhost" />
<option value="http://127.0.0.1" />
<option value="http://0.0.0.0" />
<option value="http://www.w3.org/" />
<option value="http://json-schema.org/draft" />
<option value="http://java.sun.com/" />
<option value="http://xmlns.jcp.org/" />
<option value="http://javafx.com/javafx/" />
<option value="http://javafx.com/fxml" />
<option value="http://maven.apache.org/xsd/" />
<option value="http://maven.apache.org/POM/" />
<option value="http://www.springframework.org/schema/" />
<option value="http://www.springframework.org/tags" />
<option value="http://www.springframework.org/security/tags" />
<option value="http://www.thymeleaf.org" />
<option value="http://www.jboss.org/j2ee/schema/" />
<option value="http://www.jboss.com/xml/ns/" />
<option value="http://www.ibm.com/webservices/xsd" />
<option value="http://activemq.apache.org/schema/" />
<option value="http://schema.cloudfoundry.org/spring/" />
<option value="http://schemas.xmlsoap.org/" />
<option value="http://cxf.apache.org/schemas/" />
<option value="http://primefaces.org/ui" />
<option value="http://tiles.apache.org/" />
<option value="http://yommi.duckdns.org" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="17">
<item index="0" class="java.lang.String" itemvalue="trio-websocket" />
<item index="1" class="java.lang.String" itemvalue="h11" />
<item index="2" class="java.lang.String" itemvalue="loguru" />
<item index="3" class="java.lang.String" itemvalue="sniffio" />
<item index="4" class="java.lang.String" itemvalue="sqlalchemy" />
<item index="5" class="java.lang.String" itemvalue="wsproto" />
<item index="6" class="java.lang.String" itemvalue="attrs" />
<item index="7" class="java.lang.String" itemvalue="sortedcontainers" />
<item index="8" class="java.lang.String" itemvalue="exceptiongroup" />
<item index="9" class="java.lang.String" itemvalue="trio" />
<item index="10" class="java.lang.String" itemvalue="selenium" />
<item index="11" class="java.lang.String" itemvalue="certifi" />
<item index="12" class="java.lang.String" itemvalue="pysocks" />
<item index="13" class="java.lang.String" itemvalue="urllib3" />
<item index="14" class="java.lang.String" itemvalue="async-generator" />
<item index="15" class="java.lang.String" itemvalue="outcome" />
<item index="16" class="java.lang.String" itemvalue="idna" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="N802" />
<option value="N803" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="sqlalchemy.engine.result.Result.__await__" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

4
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (FF)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/flaskfarm.iml" filepath="$PROJECT_DIR$/.idea/flaskfarm.iml" />
</modules>
</component>
</project>

View File

@@ -0,0 +1,9 @@
c python:S6019"FFix this reluctant quantifier that will only ever match 0 repetitions.(¨†…™üÿÿÿÿ
c python:S6019 "FFix this reluctant quantifier that will only ever match 0 repetitions.(Ìé©èùÿÿÿÿ
c python:S6019"FFix this reluctant quantifier that will only ever match 0 repetitions.(–…‡éþÿÿÿÿ
^ python:S6019"FFix this reluctant quantifier that will only ever match 0 repetitions.(ëÅÑš
^ python:S6019"FFix this reluctant quantifier that will only ever match 0 repetitions.(‹Ò•–
6 python:S125!"Remove this commented out code.(¡‚ÙÈ
T python:S5754)"<Specify an exception class to catch or reraise the exception(ˆÊÉ·
6 python:S1252"Remove this commented out code.(ý<>™Ë

View File

@@ -0,0 +1,6 @@
q python:S3776
"TRefactor this function to reduce its Cognitive Complexity from 22 to the 15 allowed.(úÈœ‚ÿÿÿÿÿ
6 python:S125"Remove this commented out code.(ÑêÏà
T python:S5754/"<Specify an exception class to catch or reraise the exception(ˆÊÉ·
6 python:S125("Remove this commented out code.(£ÌÎæ

View File

@@ -0,0 +1,2 @@
l python:S3776"TRefactor this function to reduce its Cognitive Complexity from 25 to the 15 allowed.(”Ò­Ñ

View File

@@ -0,0 +1,10 @@
e python:S1192'"MDefine a constant instead of duplicating this literal 'Exception:%s' 9 times.(ï°Ð½
L python:S10669"/Merge this if statement with the enclosing one.(Íìáöüÿÿÿÿ
l python:S3776"TRefactor this function to reduce its Cognitive Complexity from 50 to the 15 allowed.(<28>Øê­
6 python:S125/"Remove this commented out code.(“Ä¡–
l python:S3776k"TRefactor this function to reduce its Cognitive Complexity from 39 to the 15 allowed.(ûãô”
r python:S3776á"TRefactor this function to reduce its Cognitive Complexity from 58 to the 15 allowed.(ó‚‚åýÿÿÿÿ
U python:S5754ü"<Specify an exception class to catch or reraise the exception(ˆÊÉ·
< python:S125¼"Remove this commented out code.(ÁêÕúûÿÿÿÿ
U python:S5754é"<Specify an exception class to catch or reraise the exception(ˆÊÉ·

View File

@@ -0,0 +1,7 @@
B python:S1481"%Remove the unused local variable "e".(¼–¯¸ÿÿÿÿÿ
6 python:S125 "Remove this commented out code.(™èÇÅ
; python:S125"Remove this commented out code.(à髈üÿÿÿÿ
6 python:S125T"Remove this commented out code.(»—Ö
C python:S5806Y"+Rename this variable; it shadows a builtin.(Ç­¡¡
6 python:S125Z"Remove this commented out code.(¾§Ç¡

View File

@@ -0,0 +1,33 @@
e python:S5797I"HReplace this expression; used as a condition it will always be constant.(™¼ï€üÿÿÿÿ
M python:S1066ã"/Merge this if statement with the enclosing one.(„Õý“þÿÿÿÿ
v python:S1163"ZRename this field "SystemModelSetting" to match the regular expression ^[_a-z][_a-z0-9]*$.(òªÆÓýÿÿÿÿ
b python:S1164"KRename this field "Job" to match the regular expression ^[_a-z][_a-z0-9]*$.(„è´’
r python:S116Ù"URename this field "PluginManager" to match the regular expression ^[_a-z][_a-z0-9]*$.(æ¥Ë÷ùÿÿÿÿ
6 python:S125/"Remove this commented out code.(´ÔÔí
6 python:S125_"Remove this commented out code.(„ÿ’È
6 python:S125"Remove this commented out code.(æ‰Èì
[ python:S112">Replace this generic exception class with a more specific one.(¢…›°þÿÿÿÿ
y python:S1186¯"[Add a nested comment explaining why this function is empty, or complete the implementation.(³‡Êºþÿÿÿÿ
| python:S117Á"_Rename this local variable "SystemInstance" to match the regular expression ^[_a-z][a-z0-9_]*$.(çÉÍ¥úÿÿÿÿ
r python:S3776÷"TRefactor this function to reduce its Cognitive Complexity from 26 to the 15 allowed.(<28><>†øÿÿÿÿ
7 python:S125ü"Remove this commented out code.(ðû<C3B0>
A python:S108")Either remove or fill this block of code.(ã‘Û¾
U python:S5754 "<Specify an exception class to catch or reraise the exception(ˆÊÉ·
< python:S125¼"Remove this commented out code.(ÛÑ“¹üÿÿÿÿ
r python:S3776Ç"TRefactor this function to reduce its Cognitive Complexity from 21 to the 15 allowed.(û´´¨ýÿÿÿÿ
< python:S125Ê"Remove this commented out code.(ˆà¡×ÿÿÿÿÿ
U python:S5754þ"<Specify an exception class to catch or reraise the exception(ˆÊÉ·
t python:S117²"\Rename this local variable "fileHandler" to match the regular expression ^[_a-z][a-z0-9_]*$.(ºŽ™…
v python:S117"^Rename this local variable "streamHandler" to match the regular expression ^[_a-z][a-z0-9_]*$.(<28>½ÝÀ
m python:S3776<18>"TRefactor this function to reduce its Cognitive Complexity from 24 to the 15 allowed.(¸ì¶Ê
U python:S5754¢"<Specify an exception class to catch or reraise the exception(ˆÊÉ·
U python:S5754"<Specify an exception class to catch or reraise the exception(ˆÊÉ·
n python:S1542¥"PRename function "customTime" to match the regular expression ^[a-z_][a-z0-9_]*$.(ŠˆíÖúÿÿÿÿ
U python:S5754À"<Specify an exception class to catch or reraise the exception(Ê<E28099>
U python:S5754Â"<Specify an exception class to catch or reraise the exception(Ê<E28099>
U python:S5754Ä"<Specify an exception class to catch or reraise the exception(Ê<E28099>
U python:S5754Æ"<Specify an exception class to catch or reraise the exception(Ê<E28099>
U python:S5754È"<Specify an exception class to catch or reraise the exception(Ê<E28099>
U python:S5754Ï"<Specify an exception class to catch or reraise the exception(ˆÊÉ·
U python:S5754<18>"<Specify an exception class to catch or reraise the exception(ˆÊÉ·

17
.idea/sonarlint/issuestore/index.pb generated Normal file
View File

@@ -0,0 +1,17 @@
I
lib/framework/__init__.py,0/3/03875fef6dc33ed50c8bc5f25df52c02e352a134
M
lib/framework/init_declare.py,9/5/95519e06d92ec11e26cf873c3c30e2fcedf78892
J
lib/framework/init_menu.py,6/2/627433fe5c5c7210e3062642e7963227a319d5c6
L
lib/framework/init_plugin.py,8/2/82544e7bcd3de23afaf278a62d1180d65a1ef456
K
lib/framework/init_route.py,5/8/58836750c643ef469da17133f44914292a82f3b3
I
lib/framework/init_web.py,0/e/0e68783ed60c8d2f67617374a92c1652fb6bdaee
K
lib/framework/log_viewer.py,0/c/0c528d2f014ab7c32dd27d4e5d79396e74f3e62c
J
lib/framework/init_main.py,e/e/eeb13886aba87bd6947610e1cba3283d308baf0c

13
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/data/plugins/ffmpeg" vcs="Git" />
<mapping directory="$PROJECT_DIR$/data/plugins/flaskcode" vcs="Git" />
<mapping directory="$PROJECT_DIR$/data/plugins/klive_plus" vcs="Git" />
<mapping directory="$PROJECT_DIR$/data/plugins/number_baseball" vcs="Git" />
<mapping directory="$PROJECT_DIR$/data/plugins/sjva" vcs="Git" />
<mapping directory="$PROJECT_DIR$/data/plugins/terminal" vcs="Git" />
<mapping directory="$PROJECT_DIR$/data/plugins/trans" vcs="Git" />
</component>
</project>

43
cli/encode.py Normal file
View File

@@ -0,0 +1,43 @@
import argparse
import os
import sys
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib'))
from support import SupportFile, SupportSC, logger
class Encode:
def start_folder(self, folderpath):
for name in os.listdir(folderpath):
filepath = os.path.join(folderpath, name)
if os.path.isfile(filepath) and name not in ['setup.py', '__init__.py']:
self.encode_file(filepath)
def encode_file(self, filepath):
text = SupportFile.read_file(filepath)
data = SupportSC.encode(text, 0)
SupportFile.write_file(filepath + 'f', data)
logger.info(f"Create {os.path.basename(filepath + 'f')}")
def process_args(self):
parser = argparse.ArgumentParser()
parser.add_argument('--mode', default='encode')
parser.add_argument('--source', required=True, help=u'absolute path. folder or file')
args = parser.parse_args()
if SupportSC.LIBRARY_LOADING == False:
logger.error("sc import fail")
return
if os.path.exists(args.source):
if os.path.isdir(args.source):
self.start_folder(args.source)
elif os.path.isfile(args.source):
self.encode_file(args.source)
else:
logger.error("wrong source path!!")
if __name__== "__main__":
Encode().process_args()

90
ff_3_10_requirements.txt Normal file
View File

@@ -0,0 +1,90 @@
aiohttp==3.8.3
aiosignal==1.2.0
amqp==5.1.1
appdirs==1.4.4
APScheduler==3.9.1
async-generator==1.10
async-timeout==4.0.2
attrs==22.1.0
beautifulsoup4==4.11.1
bidict==0.22.0
billiard==3.6.4.0
cattrs==22.2.0
celery==5.2.7
certifi==2022.9.24
charset-normalizer==2.1.1
click==8.1.3
click-didyoumean==0.3.0
click-plugins==1.1.1
click-repl==0.2.0
cloudscraper==1.2.64
Deprecated==1.2.13
discord-webhook==0.17.0
EditorConfig==0.12.3
exceptiongroup==1.0.0rc9
Flask==2.2.2
Flask-Cors==3.0.10
Flask-Dropzone==1.6.0
Flask-Login==0.6.2
Flask-Markdown==0.3
Flask-SocketIO==5.3.1
Flask-SQLAlchemy==3.0.2
FlaskFarm==4.0.47
frozenlist==1.3.1
gevent==22.10.1
gevent-websocket==0.10.1
greenlet==1.1.3.post0
h11==0.14.0
idna==3.4
importlib-metadata==5.0.0
itsdangerous==2.1.2
Jinja2==3.1.2
jsbeautifier==1.14.7
kombu==5.2.4
lxml==4.9.1
Markdown==3.4.1
MarkupSafe==2.1.1
multidict==6.0.2
outcome==1.2.0
packaging==21.3
Pillow==9.2.0
prompt-toolkit==3.0.31
psutil==5.9.3
pycryptodome==3.15.0
pyparsing==3.0.9
PySocks==1.7.1
python-dotenv==0.21.0
python-engineio==4.3.4
python-socketio==5.7.2
pytz==2022.5
pytz-deprecation-shim==0.1.0.post0
PyYAML==6.0
redis==4.3.4
requests==2.28.1
requests-cache==0.9.6
requests-toolbelt==0.10.1
selenium==4.5.0
selenium-stealth==1.0.6
six==1.16.0
sniffio==1.3.0
sortedcontainers==2.4.0
soupsieve==2.3.2.post1
SQLAlchemy==1.4.42
telepot-mod==0.0.1
tqdm==4.64.1
trio==0.22.0
trio-websocket==0.9.2
tzdata==2022.5
tzlocal==4.2
url-normalize==1.4.3
urllib3==1.26.12
vine==5.0.0
wcwidth==0.2.5
webdriver-manager==3.8.4
Werkzeug==2.2.2
wrapt==1.14.1
wsproto==1.2.0
yarl==1.8.1
zipp==3.10.0
zope.event==4.5.0
zope.interface==5.5.0

View File

@@ -1,18 +1,12 @@
# 카테고리
# uri 가 plugin인 경우 name 값은 대체
#- name: "토렌트"
# list:
# - uri: "rss"
#- name: "기본 기능"
# list:
# - uri: "terminal"
# - uri: "command"
# - uri: "flaskfilemanager"
# - uri: "flaskcode"
# - uri: "number_baseball"
#- name: "링크"
@@ -32,6 +26,8 @@
name: "확장 설정"
- uri: "system/plugin"
name: "플러그인 관리"
- uri: "system/tool/command"
name: "Command 관리"
- uri: "-"
- uri: "system/logout"
name: "로그아웃"

View File

@@ -3,7 +3,7 @@
# message id 가 없을 경우 DEFAULT 값 사용
# 공통: type, enable_time (시작시간-종료시간. 항상 받을 경우 생략)
- DEFAULT:
DEFAULT:
- type: 'telegram'
token: ''
chat_id: ''
@@ -14,7 +14,7 @@
enable_time: '09-23'
- system_start:
system_start:
- type: 'telegram'
token: ''
chat_id: ''

View File

@@ -3,11 +3,11 @@ Flask
Flask-SQLAlchemy
Flask-Login
Flask-Cors
Flask-Markdown
#Flask-Markdown
Flask-SocketIO
python-engineio
python-socketio
Werkzeug
python-socketio<5.8.0
Werkzeug<3.0
Jinja2
# common util
@@ -26,3 +26,4 @@ pillow
gevent
gevent-websocket
pycryptodome
json_fix

BIN
lib/.DS_Store vendored Normal file

Binary file not shown.

BIN
lib/framework/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -1,25 +1,70 @@
try:
import yaml
except:
import os
try:
os.system("pip install pyyaml")
except:
pass
from .init_main import Framework
from .version import VERSION
frame = Framework.get_instance()
F = frame
logger = frame.logger
app = frame.app
celery = frame.celery
db = frame.db
scheduler = frame.scheduler
socketio = frame.socketio
path_app_root = frame.path_app_root
path_data = frame.path_data
get_logger = frame.get_logger
# 2024.06.13
# 잘못된 설계로 인해 import 만으로 초기화 되버려 lib을 사용할 수 없다.
# 분리.
F = None
frame = None
logger = None
app = None
celery = None
db = None
scheduler = None
socketio = None
rd = None
path_app_root = None
path_data = None
get_logger = None
SystemModelSetting = None
get_cache = None
def initiaize():
global F
global frame
global logger
global app
global celery
global db
global scheduler
global socketio
global path_app_root
global path_data
global get_logger
global SystemModelSetting
global get_cache
F = Framework.get_instance()
frame = F
logger = frame.logger
app = frame.app
celery = frame.celery
db = frame.db
scheduler = frame.scheduler
socketio = frame.socketio
rd = frame.rd
path_app_root = frame.path_app_root
path_data = frame.path_data
get_logger = frame.get_logger
frame.initialize_system()
from system.setup import SystemModelSetting as SS
SystemModelSetting = SS
frame.initialize_plugin()
return frame
from flask_login import login_required
from support import d
from .init_declare import User, check_api
from .scheduler import Job
frame.initialize_system()
from system.setup import SystemModelSetting
frame.initialize_plugin()

View File

@@ -0,0 +1,73 @@
import redis
class _RedisManager:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, host='localhost', port=6379):
if hasattr(self, 'redis_client'):
return
try:
self.redis_client = redis.Redis(host=host, port=port, db=1, decode_responses=True)
self.redis_client.ping()
self.is_redis = True
except redis.exceptions.ConnectionError:
self.is_redis = False
self.cache_backend = {} # Redis 실패 시 메모리 캐시 사용
def set(self, key, value, ex=None):
if self.is_redis:
self.redis_client.set(key, value, ex=ex)
else:
self.cache_backend[key] = value
def get(self, key):
if self.is_redis:
return self.redis_client.get(key)
else:
return self.cache_backend.get(key)
def delete(self, key):
if self.is_redis:
self.redis_client.delete(key)
else:
if key in self.cache_backend:
del self.cache_backend[key]
#_redis_manager_instance = _RedisManager()
class NamespacedCache:
def __init__(self, namespace):
self._manager = _RedisManager._instance
self.namespace = namespace
def _make_key(self, key):
# 'plugin_name:key' 형식으로 실제 키를 생성
return f"{self.namespace}:{key}"
def set(self, key, value, ex=None):
full_key = self._make_key(key)
self._manager.set(full_key, value, ex=ex)
def get(self, key):
full_key = self._make_key(key)
return self._manager.get(full_key)
def delete(self, key):
full_key = self._make_key(key)
self._manager.delete(full_key)
def get_cache(plugin_name: str) -> NamespacedCache:
"""
플러그인 이름을 기반으로 네임스페이스가 적용된 캐시 객체를 반환합니다.
"""
if not plugin_name:
raise ValueError("플러그인 이름은 필수입니다.")
return NamespacedCache(plugin_name)

View File

@@ -13,13 +13,13 @@ def check_api(original_function):
#logger.warning(request.url)
#logger.warning(request.form)
try:
if F.SystemModelSetting.get_bool('auth_use_apikey'):
if request.method == 'POST':
apikey = request.form['apikey']
else:
apikey = request.args.get('apikey')
#apikey = request.args.get('apikey')
if apikey is None or apikey != F.SystemModelSetting.get('auth_apikey'):
if F.SystemModelSetting.get_bool('use_apikey'):
try:
d = request.get_json()
except Exception:
d = request.form.to_dict() if request.method == 'POST' else request.args.to_dict()
apikey = d.get('apikey')
if apikey is None or apikey != F.SystemModelSetting.get('apikey'):
F.logger.warning('CHECK API : ABORT no match ({})'.format(apikey))
F.logger.warning(request.environ.get('HTTP_X_REAL_IP', request.remote_addr))
abort(403)
@@ -31,7 +31,7 @@ def check_api(original_function):
return original_function(*args, **kwargs) #2
return wrapper_function
# Suuport를 logger 생성전에 쓰지 않기 위해 중복 선언
# Support를 logger 생성전에 쓰지 않기 위해 중복 선언
import logging
@@ -47,7 +47,7 @@ class CustomFormatter(logging.Formatter):
# pathname filename
#format = "[%(asctime)s|%(name)s|%(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
__format = '[{yellow}%(asctime)s{reset}|{color}%(levelname)s{reset}|{green}%(name)s{reset} %(pathname)s:%(lineno)s] {color}%(message)s{reset}' if os.environ.get('LOGGER_PATHNAME', "False") == "True" else '[{yellow}%(asctime)s{reset}|{color}%(levelname)s{reset}|{green}%(name)s{reset} %(filename)s:%(lineno)s] {color}%(message)s{reset}'
__format = '[{yellow}%(asctime)s{reset}|{color}%(levelname)s{reset}|{green}%(name)s{reset}|%(pathname)s:%(lineno)s] {color}%(message)s{reset}' if os.environ.get('LOGGER_PATHNAME', "False") == "True" else '[{yellow}%(asctime)s{reset}|{color}%(levelname)s{reset}|{green}%(name)s{reset}|%(filename)s:%(lineno)s] {color}%(message)s{reset}'
FORMATS = {
logging.DEBUG: __format.format(color=grey, reset=reset, yellow=yellow, green=green),

View File

@@ -8,14 +8,15 @@ import time
import traceback
from datetime import datetime
import redis
import yaml
from flask import Flask
from flask_cors import CORS
from flask_login import LoginManager, login_required
from flask_socketio import SocketIO
from flask_sqlalchemy import SQLAlchemy
from flaskext.markdown import Markdown
from pytz import timezone, utc
from werkzeug.middleware.proxy_fix import ProxyFix
from .init_declare import CustomFormatter, check_api
@@ -37,12 +38,15 @@ class Framework:
self.db = None
self.scheduler = None
self.socketio = None
self.rd = None
self.path_app_root = None
self.path_data = None
self.users = {}
self.get_cache = None
self.__level_unset_logger_list = []
self.__logger_list = []
self.all_log_filehandler = None
self.__exit_code = -1
self.login_manager = None
#self.plugin_instance_list = {}
@@ -59,14 +63,17 @@ class Framework:
def __initialize(self):
os.environ["PYTHONUNBUFFERED"] = "1"
os.environ['FF'] = "true"
os.environ['FF_PYTHON'] = sys.executable
self.__config_initialize("first")
self.__make_default_dir()
self.logger = self.get_logger(__package__)
self.get_logger('support')
import support
self.__prepare_starting()
self.app = Flask(__name__)
self.app.wsgi_app = ProxyFix(self.app.wsgi_app, x_proto=1)
self.__config_initialize('flask')
self.__init_db()
@@ -82,7 +89,6 @@ class Framework:
self.socketio = SocketIO(self.app, cors_allowed_origins="*", async_mode='threading')
CORS(self.app)
Markdown(self.app)
self.login_manager = LoginManager()
self.login_manager.init_app(self.app)
@@ -94,12 +100,13 @@ class Framework:
self.app.config.update(
DROPZONE_MAX_FILE_SIZE = 102400,
DROPZONE_TIMEOUT = 5*60*1000,
#DROPZONE_ALLOWED_FILE_CUSTOM = True,
#DROPZONE_ALLOWED_FILE_TYPE = 'default, image, audio, video, text, app, *.*',
DROPZONE_ALLOWED_FILE_CUSTOM = True,
DROPZONE_ALLOWED_FILE_TYPE = "image/*, audio/*, video/*, text/*, application/*, *.*",
)
self.dropzone = Dropzone(self.app)
def __init_db(self):
# https://flask-sqlalchemy.palletsprojects.com/en/3.0.x/config/#flask_sqlalchemy.config.SQLALCHEMY_BINDS
# 어떤 편법도 불가. db를 사용하지 않아도 파일이 생김.
@@ -131,19 +138,20 @@ class Framework:
def __init_celery(self):
redis_port = 6379
try:
from celery import Celery
#if frame.config['use_celery'] == False or platform.system() == 'Windows':
if self.config['use_celery'] == False:
raise Exception('no celery')
raise Exception('use_celery=False')
from celery import Celery
redis_port = os.environ.get('REDIS_PORT', None)
if redis_port == None:
redis_port = self.config.get('redis_port', None)
if redis_port == None:
redis_port = '6379'
self.config['redis_port'] = redis_port
self.rd = redis.StrictRedis(host='localhost', port=redis_port, db=0)
if self.config['use_celery'] == False:
raise Exception('no celery')
self.app.config['CELERY_BROKER_URL'] = 'redis://localhost:%s/0' % redis_port
self.app.config['CELERY_RESULT_BACKEND'] = 'redis://localhost:%s/0' % redis_port
@@ -166,6 +174,7 @@ class Framework:
F.logger.info(f"celery running_type: {running_type}")
#F.logger.info(f"celery running_type: {options}")
celery.steps['worker'].add(CustomArgs)
except Exception as e:
if self.config['use_celery']:
self.logger.error('CELERY!!!')
@@ -187,6 +196,14 @@ class Framework:
if len(args) > 0 and type(args[0]) == type(dummy_func):
return args[0]
self.f(*args, **kwargs)
try:
from .init_cache_manager import _RedisManager, get_cache
_RedisManager(host='localhost', port=redis_port)
self.get_cache = get_cache
except Exception as e:
self.logger.error(f"get_cache import error: {str(e)}")
self.get_cache = None
return celery
@@ -201,11 +218,13 @@ class Framework:
self.logger.error(f'Exception:{str(e)}')
self.logger.error(traceback.format_exc())
self.SystemModelSetting = SystemInstance.ModelSetting
SystemInstance.plugin_load()
if self.config['run_flask']:
SystemInstance.plugin_load()
self.app.register_blueprint(SystemInstance.blueprint)
self.config['flag_system_loading'] = True
self.__config_initialize('member')
self.__config_initialize('system_loading_after')
self.set_level(self.SystemModelSetting.get_int('log_level'))
def initialize_plugin(self):
@@ -232,6 +251,7 @@ class Framework:
self.__make_default_logger()
self.__config_initialize("last")
self.config['loading_completed'] = True
self.logger.info('### LAST')
self.logger.info(f"### PORT: {self.config.get('port')}")
self.logger.info('### Now you can access App by webbrowser!!')
@@ -248,6 +268,7 @@ class Framework:
def __config_initialize(self, mode):
if mode == "first":
self.config = {}
self.config['loading_completed'] = False
self.config['os'] = platform.system()
self.config['flag_system_loading'] = False
#self.config['run_flask'] = True if sys.argv[0].endswith('main.py') else False
@@ -263,6 +284,8 @@ class Framework:
self.config['export_filepath'] = os.path.join(self.config['path_app'], 'export.sh')
self.config['exist_export'] = os.path.exists(self.config['export_filepath'])
self.config['recent_version'] = '--'
from .version import VERSION
self.config['version'] = VERSION
self.__process_args()
self.__load_config()
self.__init_define()
@@ -270,7 +293,7 @@ class Framework:
self.config['notify_yaml_filepath'] = os.path.join(self.config['path_data'], 'db', 'notify.yaml')
if 'running_type' not in self.config:
self.config['running_type'] = 'native'
self.pip_install()
elif mode == "flask":
self.app.secret_key = os.urandom(24)
self.app.config['TEMPLATES_AUTO_RELOAD'] = True
@@ -295,8 +318,8 @@ class Framework:
self.config['DEFINE'] = {}
# 이건 필요 없음
self.config['DEFINE']['GIT_VERSION_URL'] = 'https://raw.githubusercontent.com/flaskfarm/flaskfarm/main/lib/framework/version.py'
self.config['DEFINE']['CHANGELOG'] = 'https://flaskfarm.github.io/posts/changelog'
self.config['DEFINE']['CHANGELOG'] = 'https://github.com/flaskfarm/flaskfarm'
#self.config['DEFINE']['WEB_DIRECT_URL'] = "http://52.78.103.230:49734"
def __process_args(self):
@@ -363,6 +386,9 @@ class Framework:
self.config['debug'] = False
if self.config.get('plugin_update') == None:
self.config['plugin_update'] = True
# 2022-11-20
if self.config['debug']:
self.config['plugin_update'] = False
if self.config.get('plugin_loading_only_devpath') == None:
self.config['plugin_loading_only_devpath'] = False
if self.config.get('plugin_loading_list') == None:
@@ -402,8 +428,8 @@ class Framework:
try:
if self.config['flag_system_loading']:
try:
from system import SystemModelSetting
level = SystemModelSetting.get_int('log_level')
#from system import SystemModelSetting
level = self.SystemModelSetting.get_int('log_level')
except:
level = logging.DEBUG
if self.__level_unset_logger_list is not None:
@@ -426,7 +452,7 @@ class Framework:
return converted.timetuple()
if from_command == False:
file_formatter = logging.Formatter(u'[%(asctime)s|%(levelname)s|%(filename)s:%(lineno)s] %(message)s')
file_formatter = logging.Formatter(u'[%(asctime)s|%(levelname)s|%(name)s|%(filename)s:%(lineno)s] %(message)s')
else:
file_formatter = logging.Formatter(u'[%(asctime)s] %(message)s')
@@ -435,10 +461,18 @@ class Framework:
fileHandler = logging.handlers.RotatingFileHandler(filename=os.path.join(self.path_data, 'log', f'{name}.log'), maxBytes=file_max_bytes, backupCount=5, encoding='utf8', delay=True)
fileHandler.setFormatter(file_formatter)
logger.addHandler(fileHandler)
if name == 'framework' and self.all_log_filehandler == None:
self.all_log_filehandler = logging.handlers.RotatingFileHandler(filename=os.path.join(self.path_data, 'log', f'all.log'), maxBytes=5*1024*1024, backupCount=5, encoding='utf8', delay=True)
self.all_log_filehandler.setFormatter(file_formatter)
if from_command == False:
streamHandler = logging.StreamHandler()
streamHandler.setFormatter(CustomFormatter())
logger.addHandler(streamHandler)
if self.all_log_filehandler != None:
logger.addHandler(self.all_log_filehandler)
return logger
@@ -459,7 +493,7 @@ class Framework:
def set_level(self, level):
try:
for l in self.__logger_list:
l.setLevel(level)
l.setLevel(int(level))
self.__make_default_logger()
except:
pass
@@ -517,8 +551,8 @@ class Framework:
PluginManager.plugin_unload()
with self.app.test_request_context():
self.socketio.stop()
except Exception as exception:
self.logger.error('Exception:%s', exception)
except Exception as e:
self.logger.error(f"Exception:{str(e)}")
self.logger.error(traceback.format_exc())
def get_recent_version(self):
@@ -532,3 +566,11 @@ class Framework:
self.logger.error(traceback.format_exc())
self.config['recent_version'] = '확인 실패'
return False
# dev 도커용. package는 setup에 포함.
def pip_install(self):
try:
import json_fix
except:
os.system('pip install json_fix')

View File

@@ -1,93 +1,144 @@
import os
import shutil
import traceback
from framework import F, logger
from support import SupportYaml, d
from framework import F
class MenuManager:
menu_map = None
@classmethod
def __load_menu_yaml(cls):
menu_yaml_filepath = os.path.join(F.config['path_data'], 'db', 'menu.yaml')
if os.path.exists(menu_yaml_filepath) == False:
shutil.copy(
os.path.join(F.config['path_app'], 'files', 'menu.yaml.template'),
menu_yaml_filepath
)
cls.menu_map = SupportYaml.read_yaml(menu_yaml_filepath)
try:
menu_yaml_filepath = os.path.join(F.config['path_data'], 'db', 'menu.yaml')
if os.path.exists(menu_yaml_filepath) == False:
shutil.copy(
os.path.join(F.config['path_app'], 'files', 'menu.yaml.template'),
menu_yaml_filepath
)
cls.menu_map = SupportYaml.read_yaml(menu_yaml_filepath)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
cls.menu_map = SupportYaml.read_yaml(os.path.join(F.config['path_app'], 'files', 'menu.yaml.template'))
@classmethod
def init_menu(cls):
cls.__load_menu_yaml()
from .init_plugin import PluginManager
plugin_menus = PluginManager.plugin_menus
copy_map = []
if cls.__init_menu() == False:
cls.menu_map = SupportYaml.read_yaml(os.path.join(F.config['path_app'], 'files', 'menu.yaml.template'))
cls.__init_menu()
for category in cls.menu_map:
if 'uri' in category:
copy_map.append(category)
continue
cate_count = 0
@classmethod
def __init_menu(cls):
try:
from .init_plugin import PluginManager
plugin_menus = PluginManager.plugin_menus
copy_map = []
for category in cls.menu_map:
if 'uri' in category:
if category['uri'] in plugin_menus:
plugin_menus[category['uri']]['match'] = True
copy_map.append(plugin_menus[category['uri']]['menu'])
else:
copy_map.append(category)
continue
cate_count = 0
tmp_cate_list = []
for item in category['list']:
if item['uri'] in plugin_menus:
plugin_menus[item['uri']]['match'] = True
tmp_cate_list.append(plugin_menus[item['uri']]['menu'])
cate_count += 1
elif item['uri'].startswith('http'):
tmp_cate_list.append({
'uri': item['uri'],
'name': item['name'],
'target': item.get('target', '_blank')
})
cate_count += 1
elif (len(item['uri'].split('/')) > 1 and item['uri'].split('/')[0] in plugin_menus) or item['uri'].startswith('javascript') or item['uri'] in ['-']:
tmp_cate_list.append({
'uri': item['uri'],
'name': item.get('name', ''),
})
cate_count += 1
elif item['uri'] == 'setting':
if len(PluginManager.setting_menus) > 0:
tmp_cate_list = []
for item in category['list']:
if item['uri'] in plugin_menus:
plugin_menus[item['uri']]['match'] = True
tmp_cate_list.append(plugin_menus[item['uri']]['menu'])
cate_count += 1
elif item['uri'].startswith('http'):
tmp_cate_list.append({
'uri': item['uri'],
'name': item['name'],
'target': item.get('target', '_blank')
})
cate_count += 1
elif (len(item['uri'].split('/')) > 1 and item['uri'].split('/')[0] in plugin_menus) or item['uri'].startswith('javascript') or item['uri'] in ['-']:
tmp_cate_list.append({
'uri': item['uri'],
'name': item.get('name', ''),
'list': PluginManager.setting_menus
})
cate_count += 1
elif item['uri'] == 'setting':
# 2024.06.04
# 확장설정도 메뉴 구성
if len(PluginManager.setting_menus) > 0:
set_tmp = item.get('list')
if set_tmp:
cp = PluginManager.setting_menus.copy()
include = []
for set_ch in set_tmp:
if set_ch.get('uri') and (set_ch.get('uri') == '-' or set_ch.get('uri').startswith('http')):
include.append(set_ch)
continue
for i, ps in enumerate(cp):
if set_ch.get('plugin') != None and set_ch.get('plugin') == ps.get('plugin'):
include.append(ps)
del cp[i]
break
tmp_cate_list.append({
'uri': item['uri'],
'name': item.get('name', ''),
'list': include + cp
})
else:
tmp_cate_list.append({
'uri': item['uri'],
'name': item.get('name', ''),
'list': PluginManager.setting_menus
})
if cate_count > 0:
copy_map.append({
'name': category['name'],
'list': tmp_cate_list,
'count': cate_count
})
cls.menu_map = copy_map
make_dummy_cate = False
for name, plugin_menu in plugin_menus.items():
#F.logger.info(d(plugin_menu))
#if 'uri' not in plugin_menu['menu']:
# continue
if plugin_menu['match'] == False:
if make_dummy_cate == False:
make_dummy_cate = True
cls.menu_map.insert(len(cls.menu_map)-1, {
'name':'미분류', 'count':0, 'list':[]
})
if cate_count > 0:
copy_map.append({
'name': category['name'],
'list': tmp_cate_list,
'count': cate_count
})
cls.menu_map = copy_map
make_dummy_cate = False
for name, plugin_menu in plugin_menus.items():
#F.logger.info(d(plugin_menu))
#if 'uri' not in plugin_menu['menu']:
# continue
if plugin_menu['match'] == False:
if make_dummy_cate == False:
make_dummy_cate = True
cls.menu_map.insert(len(cls.menu_map)-1, {
'name':'미분류', 'count':0, 'list':[]
})
c = cls.menu_map[-2]
c['count'] += 1
c['list'].append(plugin_menu['menu'])
c = cls.menu_map[-2]
c['count'] += 1
c['list'].append(plugin_menu['menu'])
return True
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False
#F.logger.warning(d(cls.menu_map))
@classmethod
def get_menu_map(cls):
#F.logger.warning(d(cls.menu_map))
return cls.menu_map
@classmethod
def get_setting_menu(cls, plugin):
from .init_plugin import PluginManager
for tmp in PluginManager.setting_menus:
if tmp['plugin'] == plugin:
return tmp

View File

@@ -7,9 +7,8 @@ import traceback
import zipfile
import requests
from support import SupportFile, SupportSubprocess, SupportYaml
from framework import F
from support import SupportFile, SupportSubprocess, SupportYaml
class PluginManager:
@@ -30,13 +29,13 @@ class PluginManager:
tmps = os.listdir(plugin_path)
add_plugin_list = []
for t in tmps:
if not t.startswith('_') and os.path.isdir(os.path.join(plugin_path, t)):
if t.startswith('_') == False and t.startswith('.') == False and os.path.isdir(os.path.join(plugin_path, t)) and t != 'false' and t != 'tmp':
add_plugin_list.append(t)
cls.all_package_list[t] = {'pos':'normal', 'path':os.path.join(plugin_path, t), 'loading':(F.config.get('plugin_loading_only_devpath', None) != True)}
plugins = plugins + add_plugin_list
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
if F.config.get('plugin_loading_only_devpath', None) == True:
@@ -59,12 +58,12 @@ class PluginManager:
tmps = os.listdir(__)
add_plugin_list = []
for t in tmps:
if not t.startswith('_') and os.path.isdir(os.path.join(__, t)):
if t.startswith('_') == False and t.startswith('.') == False and os.path.isdir(os.path.join(__, t)) and t != 'false' and t != 'tmp':
add_plugin_list.append(t)
cls.all_package_list[t] = {'pos':'dev', 'path':os.path.join(__, t), 'loading':True}
plugins = plugins + add_plugin_list
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
# plugin_loading_list
@@ -79,8 +78,8 @@ class PluginManager:
cls.all_package_list[_]['loading'] = False
cls.all_package_list[_]['status'] = 'not_include_loading_list'
plugins = new_plugins
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
# plugin_except_list
@@ -95,8 +94,8 @@ class PluginManager:
cls.all_package_list[_]['loading'] = False
cls.all_package_list[_]['status'] = 'include_except_list'
plugins = new_plugins
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
return plugins
@@ -113,43 +112,26 @@ class PluginManager:
for plugin_name in plugins:
F.logger.debug(f'[+] PLUGIN LOADING Start.. [{plugin_name}]')
entity = cls.all_package_list[plugin_name]
entity['version'] = '3'
try:
mod = __import__('%s' % (plugin_name), fromlist=[])
mod_plugin_info = None
try:
mod_plugin_info = getattr(mod, 'plugin_info')
entity['module'] = mod
except Exception as exception:
F.logger.info(f'[!] PLUGIN_INFO not exist : [{plugin_name}] - is FF')
if mod_plugin_info == None:
try:
mod = __import__(f'{plugin_name}.setup', fromlist=['setup'])
entity['version'] = '4'
except Exception as e:
F.logger.error(f'Exception:{str(e)}')
F.logger.error(traceback.format_exc())
F.logger.warning(f'[!] NOT normal plugin : [{plugin_name}]')
mod = __import__(f'{plugin_name}.setup', fromlist=['setup'])
except Exception as e:
F.logger.error(f'Exception:{str(e)}')
F.logger.error(traceback.format_exc())
F.logger.warning(f'[!] NOT normal plugin : [{plugin_name}]')
continue
try:
if entity['version'] != '4':
mod_blue_print = getattr(mod, 'blueprint')
else:
entity['setup_mod'] = mod
entity['P'] = getattr(mod, 'P')
mod_blue_print = getattr(entity['P'], 'blueprint')
entity['setup_mod'] = mod
entity['P'] = getattr(mod, 'P')
mod_blue_print = getattr(entity['P'], 'blueprint')
if mod_blue_print:
F.app.register_blueprint(mod_blue_print)
except Exception as exception:
#logger.error('Exception:%s', exception)
#logger.error(traceback.format_exc())
F.logger.warning(f'[!] BLUEPRINT not exist : [{plugin_name}]')
cls.plugin_list[plugin_name] = entity
#system.LogicPlugin.current_loading_plugin_list[plugin_name]['status'] = 'success'
#system.LogicPlugin.current_loading_plugin_list[plugin_name]['info'] = mod_plugin_info
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
F.logger.debug('no blueprint')
cls.all_package_list[plugin_name]['loading'] = False
@@ -157,36 +139,50 @@ class PluginManager:
cls.all_package_list[plugin_name]['log'] = traceback.format_exc()
if not F.config['run_celery']:
try:
with F.app.app_context():
F.db.create_all()
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
F.logger.debug('db.create_all error')
if F.config['run_celery']:
for key, entity in cls.plugin_list.items():
try:
mod_plugin_load = getattr(entity['P'], 'plugin_load_celery')
if mod_plugin_load:
def func(mod_plugin_load, key):
try:
#F.logger.debug(f'[!] plugin_load_celery threading start : [{key}]')
mod_plugin_load()
#F.logger.debug(f'[!] plugin_load_celery threading end : [{key}]')
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
t = threading.Thread(target=func, args=(mod_plugin_load, key))
t.setDaemon(True)
t.start()
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
if not F.config['run_flask']:
return
for key, entity in cls.plugin_list.items():
try:
mod_plugin_load = None
if entity['version'] == '3':
mod_plugin_load = getattr(entity['module'], 'plugin_load')
elif entity['version'] == '4':
mod_plugin_load = getattr(entity['P'], 'plugin_load')
mod_plugin_load = getattr(entity['P'], 'plugin_load')
if mod_plugin_load:
def func(mod_plugin_load, key):
try:
F.logger.debug(f'[!] plugin_load threading start : [{key}]')
#mod.plugin_load()
F.logger.info(f'[!] plugin_load threading start : [{key}]')
mod_plugin_load()
F.logger.debug(f'[!] plugin_load threading end : [{key}]')
except Exception as exception:
except Exception as e:
F.logger.error('### plugin_load exception : %s', key)
F.logger.error('Exception:%s', exception)
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
cls.all_package_list[key]['loading'] = False
cls.all_package_list[key]['status'] = 'plugin_load error'
@@ -199,42 +195,29 @@ class PluginManager:
MenuManager.init_menu()
F.logger.info(f"플러그인 로딩 실패로 메뉴 삭제2 : {key}")
t = threading.Thread(target=func, args=(mod_plugin_load, key))
t.setDaemon(True)
t.start()
# mod는 위에서 로딩
if key != 'mod':
t = threading.Thread(target=func, args=(mod_plugin_load, key))
t.setDaemon(True)
t.start()
#if key == 'mod':
# t.join()
except Exception as exception:
except Exception as e:
F.logger.debug(f'[!] PLUGIN_LOAD function not exist : [{key}]')
#logger.error('Exception:%s', exception)
#logger.error(traceback.format_exc())
#logger.debug('no init_scheduler')
try:
mod_menu = None
if entity['version'] == '3':
mod_menu = getattr(entity['module'], 'menu')
elif entity['version'] == '4':
mod_menu = getattr(entity['P'], 'menu')
try:
mod_menu = getattr(entity['P'], 'menu')
if mod_menu and cls.all_package_list[key]['loading'] != False:
cls.plugin_menus[key]= {'menu':mod_menu, 'match':False}
if entity['version'] == '4':
setting_menu = getattr(entity['P'], 'setting_menu')
if setting_menu != None and cls.all_package_list[key]['loading'] != False:
F.logger.info(f"메뉴 포함 : {key}")
cls.setting_menus.append(setting_menu)
setting_menu = getattr(entity['P'], 'setting_menu')
setting_menu['plugin'] = entity['P'].package_name
if setting_menu != None and cls.all_package_list[key]['loading'] != False:
F.logger.info(f"확장 설정 : {key}")
cls.setting_menus.append(setting_menu)
except Exception as exception:
F.logger.debug('no menu')
F.logger.debug('### plugin_load threading all start.. : %s ', len(cls.plugin_list))
# 모든 모듈을 로드한 이후에 app 등록, table 생성, start
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
@@ -243,17 +226,9 @@ class PluginManager:
def plugin_unload(cls):
for key, entity in cls.plugin_list.items():
try:
if entity['version'] == '3':
mod_plugin_unload = getattr(entity['module'], 'plugin_unload')
elif entity['version'] == '4':
mod_plugin_unload = getattr(entity['P'], 'plugin_unload')
#if plugin_name == 'rss':
# continue
#mod_plugin_unload = getattr(mod, 'plugin_unload')
mod_plugin_unload = getattr(entity['P'], 'plugin_unload')
if mod_plugin_unload:
mod_plugin_unload()
#mod.plugin_unload()
except Exception as e:
F.logger.error('module:%s', key)
F.logger.error(f'Exception:{str(e)}')
@@ -267,6 +242,7 @@ class PluginManager:
@classmethod
def plugin_install(cls, plugin_git, zip_url=None, zip_filename=None):
plugin_git = plugin_git.strip()
is_git = True if plugin_git != None and plugin_git != '' else False
ret = {}
try:
@@ -381,7 +357,7 @@ class PluginManager:
tmps = os.listdir(plugins_path)
for t in tmps:
plugin_path = os.path.join(plugins_path, t)
if t.startswith('_'):
if t.startswith('_') or t.startswith('.'):
continue
if os.path.exists(os.path.join(plugin_path, '.git')):
command = ['git', '-C', plugin_path, 'reset', '--hard', 'HEAD']
@@ -392,14 +368,15 @@ class PluginManager:
F.logger.debug(ret)
else:
F.logger.debug(f"{plugin_path} not git repo")
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
@classmethod
def get_plugin_instance(cls, package_name):
try:
return cls.all_package_list[package_name]['P']
if cls.all_package_list[package_name]['loading']:
return cls.all_package_list[package_name]['P']
except:
pass

View File

@@ -4,7 +4,6 @@ import traceback
from flask import (jsonify, redirect, render_template, request,
send_from_directory)
from flask_login import login_required
from framework import F
@@ -86,27 +85,31 @@ def open_file(path):
@F.app.route("/file/<path:path>")
@F.check_api
def file2(path):
# 윈도우 drive 필요 없음
import platform
if platform.system() == 'Windows':
path = os.path.splitdrive(path)[1][1:]
return send_from_directory('/', path, as_attachment=True)
@F.app.route("/upload", methods=['GET', 'POST'])
@login_required
def upload():
try:
if request.method == 'POST':
f = request.files['file']
from werkzeug import secure_filename
from werkzeug.utils import secure_filename
upload_path = F.SystemModelSetting.get('path_upload')
os.makedirs(upload_path, exist_ok=True)
f.save(os.path.join(upload_path, secure_filename(f.filename)))
return jsonify('success')
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
return jsonify('fail')
@F.app.route("/videojs", methods=['GET', 'POST'])
@login_required
def videojs():
data = {}
data['play_title'] = request.form['play_title']
@@ -116,9 +119,33 @@ def videojs():
data['play_subtitle_src'] = request.form['play_subtitle_src']
return render_template('videojs.html', data=data)
@F.app.route("/videojs_drm", methods=['GET', 'POST'])
@login_required
def videojs_drm():
data = {}
data['play_title'] = request.form['play_title']
data['play_source_src'] = request.form['play_source_src']
data['play_source_type'] = request.form['play_source_type']
if 'play_subtitle_src' in request.form:
data['play_subtitle_src'] = request.form['play_subtitle_src']
return render_template('videojs_drm.html', data=data)
@F.app.route("/videojs_discord", methods=['GET', 'POST'])
@login_required
def videojs_og():
data = {}
"""
data['play_title'] = request.form['play_title']
data['play_source_src'] = request.form['play_source_src']
data['play_source_type'] = request.form['play_source_type']
if 'play_subtitle_src' in request.form:
data['play_subtitle_src'] = request.form['play_subtitle_src']
"""
return render_template('videojs_discord.html', data=data)
@F.app.route("/headers", methods=['GET', 'POST'])
@login_required
def headers():
from support import d
F.logger.info(d(request.headers))
@@ -127,6 +154,7 @@ def headers():
# 3.10에서 이거 필수
@F.socketio.on('connect', namespace=f'/framework')
@login_required
def connect():
pass

View File

@@ -4,6 +4,10 @@ from framework import F
def get_menu(full_query):
match = re.compile(r'\/(?P<package_name>.*?)\/(?P<module_name>.*?)\/manual\/(?P<sub2>.*?)($|\?)').match(full_query)
if match:
return match.group('package_name'), match.group('module_name'), f"manual/{match.group('sub2')}"
match = re.compile(r'\/(?P<menu>.*?)\/manual\/(?P<sub2>.*?)($|\?)').match(full_query)
if match:
return match.group('menu'), 'manual', match.group('sub2')
@@ -48,12 +52,14 @@ def jinja_initialize(app):
app.jinja_env.globals.update(get_menu=get_menu)
app.jinja_env.globals.update(get_theme=get_theme)
app.jinja_env.globals.update(get_menu_map=MenuManager.get_menu_map)
app.jinja_env.globals.update(get_setting_menu=MenuManager.get_setting_menu)
app.jinja_env.globals.update(get_web_title=get_web_title)
app.jinja_env.globals.update(dropzone=F.dropzone)
app.jinja_env.filters['get_menu'] = get_menu
app.jinja_env.filters['get_theme'] = get_theme
app.jinja_env.filters['get_menu_map'] = MenuManager.get_menu_map
app.jinja_env.filters['get_setting_menu'] = MenuManager.get_setting_menu
app.jinja_env.filters['get_web_title'] = get_web_title
app.jinja_env.auto_reload = True

View File

@@ -4,17 +4,18 @@ import time
import traceback
from flask import request
from support import SingletonClass
from framework import F
from support import SingletonClass
namespace = 'log'
@F.socketio.on('connect', namespace='/%s' % namespace)
@F.login_required
def socket_connect():
F.logger.debug('log connect')
@F.socketio.on('start', namespace='/%s' % namespace)
@F.login_required
def socket_file(data):
try:
package = filename = None
@@ -24,8 +25,8 @@ def socket_file(data):
filename = data['filename']
LogViewer.instance().start(package, filename, request.sid)
F.logger.debug('start package:%s filename:%s sid:%s', package, filename, request.sid)
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
@F.socketio.on('disconnect', namespace='/%s' % namespace)
@@ -33,8 +34,8 @@ def disconnect():
try:
LogViewer.instance().disconnect(request.sid)
F.logger.debug('disconnect sid:%s', request.sid)
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
@@ -62,18 +63,17 @@ class WatchThread(threading.Thread):
key = 'filename'
value = self.filename
if os.path.exists(logfile):
with open(logfile, 'r') as f:
with open(logfile, 'r', encoding='utf8') as f:
f.seek(0, os.SEEK_END)
while not self.stop_flag:
line = f.readline()
if not line:
time.sleep(0.1) # Sleep briefly
continue
F.socketio.emit("add", {key : value, 'data': line}, namespace='/log', broadcast=True)
F.socketio.emit("add", {key : value, 'data': line}, namespace='/log')
F.logger.debug('WatchThread.. End %s', value)
else:
F.socketio.emit("add", {key : value, 'data': 'not exist logfile'}, namespace='/log', broadcast=True)
F.socketio.emit("add", {key : value, 'data': 'not exist logfile'}, namespace='/log')
class LogViewer(SingletonClass):

View File

@@ -49,8 +49,8 @@ class Scheduler(object):
if flag_exit:
self.remove_job("scheduler_check")
#time.sleep(30)
except Exception as exception:
self.logger.error('Exception:%s', exception)
except Exception as e:
self.logger.error(f"Exception:{str(e)}")
self.logger.error(traceback.format_exc())
def shutdown(self):
@@ -233,21 +233,21 @@ class Job(object):
if self.args is None:
self.thread = threading.Thread(target=self.target_function, args=())
else:
self.thread = threading.Thread(target=self.target_function, args=(self.args,))
self.thread = threading.Thread(target=self.target_function, args=self.args)
self.thread.daemon = True
self.thread.start()
F.socketio.emit('notify', {'type':'success', 'msg':f"{self.description}<br>작업을 시작합니다." }, namespace='/framework', broadcast=True)
F.socketio.emit('notify', {'type':'success', 'msg':f"{self.description}<br>작업을 시작합니다." }, namespace='/framework')
self.thread.join()
F.socketio.emit('notify', {'type':'success', 'msg':f"{self.description}<br>작업이 종료되었습니다." }, namespace='/framework', broadcast=True)
F.socketio.emit('notify', {'type':'success', 'msg':f"{self.description}<br>작업이 종료되었습니다." }, namespace='/framework')
self.end_time = datetime.now(timezone('Asia/Seoul'))
self.running_timedelta = self.end_time - self.start_time
self.status = 'success'
if not F.scheduler.is_include(self.job_id):
F.scheduler.remove_job_instance(self.job_id)
self.count += 1
except Exception as exception:
except Exception as e:
self.status = 'exception'
F.logger.error('Exception:%s', exception)
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
finally:
self.is_running = False

BIN
lib/framework/static/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -106,3 +106,5 @@ background-color: #ffff0080 !important;
.dropdown-menu {
margin:-2px;
}
.modal { overflow: scroll !important; }

View File

@@ -0,0 +1,160 @@
h3 {
border-bottom: 1px solid #ddd;
}
.top-bar {
height: 45px;
min-height: 45px;
position: absolute;
top: 0;
right: 0;
left: 0;
}
.bars-lnk {
color: #fff;
}
.bars-lnk i {
display: inline-block;
margin-left: 10px;
margin-top: 7px;
}
.bars-lnk img {
display: inline-block;
margin-left: 10px;
margin-top: -15px;
margin-right: 15px;
height: 35px;
}
.lateral-menu {
background-color: #333;
color: rgb(144, 144, 144);
width: 300px;
}
.lateral-menu label {
color: rgb(144, 144, 144);
}
.lateral-menu-content {
padding-left: 10px;
height: 100%;
font-size: 12px;
font-style: normal;
font-variant: normal;
font-weight: bold;
line-height: 16px;
}
.lateral-menu-content .title{
padding-top: 15px;
font-size: 2em;
height: 45px;
}
.lateral-menu-content-inner {
overflow-y: auto;
height: 100%;
padding-top: 10px;
padding-bottom: 50px;
padding-right: 10px;
font-size: 0.9em;
}
#preview {
height: 97%;
max-height: 97%;
border: 1px solid #eee;
overflow-y: scroll;
width: 55%;
padding: 10px;
}
pre {
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
background-color: #f8f8f8;
border: 1px solid #dfdfdf;
margin-top: 1.5em;
margin-bottom: 1.5em;
padding: 0.125rem 0.3125rem 0.0625rem;
}
pre code {
background-color: transparent;
border: 0;
padding: 0;
}
.modal-wrapper {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 999;
background-color: rgba(51,51,51,0.5);
}
.modal-inner {
margin-top: 200px;
margin-left: auto;
margin-right: auto;
width: 600px;
height: 225px;
background-color: #fff;
opacity: 1;
z-index: 1000;
}
.modal-close-btn {
float: right;
display: inline-block;
margin-right: 5px;
color: #ff4336;
}
.modal-close-btn:hover {
float: right;
display: inline-block;
margin-right: 5px;
color: #8d0002;
}
.modal-topbar {
clear: both;
height: 25px;
}
.modal-inner .link-area {
margin: 10px;
height: 170px;
}
.modal-inner textarea {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.version {
color: white;
font-size: 0.8em !important;
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="100px" height="100px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<circle cx="50" cy="50" r="31" stroke-width="4" stroke="#e15b64" stroke-dasharray="48.69468613064179 48.69468613064179" fill="none" stroke-linecap="round">
<animateTransform attributeName="transform" type="rotate" dur="2.6315789473684212s" repeatCount="indefinite" keyTimes="0;1" values="0 50 50;360 50 50"></animateTransform>
</circle>
<circle cx="50" cy="50" r="26" stroke-width="4" stroke="#f8b26a" stroke-dasharray="40.840704496667314 40.840704496667314" stroke-dashoffset="40.840704496667314" fill="none" stroke-linecap="round">
<animateTransform attributeName="transform" type="rotate" dur="2.6315789473684212s" repeatCount="indefinite" keyTimes="0;1" values="0 50 50;-360 50 50"></animateTransform>
</circle>
<!-- [ldio] generated by https://loading.io/ --></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ if (tmp.length == 2) {
var PACKAGE_NAME = tmp[1];
var MODULE_NAME = tmp[2];
var PAGE_NAME = "";
} else if (tmp.length == 4){
} else if (tmp.length > 3){
var PACKAGE_NAME = tmp[1];
var MODULE_NAME = tmp[2];
var PAGE_NAME = tmp[3];
@@ -23,8 +23,6 @@ $(window).on("load resize", function (event) {
});
$('#command_modal').on('show.bs.modal', function (event) {
console.log('111111111')
console.log(event);
})
///////////////////////////////////////
@@ -113,7 +111,6 @@ function showModal(data='EMPTY', title='JSON', json=true) {
data = JSON.stringify(data, null, 2);
}
document.getElementById("modal_body").innerHTML = '<pre style="white-space: pre-wrap;">' +data + '</pre>';
$("#large_modal").modal();
}
@@ -168,7 +165,22 @@ function use_collapse(div, reverse=false) {
}
}
// jquery extend function
// post로 요청하면서 리다이렉트
$.extend(
{
redirectPost: function(location, args)
{
var form = '';
$.each( args, function( key, value ) {
console.log(key);
console.log(value);
value = value.split('"').join('\"')
form += '<input type="hidden" name="'+key+'" value="'+value+'">';
});
$('<form action="' + location + '" method="POST">' + form + '</form>').appendTo($(document.body)).submit();
}
});
@@ -282,20 +294,3 @@ function pad(n, width) {
return n.length >= width ? n : new Array(width - n.length + 1).join('0') + n;
}
// jquery extend function
// post로 요청하면서 리다이렉트
// 푹 자동에서 푹 기본 검색할때 사용
$.extend(
{
redirectPost: function(location, args)
{
var form = '';
$.each( args, function( key, value ) {
console.log(key);
console.log(value);
value = value.split('"').join('\"')
form += '<input type="hidden" name="'+key+'" value="'+value+'">';
});
$('<form action="' + location + '" method="POST">' + form + '</form>').appendTo($(document.body)).submit();
}
});

View File

@@ -15,11 +15,10 @@ $(document).ready(function(){
var protocol = window.location.protocol;
var frameSocket = io.connect(protocol + "//" + document.domain + ":" + location.port + "/framework");
console.log(frameSocket);
frameSocket.on('notify', function(data){
$.notify({
message : data['msg'],
message : '<strong>' + data['msg'] + '</strong>',
url: data['url'],
target: '_self'
},{
@@ -29,7 +28,7 @@ frameSocket.on('notify', function(data){
});
frameSocket.on('modal', function(data){
m_modal(data.data, data.title, false);
showModal(data.data, data.title, false);
});
frameSocket.on('loading_hide', function(data){
@@ -37,14 +36,12 @@ frameSocket.on('loading_hide', function(data){
});
frameSocket.on('refresh', function(data){
console.log('data')
window.location.reload();
});
$('#command_modal').on('hide.bs.modal', function (e) {
//e.preventDefault(); 있으면 동작 안함.
console.log("ff global command_modal hide.bs.modal CATCH")
$.ajax({
url: `/global/ajax/command_modal_hide`,
type: 'POST',
@@ -74,13 +71,27 @@ $("body").on('click', '#globalLinkBtn', function(e) {
window.location.href = url;
});
$("body").on('click', '#globalReloadBtn', function(e) {
e.preventDefault();
location.reload();
});
// global_link_btn 모두 찾아 변경
$("body").on('click', '#globalSettingSaveBtn', function(e){
e.preventDefault();
globalSettingSave();
if (globalSettingSaveBefore()) {
globalSettingSave();
}
});
function globalSettingSaveBefore() {
return true;
}
function globalSettingSaveAfter() {
return true;
}
function globalSettingSave() {
var formData = getFormdata('#setting');
$.ajax({
@@ -94,6 +105,7 @@ function globalSettingSave() {
$.notify('<strong>설정을 저장하였습니다.</strong>', {
type: 'success'
});
globalSettingSaveAfter();
} else {
$.notify('<strong>설정 저장에 실패하였습니다.</strong>', {
type: 'warning'
@@ -106,7 +118,10 @@ function globalSettingSave() {
$("body").on('click', '#globalEditBtn', function(e) {
e.preventDefault();
file = $(this).data('file');
console.log(file);
if (file == null) {
var tag = $(this).data('tag');
file = $('#' + tag).val();
}
$.ajax({
url: '/global/ajax/is_available_edit',
type: "POST",
@@ -237,106 +252,187 @@ $("body").on('click', '#globalImmediatelyExecutePageBtn', function(e){
});
$("body").on('click', '#globalDbDeleteBtn', function(e){
$("body").on('click', '#globalDbDeleteDayBtn', function(e){
e.preventDefault();
document.getElementById("confirm_title").innerHTML = "DB 삭제";
document.getElementById("confirm_body").innerHTML = "전체 목록을 삭제 하시겠습니까?";
$('#confirm_button').attr('onclick', "globalDbDelete();");
$("#confirm_modal").modal();
return;
var tag_id = $(this).data('tag_id');
var day = $('#' + tag_id).val();
globalConfirmModal('DB 삭제', "최근 " + day + "일 이내 데이터를 제외하고 삭제 하시겠습니까?", function() {
globalDbDelete(day);
});
});
function globalDbDelete() {
$("body").on('click', '#globalDbDeleteBtn', function(e){
e.preventDefault();
globalConfirmModal('DB 삭제', "전체 목록을 삭제 하시겠습니까?", function() {
globalDbDelete(0);
});
});
function globalDbDelete(day) {
$.ajax({
url: '/'+PACKAGE_NAME+'/ajax/' + MODULE_NAME + '/reset_db',
url: '/'+PACKAGE_NAME+'/ajax/' + MODULE_NAME + '/db_delete',
type: "POST",
cache: false,
data: {},
data: {day:day},
dataType: "json",
success: function (data) {
if (data) {
$.notify('<strong>삭제하였습니다.</strong>', {
type: 'success'
});
} else {
if (data == -1) {
$.notify('<strong>삭제에 실패하였습니다.</strong>',{
type: 'warning'
});
} else {
$.notify('<strong>'+data+'개를 삭제하였습니다.</strong>', {
type: 'success'
});
globalRequestSearch('1');
}
}
});
}
$("body").on('click', '#globalDbDeletePageBtn', function(e){
///////////////////////////////////////////////////
$("body").on('click', '#globalDbDeleteDayPageBtn', function(e){
e.preventDefault();
document.getElementById("confirm_title").innerHTML = "DB 삭제";
document.getElementById("confirm_body").innerHTML = "전체 목록을 삭제 하시겠습니까?";
$('#confirm_button').attr('onclick', "globalDbDeletePage();");
$("#confirm_modal").modal();
return;
var tag_id = $(this).data('tag_id');
var day = $('#' + tag_id).val();
globalConfirmModal('DB 삭제', day + "일 제외 목록을 삭제 하시겠습니까?", function() {
globalDbDeletePage(day);
});
});
function globalDbDeletePage() {
$("body").on('click', '#globalDbDeletePageBtn', function(e){
e.preventDefault();
globalConfirmModal('DB 삭제', "최근 " + day + "일 이내 데이터를 제외하고 삭제 하시겠습니까?", function() {
globalDbDeletePage(0);
});
});
function globalDbDeletePage(day) {
$.ajax({
url: '/'+PACKAGE_NAME+'/ajax/' + MODULE_NAME + '/' + PAGE_NAME + '/reset_db',
type: "POST",
cache: false,
data: {sub:sub},
data: {day:day},
dataType: "json",
success: function (data) {
if (data) {
$.notify('<strong>삭제하였습니다.</strong>', {
type: 'success'
});
} else {
if (data == -1) {
$.notify('<strong>삭제에 실패하였습니다.</strong>',{
type: 'warning'
});
} else {
$.notify('<strong>'+data+'개를 삭제하였습니다.</strong>', {
type: 'success'
});
globalRequestSearch('1');
}
}
});
}
$("body").on('click', '#globalDbDeleteItemBtn', function(e){
e.preventDefault();
var db_id = $(this).data('id');
$.ajax({
url: '/'+PACKAGE_NAME+'/ajax/' + MODULE_NAME + '/db_delete_item',
type: "POST",
cache: false,
data: {db_id:db_id},
dataType: "json",
success: function (ret) {
if (ret) {
notify('삭제하였습니다.', 'success');
globalRequestSearch(current_page);
} else {
notify('삭제에 실패하였습니다.', 'warning');
}
}
});
});
$("body").on('click', '#globalDbDeleteItemPageBtn', function(e){
e.preventDefault();
var db_id = $(this).data('id');
$.ajax({
url: '/'+PACKAGE_NAME+'/ajax/' + MODULE_NAME + '/' + PAGE_NAME + '/db_delete_item',
type: "POST",
cache: false,
data: {db_id:db_id},
dataType: "json",
success: function (ret) {
if (ret) {
notify('삭제하였습니다.', 'success');
globalRequestSearch(current_page);
} else {
notify('삭제에 실패하였습니다.', 'warning');
}
}
});
});
$("body").on('click', '#globalJsonBtn', function(e){
e.preventDefault();
showModal(current_data.list[$(this).data('idx')]);
});
///////////////////////////////////////
// Global - 함수
///////////////////////////////////////
function globalSendCommand(command, arg1, arg2, arg3, modal_title, callback) {
console.log("globalSendCommand [" + command + '] [' + arg1 + '] [' + arg2 + '] [' + arg3 + '] [' + modal_title + '] [' + callback + ']');
console.log('/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/command');
function globalSendCommand(command, arg1, arg2, arg3, callback) {
var url = '/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/command';
return globalSendCommandByUrl(url, command, arg1, arg2, arg3, callback);
}
function globalSendCommandByUrl(url, command, arg1, arg2, arg3, callback) {
$.ajax({
url: '/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/command',
url: url,
type: "POST",
cache: false,
data:{command:command, arg1:arg1, arg2:arg2, arg3},
dataType: "json",
success: function (ret) {
console.log(ret)
if (ret.msg != null) notify(ret.msg, ret.ret);
if (ret.modal != null) showModal(ret.modal, modal_title, false);
if (ret.json != null) showModal(ret.json, modal_title, true);
if (ret.modal != null) showModal(ret.modal, ret.title, false);
if (ret.json != null) showModal(ret.json, ret.title, true);
if (callback != null) callback(ret);
if (ret.reload) location.reload();
}
});
}
function globalSendCommandPage(command, arg1, arg2, arg3, modal_title, callback) {
console.log("globalSendCommandPage [" + command + '] [' + arg1 + '] [' + arg2 + '] [' + arg3 + '] [' + modal_title + '] [' + callback + ']');
console.log('/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/command');
function globalSendCommandPage(command, arg1, arg2, arg3, callback) {
var url = '/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/' + PAGE_NAME + '/command';
return globalSendCommandPageByUrl(url, command, arg1, arg2, arg3, callback);
}
function globalSendCommandPageByUrl(url, command, arg1, arg2, arg3, callback) {
$.ajax({
url: '/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/' + PAGE_NAME + '/command',
url: url,
type: "POST",
cache: false,
data:{command:command, arg1:arg1, arg2:arg2, arg3},
dataType: "json",
success: function (ret) {
if (ret.msg != null) notify(ret.msg, ret.ret);
if (ret.modal != null) m_modal(ret.modal, modal_title, false);
if (ret.json != null) m_modal(ret.json, modal_title, true);
if (ret.modal != null) showModal(ret.modal, ret.title, false);
if (ret.json != null) showModal(ret.json, ret.title, true);
if (callback != null) callback(ret);
if (ret.reload) location.reload();
}
});
}
@@ -400,6 +496,10 @@ function make_page_html(data) {
str += '<button id="gloablSearchPageBtn" data-page="' + (data.last_page+1) + '" type="button" class="btn btn-secondary">&raquo;</button>'
}
if (data.last_page != data.total_page) {
str += '<button id="gloablSearchPageBtn" data-page="' + (data.total_page) + '" type="button" class="btn btn-secondary">'+data.total_page+'</button>'
}
str += '</div> \
</div> \
</div> \
@@ -431,6 +531,22 @@ $("body").on('click', '#globalSearchResetBtn', function(e){
});
$("body").on('change', '#option1', function(e){
e.preventDefault();
globalRequestSearch(1);
});
$("body").on('change', '#option2', function(e){
e.preventDefault();
globalRequestSearch(1);
});
$("body").on('change', '#order', function(e){
e.preventDefault();
globalRequestSearch(1);
});
///////////////////////////////////////
// 파일 선택 모달
@@ -483,7 +599,6 @@ let listdir = (path = '/', only_dir = true) => {
},
dataType: 'json'
}).done((datas) => {
console.log(datas)
if (datas.length == 0) {
return false;
}
@@ -510,8 +625,6 @@ let listdir = (path = '/', only_dir = true) => {
} else {
//new_path = (path !== path_spliter) ? path + path_spliter + $(evt.currentTarget).text() : path + $(evt.currentTarget).text();
new_path = $(evt.currentTarget).data('value');
console.log(new_path)
console.log(evt)
}
*/
@@ -587,3 +700,23 @@ function ResizeTextArea() {
///////////////////////////////////////
///////////////////////////////////////
// Confirm MODAL
///////////////////////////////////////
function globalConfirmModal(title, body, func) {
$("#confirm_title").html(title);
$("#confirm_body").html(body);
//$('#confirm_button').attr('onclick', func);
$("body").on('click', '#confirm_button', function(e){
e.stopImmediatePropagation();
e.preventDefault();
func();
});
$("#confirm_modal").modal();
}

View File

@@ -0,0 +1,14 @@
///////////////////////////////////////
// 자주 사용하는 플러그인에 전용 명령
function pluginRcloneLs(remote_path) {
var url = '/rclone/ajax/config/command';
globalSendCommandByUrl(url, "ls", remote_path);
}
function pluginRcloneSize(remote_path) {
var url = '/rclone/ajax/config/command';
globalSendCommandByUrl(url, "size", remote_path);
}

View File

@@ -10,12 +10,13 @@ function j_button_group(h) {
}
// primary, secondary, success, danger, warning, info, light, dark, white
function j_button(id, text, data={}, color='primary', outline=true, small=false) {
function j_button(id, text, data={}, color='primary', outline=true, small=false, _class='') {
var str = '<button id="'+id+'" name="'+id+'" class="btn btn-sm btn';
if (outline) {
str += '-outline';
}
str += '-' + color+'';
str += ' ' + _class;
if (small) {
str += ' py-0" style="font-size: 0.8em;"';
} else {
@@ -35,9 +36,14 @@ function j_button_small(id, text, data={}, color='primary', outline=true) {
function j_row_start(padding='10', align='center') {
var str = '<div class="row" style="padding-top: '+padding+'px; padding-bottom:'+padding+'px; align-items:'+align+';">';
var str = '<div class="row chover" style="padding-top: '+padding+'px; padding-bottom:'+padding+'px; align-items:'+align+';">';
return str;
}
function j_row_start_hover(padding='10', align='center') {
var str = '<div class="row my_hover" style="padding-top: '+padding+'px; padding-bottom:'+padding+'px; align-items:'+align+';">';
return str;
}
function j_col(w, h, align='left') {
var str = '<div class="col-sm-' + w + ' " style="text-align: '+align+'; word-break:break-all;">';
str += h;
@@ -45,6 +51,13 @@ function j_col(w, h, align='left') {
return str;
}
function j_col_with_class(w, h, align='left', _class='context_menu') {
var str = '<div class="col-sm-' + w + ' '+_class+'" style="text-align: '+align+'; word-break:break-all;">';
str += h;
str += '</div>';
return str;
}
function j_col_wide(w, h, align='left') {
var str = '<div class="col-sm-' + w + ' " style="padding:0px; margin:0px; text-align: '+align+'; word-break:break-all;">';
str += h;
@@ -87,57 +100,101 @@ function j_row_info(left, right, l=2, r=8) {
function j_progress(id, width, label) {
var str = '';
str += '<div class="progress" style="height: 25px;">'
str += '<div id="'+id+'" class="progress-bar" style="background-color:yellow;width:'+width+'%"></div>';
str += '<div id="'+id+'_label" class="justify-content-center d-flex w-100 " style="margin-top:2px">'+label+'</div>';
str += '<div id="'+id+'" class="progress-bar bg-success" style="width:'+width+'%"></div>';
str += '<div id="'+id+'_label" class="justify-content-center d-flex w-100 position-absolute" style="margin-top:2px">'+label+'</div>';
str += '</div>'
return str;
}
function j_td(text, width='10', align='center', colspan='1') {
str = '<td scope="col" colspan="'+colspan+'" style="width:'+width+'%; text-align:'+align+';">'+ text + '</td>';
return str;
}
function j_th(text, width='10', align='center', colspan='1') {
str = '<th scope="col" colspan="'+colspan+'" style="width:'+width+'%; text-align:'+align+';">'+ text + '</td>';
return str;
}
function make_log(key, value, left=2, right=10) {
row = m_col(left, key, aligh='right');
row += m_col(right, value, aligh='left');
function j_info_text(key, value, left=2, right=10) {
row = j_row_start(0);
row += j_col(left, '<strong>' + key + '</strong>', aligh='right');
row += j_col(right, value, aligh='left');
row += j_row_end();
return row;
}
function j_info_text_left(key, value, left=3, right=9) {
row = j_row_start(0);
row += j_col(left, '<strong>' + key + '</strong>', aligh='left');
row += j_col(right, value, aligh='left');
row += j_row_end();
return row;
}
function j_tab_make(data) {
str = '<nav><div class="nav nav-tabs" id="nav-tab" role="tablist">';
for (i in data) {
if (data[i][2]) {
str += '<a class="nav-item nav-link active" id="tab_head_'+data[i][0]+'" data-toggle="tab" href="#tab_content_'+data[i][0]+'" role="tab">'+data[i][1]+'</a>';
} else {
str += '<a class="nav-item nav-link" id="tab_head_'+data[i][0]+'" data-toggle="tab" href="#tab_content_'+data[i][0]+'" role="tab">'+data[i][1]+'</a>';
}
}
str += '</div></nav>';
str += '<div class="tab-content" id="nav-tabContent">';
for (i in data) {
if (data[i][2]) {
str += '<div class="tab-pane fade show active" id="tab_content_'+data[i][0]+'" role="tabpanel" ></div>';
} else {
str += '<div class="tab-pane fade show" id="tab_content_'+data[i][0]+'" role="tabpanel" ></div>';
}
}
str += '</div>';
return str;
}
// javascript에서 화면 생성
function text_color(text, color='red') {
return '<span style="color:'+color+'; font-weight:bold">' + text + '</span>';
}
function j_pre(text) {
return '<pre style="word-wrap: break-word;white-space: pre-wrap;white-space: -moz-pre-wrap;white-space: -pre-wrap;white-space: -o-pre-wrap;word-break:break-all;">'+text+'</pre>';
}
@@ -277,10 +334,7 @@ document.addEventListener("DOMContentLoaded", function(){
function m_row_start_hover(padding='10', align='center') {
var str = '<div class="row my_hover" style="padding-top: '+padding+'px; padding-bottom:'+padding+'px; align-items:'+align+';">';
return str;
}
function m_row_start_top(padding='10') {
return m_row_start(padding, 'top');
}
@@ -309,46 +363,5 @@ function m_row_start_color2(padding='10', align='center') {
function m_tab_head(name, active) {
if (active) {
var str = '<a class="nav-item nav-link active" id="id_'+name+'" data-toggle="tab" href="#'+name+'" role="tab">'+name+'</a>';
} else {
var str = '<a class="nav-item nav-link" id="id_'+name+'" data-toggle="tab" href="#'+name+'" role="tab">'+name+'</a>';
}
return str;
}
function m_tab_content(name, content, active) {
if (active) {
var str = '<div class="tab-pane fade show active" id="'+name+'" role="tabpanel" >';
} else {
var str = '<div class="tab-pane fade show" id="'+name+'" role="tabpanel" >';
}
str += content;
str += '</div>'
return str;
}
function m_progress2(id, width, label) {
var str = '';
str += '<div class="progress" style="height: 25px;">'
str += '<div id="'+id+'" class="progress-bar" style="background-color:yellow;width:'+width+'%"></div>';
str += '<div id="'+id+'_label" class="justify-content-center d-flex w-100 position-absolute" style="margin:0px; margin-top:2px">'+label+'</div>';
str += '</div>'
return str;
}

View File

@@ -0,0 +1,35 @@
//
// Google Prettify
// A showdown extension to add Google Prettify (http://code.google.com/p/google-code-prettify/)
// hints to showdown's HTML output.
//
(function () {
var prettify = function () {
return [
{
type: 'output',
filter: function (source) {
return source.replace(/(<pre[^>]*>)?[\n\s]?<code([^>]*)>/gi, function (match, pre, codeClass) {
if (pre) {
return '<pre class="prettyprint linenums"><code' + codeClass + '>';
} else {
return ' <code class="prettyprint">';
}
});
}
}
];
};
// Client-side export
if (typeof window !== 'undefined' && window.showdown && window.showdown.extensions) {
window.showdown.extensions.prettify = prettify;
}
// Server-side export
if (typeof module !== 'undefined') {
module.exports = prettify;
}
}());

File diff suppressed because one or more lines are too long

View File

@@ -35,6 +35,12 @@
<script src="https://cdn.jsdelivr.net/gh/gitbrent/bootstrap4-toggle@3.4.0/js/bootstrap4-toggle.min.js"></script>
<!-- end 토글 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.8.0/jquery.contextMenu.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.8.0/jquery.contextMenu.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.8.0/jquery.ui.position.js"></script>
</head>
<body class="body ">
@@ -50,7 +56,8 @@
</div>
</main>
<div class="loading" id="loading">
<img src="/static/img/loading.gif" />
<!-- <img src="/static/img/loading.gif" /> -->
<img src="/static/img/loader.svg" />
</div>
{{ modals() }}
</body>
@@ -59,3 +66,5 @@
<!-- 글로벌 버튼이 모두 나오고 처리-->
<script src="{{ url_for('static', filename='js/sjva_global1.js') }}"></script>
<script src="{{ url_for('static', filename='js/ff_global1.js') }}"></script>
<script src="{{ url_for('static', filename='js/ff_global_plugin.js') }}"></script>

View File

@@ -8,13 +8,13 @@
{{ macros.m_tab_head_end() }}
</nav>
<div class="tab-content" id="nav-tabContent">
{{ macros.m_tab_content_start('이전', true) }}
{{ macros.m_tab_content_start('old', true) }}
<div>
<textarea id="log" class="col-md-12" rows="30" charswidth="23" disabled style="background-color:#ffffff;visibility:hidden"></textarea>
</div>
{{ macros.m_tab_content_end() }}
{{ macros.m_tab_content_start('실시간', false) }}
{{ macros.m_tab_content_start('new', false) }}
<div>
<textarea id="add" class="col-md-12" rows="30" charswidth="23" disabled style="background-color:#ffffff;visibility:visible"></textarea>
</div>
@@ -52,6 +52,7 @@ $(window).resize(function() {
var protocol = window.location.protocol;
var socket = io.connect(protocol + "//" + document.domain + ":" + location.port + "/log");
socket.emit("start", {'package':'{{package}}'} );
socket.on('on_start', function(data){
document.getElementById("log").innerHTML += data.data;

View File

@@ -25,11 +25,11 @@
<!---->
{% macro m_tab_head_start() %}
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<div class="nav nav-tabs" id="nav-tab" role="tablist">
{% endmacro %}
{% macro m_tab_head_end() %}
</div>
</div>
{% endmacro %}
{% macro m_tab_head(name, title, active) %}
@@ -39,12 +39,25 @@
<a class="nav-item nav-link" id="tab_{{name}}" data-toggle="tab" href="#{{name}}" role="tab">{{title}}</a>
{% endif %}
{% endmacro %}
<!----------------------------------------------------------------->
<!-- SETTING -->
<!-- SETTING -->
<!-- SETTING -->
<!------------------------------------------------------------------>
<!-- 설정 -->
<!-- SETTING 기본 틀-->
{% macro setting_top(left='', padding='10') %}
@@ -57,6 +70,16 @@
<div class='col-sm-9'>
{% endmacro %}
{% macro setting_top_big(left='', padding='10') %}
<div class='row' style="padding-top: {{padding}}px; padding-bottom:{{padding}}px; align-items: center;">
<div class='col-sm-3 set-left'>
{% if left != '' %}
<strong><h4>{{ left }}</h4></strong>
{% endif %}
</div>
<div class='col-sm-9'>
{% endmacro %}
{% macro setting_bottom(desc=None, padding_top='5') %}
{% if desc is not none %}
<div style="padding-left:20px; padding-top:{{padding_top}}px;">
@@ -247,18 +270,39 @@
<!-- 스케쥴링 작동 버튼-->
{% macro setting_global_scheduler_button(is_include, is_running, id='scheduler', left='스케쥴링 작동', desc=['On : 스케쥴링 시작','Off : 스케쥴링 중지']) %}
{% macro global_setting_scheduler_button(is_include, is_running, id='scheduler', left='스케쥴링 작동', desc=['On : 스케쥴링 시작','Off : 스케쥴링 중지']) %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
{% if is_include == True %}
{% if is_include == True or is_include == "True" %}
<input id="globalSchedulerSwitchBtn" name="globalSchedulerSwitchBtn" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
{% else %}
<input id="globalSchedulerSwitchBtn" name="globalSchedulerSwitchBtn" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
{% endif %}
{% if is_running == True %}
{% if is_running == True or is_running == "True" %}
<span style="padding-left:10px; padding-top: 8px; font-weight: bold;">실행중</span>
{% else %}
{% if is_include == True %}
{% if is_include == True or is_include == "True" %}
<span style="padding-left:10px; padding-top: 8px; ">대기중</span>
{% endif %}
{% endif %}
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
<!-- 스케쥴링 작동 버튼 페이지 -->
{% macro global_setting_scheduler_button_page(is_include, is_running, id='scheduler', left='스케쥴링 작동', desc=['On : 스케쥴링 시작','Off : 스케쥴링 중지']) %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
{% if is_include == True or is_include == "True" %}
<input id="globalSchedulerSwitchPageBtn" name="globalSchedulerSwitchPageBtn" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
{% else %}
<input id="globalSchedulerSwitchPageBtn" name="globalSchedulerSwitchPageBtn" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
{% endif %}
{% if is_running == True or is_running == "True" %}
<span style="padding-left:10px; padding-top: 8px; font-weight: bold;">실행중</span>
{% else %}
{% if is_include == True or is_include == "True" %}
<span style="padding-left:10px; padding-top: 8px; ">대기중</span>
{% endif %}
{% endif %}
@@ -268,13 +312,116 @@
setting_gole
<!-- NOT SETTING -->
<!-- NOT SETTING -->
<!-- NOT SETTING -->
<!-- SELECT Dummy
option을 script로 넣을 때 사용
예: 시스템 - 전체로그
-->
{% macro setting_select_empty(id, title, col='9', desc=None, value=None) %}
{{ setting_top(title) }}
<div class="input-group col-sm-{{col}}">
<div id="{{id}}_div" name="{{id}}_div"></div>
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
{% macro setting_input_int(id, left, value='', min='', max='', placeholder='', desc=None) %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
<input id="{{ id }}" name="{{ id }}" type="number" class="form-control form-control-sm"
{% if min != '' %}
min="{{ min }}"
{% endif %}
{% if max != '' %}
max="{{ max }}"
{% endif %}
{% if placeholder != '' %}
placeholder="{{ placeholder }}"
{% endif %}
value="{{ value }}">
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
<!-- 토글버튼형식 -->
{% macro setting_checkbox(id, left, value, desc='') %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
{% if value == True or value == 'True' or value == 'true' or value == 'On' %}
<input id="{{ id }}" name="{{ id }}" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
{% else %}
<input id="{{ id }}" name="{{ id }}" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
{% endif %}
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
<!------------------------------------------------------------------>
<!-- 설정 외 -->
<!-- 리스트 div로 꾸밀때 헤드 -->
{% macro m_hr_head_top() %}
<div class="d-inline-block"></div>
<hr style="width: 100%; margin:0px; background-color:#808080;">
{% endmacro %}
{% macro m_hr_head_bottom() %}
<hr style="width: 100%; margin:0px; margin-bottom:10px; margin-top:2px; background-color:#808080; height:2px" />
{% endmacro %}
<!-- 버튼 그룹 -->
{% macro m_button_group(buttons) %}
<div class="btn-group btn-group-sm flex-wrap mr-2" role="group">
@@ -304,6 +451,14 @@
{{ setting_bottom(desc, padding_top='-5') }}
{% endmacro %}
{% macro info_text_big(id, left, value='', desc=None) %}
{{ setting_top_big(left) }}
<div style="padding-left:20px; padding-top:-5px;">
<span id={{id}}><h4>{{value}}</h4></span>
</div>
{{ setting_bottom(desc, padding_top='-5') }}
{% endmacro %}
{% macro info_text_go(id, left, value='', desc=None, padding=10) %}
{{ setting_top(left, padding) }}
<div style="padding-left:20px; padding-top:-5px;">
@@ -354,219 +509,208 @@
<!-- SELECT Dummy
option을 script로 넣을 때 사용
예: 시스템 - 전체로그
-->
{% macro setting_select_empty(id, title, col='9', desc=None, value=None) %}
{{ setting_top(title) }}
<div class="input-group col-sm-{{col}}">
<div id="{{id}}_div" name="{{id}}_div"></div>
{% macro m_modal_start(id, title, size) %}
<!-- Modal -->
<div class="modal fade" id="{{id}}" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog {{size}}">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="{{id}}_title">{{title}}</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body" id="{{id}}_modal_body" style="word-break:break-all;">
{% endmacro %}
{% macro m_modal_end() %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-warning" data-dismiss="modal">닫기 (취소)</button>
</div>
<div class="loading" id="modal_loading">
<img src="/static/img/loading.gif" />
</div>
</div>
</div>
{{ setting_bottom(desc) }}
</div>
<!-- Modal end -->
{% endmacro %}
<!-- 삭제해야함 --------------------------------------------------------->
<!--
{% macro setting_radio(id, title, radios, value=None, desc=None, disabled=False) %}
{{ setting_top(title) }}
<div class="input-group col-sm-9">
{% for r in radios %}
<div class="custom-control custom-radio custom-control-inline">
{% if value|int == loop.index0 %}
<input id="{{id}}{{loop.index0}}" type="radio" class="custom-control-input" name="{{id}}" value="{{loop.index0}}" checked {% if disabled %} disabled {% endif %}>
{% else %}
<input id="{{id}}{{loop.index0}}" type="radio" class="custom-control-input" name="{{id}}" value="{{loop.index0}}" {% if disabled %} disabled {% endif %}>
{% endif %}
<label class="custom-control-label" for="{{id}}{{loop.index0}}">{{r}}</label>
{% macro m_modal_end_with_button(buttons) %}
</div>
<div class="modal-footer">
<div class="btn-group btn-group-sm flex-wrap mr-2" role="group">
{% for b in buttons %}
<button id="{{b[0]}}" class="btn btn-sm btn-outline-primary"
{% if b|length > 2 %}
{% for d in b[2] %}
data-{{d[0]}}="{{d[1]}}""
{% endfor %}
{% endif %}
>{{b[1]}}</button>
{% endfor %}
<button type="button" class="btn btn-sm btn-warning" data-dismiss="modal">닫기 (취소)</button>
</div>
</div>
<div class="loading" id="modal_loading">
<img src="/static/img/loading.gif" />
</div>
{% endfor %}
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
-->
<!-- 그룹화 하지 않음.. 삭제-->
<!--
{% macro setting_button(buttons, left='', desc='') %}
{{ setting_top(left) }}
<div class="input-group col-sm-9">
{% for b in buttons %}
{% if not loop.first %}
<span class='text-left' style="padding-left:5px; padding-top:0px">
{% endif %}
<button id="{{b[0]}}" class="btn btn-sm btn-outline-primary">{{b[1]}}</button>
</span>
{% endfor %}
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
-->
<!----------------------------------------------------------------->
{% macro setting_input_int(id, left, value='', min='', max='', placeholder='', desc=None) %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
<input id="{{ id }}" name="{{ id }}" type="number" class="form-control form-control-sm"
{% if min != '' %}
min="{{ min }}"
{% endif %}
{% if max != '' %}
max="{{ max }}"
{% endif %}
{% if placeholder != '' %}
placeholder="{{ placeholder }}"
{% endif %}
value="{{ value }}">
</div>
{{ setting_bottom(desc) }}
</div>
</div>
<!-- Modal end -->
{% endmacro %}
<!-- 토글버튼형식 -->
{% macro setting_checkbox(id, left, value, desc='') %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
{% if value == 'True' or value == 'true' or value == 'On' %}
<input id="{{ id }}" name="{{ id }}" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
{% else %}
<input id="{{ id }}" name="{{ id }}" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
{% endif %}
</div>
{{ setting_bottom(desc) }}
{% macro print_md(id, text) %}
<div id="{{id}}_div" data-text="{{text}}"></div>
<script type="text/javascript">
ret = converter.makeHtml($('#{{id}}_div').data('text'));
$('#{{id}}_div').html(ret);
</script>
{% endmacro %}
{% macro print_md1(id, text) %}
<script type="text/javascript">
ret = converter.makeHtml($('#{{id}}_div').data('text'));
</script>
{% endmacro %}
<!----------------------------------------------------------->
<!----------------------------------------------------------->
<!----------------------------------------------------------->
<!----------------------------------------------------------->
<!----------------------------------------------------------->
<!--이하 정리 필요------------------------>
<!-- 일반적인 체크박스 -->
{% macro setting_default_checkbox(id, left, label, value, desc='') %}
@@ -584,31 +728,6 @@ option을 script로 넣을 때 사용
{{ setting_bottom(desc) }}
{% endmacro %}
<!-- 스케쥴러 스위치 체크박스 전용-->
{% macro setting_scheduler_switch(left='스케쥴링 작동', desc=['On : 스케쥴링 시작','Off : 스케쥴링 중지'], is_include='False', is_running='False') %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
{% if is_include == 'True' %}
<input id="scheduler_swtich_btn" name="scheduler_swtich_btn" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
{% else %}
<input id="scheduler_swtich_btn" name="scheduler_swtich_btn" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
{% endif %}
{% if is_running == 'True' %}
<span style="padding-left:10px; padding-top: 8px;">동작중</span>
{% else %}
{% if is_include == 'True' %}
<span style="padding-left:10px; padding-top: 8px;">대기중</span>
{% endif %}
{% endif %}
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
<!--
@@ -637,6 +756,26 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
{% macro select(id, options, col='3', value=None) %}
<div class="input-group col-sm-{{col}}" style="padding-left:0px; padding-top:0px">
<select id="{{id}}" name="{{id}}" class="form-control form-control-sm">
{% for item in options %}
{% if value is not none and value == item[0] %}
<option value="{{ item[0] }}" selected>{{item[1]}}</option>
{% else %}
<option value="{{ item[0] }}">{{item[1]}}</option>
{% endif %}
{% endfor %}
</select>
</div>
{% endmacro %}
<!-- select -->
{% macro setting_select(id, title, options, col='9', desc=None, value=None) %}
{{ setting_top(title) }}
@@ -655,21 +794,6 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
{{ setting_bottom(desc) }}
{% endmacro %}
{% macro select(id, options, col='3', value=None) %}
<div class="input-group col-sm-{{col}}" style="padding-left:0px; padding-top:0px">
<select id="{{id}}" name="{{id}}" class="form-control form-control-sm">
{% for item in options %}
{% if value is not none and value == item[0] %}
<option value="{{ item[0] }}" selected>{{item[1]}}</option>
{% else %}
<option value="{{ item[0] }}">{{item[1]}}</option>
{% endif %}
{% endfor %}
</select>
</div>
{% endmacro %}
<!-- select + 버튼 -->
@@ -703,15 +827,6 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
<!--progress-bar-striped progress-bar-animated-->
{% macro setting_progress(id, left='', desc='') %}
{{ setting_top(left) }}
@@ -725,66 +840,6 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
{% endmacro %}
<!-- 스케쥴링 작동 버튼-->
{% macro setting_scheduler_button(is_include, is_running, id='scheduler', left='스케쥴링 작동', desc=['On : 스케쥴링 시작','Off : 스케쥴링 중지']) %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
{% if is_include == 'True' %}
<input id="scheduler" name="scheduler" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
{% else %}
<input id="scheduler" name="scheduler" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
{% endif %}
{% if is_running == 'True' %}
<span style="padding-left:10px; padding-top: 8px;">동작중</span>
{% else %}
{% if is_include == 'True' %}
<span style="padding-left:10px; padding-top: 8px;">대기중</span>
{% endif %}
{% endif %}
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
{% macro setting_global_scheduler_sub_button(is_include, is_running, id='scheduler', left='스케쥴링 작동', desc=['On : 스케쥴링 시작','Off : 스케쥴링 중지']) %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
{% if is_include == 'True' %}
<input id="global_scheduler_sub" name="global_scheduler_sub" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
{% else %}
<input id="global_scheduler_sub" name="global_scheduler_sub" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
{% endif %}
{% if is_running == 'True' %}
<span style="padding-left:10px; padding-top: 8px;">동작중</span>
{% else %}
{% if is_include == 'True' %}
<span style="padding-left:10px; padding-top: 8px;">대기중</span>
{% endif %}
{% endif %}
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
{% macro setting_global_scheduler_sublogic_button(is_include, is_running, id='scheduler', left='스케쥴링 작동', desc=['On : 스케쥴링 시작','Off : 스케쥴링 중지']) %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
{% if is_include == 'True' %}
<input id="global_scheduler_sublogic" name="global_scheduler_sublogic" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
{% else %}
<input id="global_scheduler_sublogic" name="global_scheduler_sublogic" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
{% endif %}
{% if is_running == 'True' %}
<span style="padding-left:10px; padding-top: 8px;">동작중</span>
{% else %}
{% if is_include == 'True' %}
<span style="padding-left:10px; padding-top: 8px;">대기중</span>
{% endif %}
{% endif %}
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
@@ -803,14 +858,7 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
{% endmacro %}
{% macro m_hr_head_top() %}
<div class="d-inline-block"></div>
<hr style="width: 100%; margin:0px; background-color:#808080;">
{% endmacro %}
{% macro m_hr_head_bottom() %}
<hr style="width: 100%; margin:0px; margin-bottom:10px; margin-top:2px; background-color:#808080; height:2px" />
{% endmacro %}
{% macro m_button(id, text) %}
<button id="{{id}}" name="{{id}}" class="btn btn-sm btn-outline-primary">{{text}}</button>
@@ -836,59 +884,6 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
{% macro m_modal_start(id, title, size) %}
<!-- Modal -->
<div class="modal fade" id="{{id}}" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog {{size}}">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="{{id}}_title">{{title}}</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body" id="modal_body" style="word-break:break-all;">
{% endmacro %}
{% macro m_modal_end() %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">닫기</button>
</div>
<div class="loading" id="modal_loading">
<img src="/static/img/loading.gif" />
</div>
</div>
</div>
</div>
<!-- Modal end -->
{% endmacro %}
{% macro m_modal_start2(id, title, size) %}
<!-- Modal -->
<div class="modal fade" id="{{id}}" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog {{size}}">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="{{id}}_title">{{title}}</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
</div>
<div class="loading" id="modal_loading">
<img src="/static/img/loading.gif" />
</div>
{% endmacro %}
{% macro m_modal_end2() %}
</div>
</div>
</div>
<!-- Modal end -->
{% endmacro %}
{% macro row_start(padding='10') %}
<div class='row' style="padding-top: {{padding}}px; padding-bottom:{{padding}}px; align-items: center;">
@@ -1002,16 +997,3 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
<!-- 다른이름으로 정의함. 나중에 삭제 -->
{% macro buttons(buttons, left='', desc='') %}
{{ setting_top(left) }}
<div class="input-group col-sm-9">
<div class="btn-group btn-group-sm flex-wrap mr-2" role="group">
{% for b in buttons %}
<button id="{{b[0]}}" class="btn btn-sm btn-outline-primary">{{b[1]}}</button>
{% endfor %}
</div>
</div>
{{ setting_bottom(desc) }}
{% endmacro %}

View File

@@ -14,7 +14,7 @@
<div class="modal-body" id="modal_body" style="word-break:break-all;">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">닫기</button>
<button type="button" class="btn btn-warning" data-dismiss="modal">닫기</button>
<!--<button type="button" class="btn btn-primary">Save changes</button>-->
</div>
</div>
@@ -70,7 +70,7 @@
<div class="modal-footer">
<button type="button" id='select_local_file_modal_confirm_btn' class="btn btn-success" data-dismiss="modal">선택
</button>
<button type="button" id='select_local_file_modal_cancel_btn' class="btn btn-default" data-dismiss="modal">닫기
<button type="button" id='select_local_file_modal_cancel_btn' class="btn btn-warning" data-dismiss="modal">닫기
</button>
</div>
</div>

View File

@@ -19,7 +19,7 @@
{% if 'uri' in category and category['uri'].startswith('http') %}
<li class="nav-item"> <a class="nav-link" href="{{ category['uri']}}" target="_blank">{{category['name']}}</a></li>
{% elif 'uri' in category and category['uri'].startswith('http') == False %}
<li class="nav-item"> <a class="nav-link" href="{{ category['uri']}}">{{category['name']}}</a></li>
<li class="nav-item"> <a class="nav-link" href="/{{ category['uri']}}">{{category['name']}}</a></li>
{% else %}
<!--{{ category }}-->
<li class="nav-item dropdown">
@@ -134,10 +134,11 @@
{% if current_menu[0] == plugin['uri'] and 'list' in plugin %}
{% for module in plugin['list'] %}
{% if module['uri'] == current_menu[1] and 'list' in module%}
<!--{{ module }}-->
<!-- {{ module }} -->
<ul class="nav nav-pills bg-light shadow text-dark">
{% for page in module['list'] %}
{% if current_menu[2] == page['uri'] %}
<!--{{ current_menu }}-->
{% if current_menu[2]!= None and page['uri'].startswith(current_menu[2]) %}
<li class="nav-item"><a class="nav-link active" href="/{{ current_menu[0] }}/{{ current_menu[1] }}/{{ page['uri'] }}">{{page['name']}}</a></li>
{% else %}
<li class="nav-item"><a class="nav-link" href="/{{ current_menu[0] }}/{{ current_menu[1] }}/{{ page['uri'] }}">{{page['name']}}</a></li>

View File

@@ -1,30 +1,32 @@
{% extends "base.html" %}
{% block content %}
{% filter markdown %}
{{ data }}
{% endfilter %}
<script src="https://cdn.jsdelivr.net/gh/google/code-prettify@master/loader/run_prettify.js?autorun=true&amp;lang=css&lang=python&skin=sunburst"></script>
<style type="text/css">
img{
<script src="{{ url_for('static', filename='js/showdown_2.1.0.js') }}"></script>
<script src="{{ url_for('static', filename='js/showdown-prettify.js') }}"></script>
<link href="{{ url_for('static', filename='css/showdown.css') }}" rel="stylesheet">
display: block;
max-width: 100%;
margin-right: auto;
}
</style>
<div id="md_div" data-url="{{ arg }}"></div>
<div id="content_div" data-url="{{ arg }}"></div>
<meta id="text" data-text="{{data}}">
<div id="text_div"></div>
<script type="text/javascript">
$(document).ready(function(){
//$('#main_container').attr('class', 'container-fluid');
});
$(document).ready(function(){
var converter = new showdown.Converter({extensions: ['prettify']});
converter.setOption('tables', true);
converter.setOption('strikethrough', true);
converter.setOption('ghCodeBlocks',true);
text = $('#text').data('text');
if (window.location.href.endsWith('.yaml')) {
text = "```" + text + "```";
}
html = converter.makeHtml(text);
$('#text_div').html(html);
});
</script>
{% endblock %}

View File

@@ -0,0 +1,30 @@
{% extends "base.html" %}
{% block content %}
{% filter markdown %}
{{ data }}
{% endfilter %}
<style type="text/css">
img{
display: block;
max-width: 100%;
margin-right: auto;
}
</style>
<div id="md_div" data-url="{{ arg }}"></div>
<div id="content_div" data-url="{{ arg }}"></div>
<script type="text/javascript">
$(document).ready(function(){
//$('#main_container').attr('class', 'container-fluid');
});
</script>
{% endblock %}

View File

@@ -1,3 +1,5 @@
<html>
<title>{{data['play_title']}}</title>
<script src="https://vjs.zencdn.net/7.11.4/video.min.js"></script>
<link href="https://vjs.zencdn.net/7.11.4/video-js.css" rel="stylesheet" />
@@ -63,3 +65,4 @@ player.ready(function(){
player.play();
</script>
</html>

View File

@@ -0,0 +1,90 @@
<title>aaaa</title>
<meta charset="UTF-8">
<meta content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" name="viewport">
<meta content="ie=edge" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1" name="viewport">
<link href="/media/avatar.png" rel="icon" type="image/jpeg">
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" rel="stylesheet" />
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet" />
<link href="https://unpkg.com/swiper@7/swiper-bundle.min.css" rel="stylesheet" />
<link href='https://unpkg.com/boxicons@2.1.2/css/boxicons.min.css' rel='stylesheet'>
<link href="/css/style.css" rel="stylesheet" />
<link href="/css/style_dark.css" rel="stylesheet" />
<link href="/media/favicon.png" rel="icon" type="image/jpeg">
<meta property="og:site_name" content="aaaaaaaaaaaaaaaaa" />
<meta property="og:url" content="https://ff.soju6jan.synology.me/gds_tool/api/route/streaming.mp4?apikey=ooo5298ooo&type=file&id=1gtpG7CAUKTWu6wxWtCKx-XN01PMz70v8" />
<meta property="og:type" content="video.other" />
<meta property="og:title" content="Mini rengar xD" />
<meta property="og:image" content="https://outplays.eu/Q5THkfY3/thumbnail.png" />
<meta property="og:video" content="https://ff.soju6jan.synology.me/gds_tool/api/route/streaming.mp4?apikey=ooo5298ooo&type=file&id=1gtpG7CAUKTWu6wxWtCKx-XN01PMz70v8" />
<meta property="og:video:type" content="video/mp4" />
<meta property="og:video:secure_url" content="https://ff.soju6jan.synology.me/gds_tool/api/route/streaming.mp4?apikey=ooo5298ooo&type=file&id=1gtpG7CAUKTWu6wxWtCKx-XN01PMz70v8" />
<meta property="og:video:height" content="1080" />
<meta property="og:video:width" content="1920" />
<meta property="og:image:height" content="1080" />
<meta property="og:image:width" content="1920" />
<script src="https://vjs.zencdn.net/7.11.4/video.min.js"></script>
<link href="https://vjs.zencdn.net/7.11.4/video-js.css" rel="stylesheet" />
<body bgcolor='black'>
<video id=player width=960 height=540 class="video-js vjs-default-skin vjs-16-9" autoplay controls>
<source
src="https://ff.soju6jan.synology.me/gds_tool/api/route/streaming.mp4?apikey=ooo5298ooo&type=file&id=1gtpG7CAUKTWu6wxWtCKx-XN01PMz70v8"
type="application/x-mpegURL" />
</video>
</body>
<script>
var subtitle_src = "aaaa";
let options = {
html5: {
nativeTextTracks: false
},
playbackRates: [.5, .75, 1, 1.5, 2],
controls: true,
preload: "auto",
controlBar: {
playToggle: false,
pictureInPictureToggle: false,
remainingTimeDisplay: true,
qualitySelector: true,
}
};
let player = videojs('player', options);
player.ready(function(){
// set subtitle track
if (subtitle_src != "") {
var suburl = subtitle_src.replace(/&amp;/g, '&');
let captionOption = {
kind: 'captions',
srclang: 'ko',
label: 'Korean',
src: suburl,
mode: 'showing'
};
player.addRemoteTextTrack(captionOption);
var settings = this.textTrackSettings;
settings.setValues({
"backgroundColor": "#000",
"backgroundOpacity": "0",
"edgeStyle": "uniform",
});
settings.updateDisplay();
}
else {
var tracks = player.textTracks();
for (var i = 0; i < tracks.length; i++) {
var track = tracks[i];
}
}
});
player.play();
</script>

View File

@@ -34,8 +34,8 @@ class Util(object):
paging['count'] = count
F.logger.debug('paging : c:%s %s %s %s %s %s', count, paging['total_page'], paging['prev_page'], paging['next_page'] , paging['start_page'], paging['last_page'])
return paging
except Exception as exception:
F.logger.debug('Exception:%s', exception)
except Exception as e:
F.logger.debug(f"Exception:{str(e)}")
F.logger.debug(traceback.format_exc())
@@ -60,8 +60,8 @@ class Util(object):
ret['dirname'] = max_filename.replace('/%s' % ret['filename'], '')
ret['max_size'] = max_size
return ret
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
@@ -81,8 +81,8 @@ class Util(object):
import shutil
shutil.rmtree(zip_path)
return True
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
return False
@@ -92,12 +92,12 @@ class Util(object):
def make_apikey(url):
from framework import SystemModelSetting
url = url.format(ddns=SystemModelSetting.get('ddns'))
if SystemModelSetting.get_bool('auth_use_apikey'):
if SystemModelSetting.get_bool('use_apikey'):
if url.find('?') == -1:
url += '?'
else:
url += '&'
url += 'apikey=%s' % SystemModelSetting.get('auth_apikey')
url += 'apikey=%s' % SystemModelSetting.get('apikey')
return url

View File

@@ -1 +1 @@
VERSION="4.0.47"
VERSION="4.1.40"

View File

@@ -1,5 +1,6 @@
# 순서 바꾸지 말 것
import os, sys, traceback, re, threading, time, queue
import os, sys, traceback, re, threading, time, queue, json, shutil, yaml
import requests
from datetime import datetime, timedelta
from flask import Blueprint, render_template, jsonify, redirect, request
from sqlalchemy import desc, or_

View File

@@ -94,8 +94,8 @@ class FfmpegQueue(object):
self.download_thread = threading.Thread(target=self.download_thread_function, args=())
self.download_thread.daemon = True
self.download_thread.start()
except Exception as exception:
self.P.logger.error('Exception:%s', exception)
except Exception as e:
self.P.logger.error(f"Exception:{str(e)}")
self.P.logger.error(traceback.format_exc())
@@ -107,8 +107,8 @@ class FfmpegQueue(object):
if self.current_ffmpeg_count < self.max_ffmpeg_count:
break
time.sleep(5)
except Exception as exception:
self.P.logger.error('Exception:%s', exception)
except Exception as e:
self.P.logger.error(f"Exception:{str(e)}")
self.P.logger.error(traceback.format_exc())
self.P.logger.error('current_ffmpeg_count : %s', self.current_ffmpeg_count)
self.P.logger.error('max_ffmpeg_count : %s', self.max_ffmpeg_count)
@@ -153,8 +153,8 @@ class FfmpegQueue(object):
f.start()
self.current_ffmpeg_count += 1
self.download_queue.task_done()
except Exception as exception:
self.P.logger.error('Exception:%s', exception)
except Exception as e:
self.P.logger.error(f"Exception:{str(e)}")
self.P.logger.error(traceback.format_exc())
def ffmpeg_listener(self, **arg):
@@ -203,8 +203,8 @@ class FfmpegQueue(object):
self.entity_list.append(entity)
self.download_queue.put(entity)
return True
except Exception as exception:
self.P.logger.error('Exception:%s', exception)
except Exception as e:
self.P.logger.error(f"Exception:{str(e)}")
self.P.logger.error(traceback.format_exc())
return False
@@ -270,8 +270,8 @@ class FfmpegQueue(object):
self.entity_list = new_list
ret['ret'] = 'refresh'
return ret
except Exception as exception:
self.P.logger.error('Exception:%s', exception)
except Exception as e:
self.P.logger.error(f"Exception:{str(e)}")
self.P.logger.error(traceback.format_exc())

View File

@@ -8,6 +8,8 @@ from support import SupportYaml
from . import (Logic, default_route, default_route_single_module,
get_model_setting)
from loguru import logger as logger1
class PluginBase(object):
package_name = None
@@ -52,6 +54,7 @@ class PluginBase(object):
self.home_module = setting.get('home_module')
self.status = "init_success"
self.config = {}
self.recent_menu_plugin_except_list = setting.get('recent_menu_plugin_except_list', [])
except Exception as e:
self.logger.error(f'Exception:{str(e)}')
self.logger.error(traceback.format_exc())
@@ -59,10 +62,16 @@ class PluginBase(object):
def set_module_list(self, mod_list):
try:
# self.module_list = []
for mod in mod_list:
logger1.debug(mod)
mod_ins = mod(self)
# self.logger.debug(mod_ins)
logger1.debug(mod_ins)
self.module_list.append(mod_ins)
if self.home_module == None:
self.home_module = self.module_list[0].name
except Exception as e:
F.logger.error(f'[{self.package_name}] Exception:{str(e)}')
F.logger.error(traceback.format_exc())
@@ -77,16 +86,31 @@ class PluginBase(object):
def plugin_load(self):
self.logic.plugin_load()
if self.logic:
self.logic.plugin_load()
def plugin_load_celery(self):
if self.logic:
self.logic.plugin_load_celery()
def plugin_unload(self):
self.logic.plugin_unload()
if self.logic:
self.logic.plugin_unload()
def get_first_manual_path(self):
for __ in self.menu['list']:
if __['uri'] == 'manual' and len(__['list']) > 0:
return __['list'][0]['uri']
def get_module(self, sub):
try:
for module in self.module_list:
if module.name == sub:
return module
except Exception as e:
self.logger.error(f'Exception:{str(e)}')
#self.P.logger.error(traceback.format_exc())
def create_plugin_instance(config):
ins = PluginBase(config)

View File

@@ -32,14 +32,39 @@ class Logic(object):
if self.P.ModelSetting is not None:
for module in self.P.module_list:
key = f'{module.name}_auto_start'
if self.P.ModelSetting.has_key(key) and self.P.ModelSetting.get_bool(key):
key2 = f'{module.name}_interval'
if self.P.ModelSetting.has_key(key) and self.P.ModelSetting.get_bool(key) and self.P.ModelSetting.has_key(key2):
self.scheduler_start(module.name)
if module.page_list is not None:
for page_instance in module.page_list:
key = f'{module.name}_{page_instance.name}_auto_start'
if self.P.ModelSetting.has_key(key) and self.P.ModelSetting.get_bool(key):
self.scheduler_start_sub(module.name, page_instance.name)
key1 = f'{module.name}_db_auto_delete'
key2 = f'{module.name}_db_delete_day'
if self.P.ModelSetting.has_key(key1) and self.P.ModelSetting.has_key(key2) and self.P.ModelSetting.get_bool(key1):
try: module.db_delete(self.P.ModelSetting.get_int(key2))
except: pass
if module.page_list == None:
continue
for page_instance in module.page_list:
key1 = f'{module.name}_{page_instance.name}_db_auto_delete'
key2 = f'{module.name}_{page_instance.name}_db_delete_day'
if self.P.ModelSetting.has_key(key1) and self.P.ModelSetting.has_key(key2) and self.P.ModelSetting.get_bool(key1):
try: page_instance.db_delete(self.P.ModelSetting.get_int(key2))
except: pass
def plugin_load_celery(self):
self.P.logger.debug('%s plugin_load_celery', self.P.package_name)
for module in self.P.module_list:
module.plugin_load_celery()
if module.page_list is not None:
for page_instance in module.page_list:
page_instance.plugin_load_celery()
def db_init(self):
try:
@@ -88,7 +113,7 @@ class Logic(object):
try:
job_id = '%s_%s' % (self.P.package_name, module_name)
module = self.get_module(module_name)
job = Job(self.P.package_name, job_id, module.get_scheduler_interval(), self.scheduler_function, module.get_scheduler_desc(), args=module_name)
job = Job(self.P.package_name, job_id, module.get_scheduler_interval(), self.scheduler_function, module.get_scheduler_desc(), args=(module_name,))
F.scheduler.add_job_instance(job)
except Exception as e:
self.P.logger.error(f'Exception:{str(e)}')
@@ -112,15 +137,25 @@ class Logic(object):
self.P.logger.error(f'Exception:{str(e)}')
self.P.logger.error(traceback.format_exc())
def reset_db(self, module_name):
def db_delete(self, module_name, page_name, day):
try:
module = self.get_module(module_name)
return module.reset_db()
if module == None:
return False
if page_name != None:
page = module.get_page(page_name)
if page != None:
return page.db_delete(day)
else:
return module.db_delete(day)
except Exception as e:
self.P.logger.error(f'Exception:{str(e)}')
self.P.logger.error(traceback.format_exc())
def one_execute(self, module_name):
self.P.logger.debug('one_execute :%s', module_name)
try:
@@ -166,6 +201,7 @@ class Logic(object):
self.P.logger.error(f'Exception:{str(e)}')
self.P.logger.error(traceback.format_exc())
"""
def process_telegram_data(self, data, target=None):
try:
for module in self.P.module_list:
@@ -174,7 +210,7 @@ class Logic(object):
except Exception as e:
self.P.logger.error(f'Exception:{str(e)}')
self.P.logger.error(traceback.format_exc())
"""
@@ -303,12 +339,23 @@ class Logic(object):
def arg_to_dict(self, arg):
"""
import urllib.parse
tmp = urllib.parse.unquote(arg)
tmps = tmp.split('&')
ret = {}
for tmp in tmps:
_ = tmp.split('=')
_ = tmp.split('=', 1)
ret[_[0]] = _[1]
return ret
"""
import html
import urllib.parse
char = '||!||'
arg = arg.replace('&amp;', char)
tmp = html.unescape(arg)
tmp = urllib.parse.unquote(tmp)
tmp = dict(urllib.parse.parse_qs(tmp, keep_blank_values=True))
ret = {k: v[0].replace(char, '&') for k, v in tmp.items()}
return ret

View File

@@ -39,6 +39,8 @@ class PluginModuleBase(object):
def get_page(self, page_name):
try:
if self.page_list == None:
return
for page in self.page_list:
if page_name == page.name:
return page
@@ -48,10 +50,22 @@ class PluginModuleBase(object):
def process_menu(self, page, req):
if self.page_list is not None:
page_ins = self.get_page(page)
if page_ins != None:
return page_ins.process_menu(req)
from framework import F
try:
if self.page_list is not None:
page_ins = self.get_page(page)
if page_ins != None:
return page_ins.process_menu(req)
arg = self.P.ModelSetting.to_dict() if self.P.ModelSetting != None else {}
arg['path_data'] = F.config['path_data']
arg['is_include'] = F.scheduler.is_include(self.get_scheduler_name())
arg['is_running'] = F.scheduler.is_running(self.get_scheduler_name())
return render_template(f'{self.P.package_name}_{self.name}_{page}.html', arg=arg)
except Exception as e:
self.P.logger.error(f'Exception:{str(e)}')
self.P.logger.error(traceback.format_exc())
return render_template('sample.html', title=f"PluginModuleBase-process_menu{self.P.package_name}/{self.name}/{page}")
def process_ajax(self, sub, req):
@@ -69,12 +83,18 @@ class PluginModuleBase(object):
def scheduler_function(self):
pass
def reset_db(self):
pass
def db_delete(self, day):
if self.web_list_model != None:
return self.web_list_model.delete_all(day)
def plugin_load(self):
pass
def plugin_load_celery(self):
pass
def plugin_unload(self):
pass
@@ -115,12 +135,37 @@ class PluginModuleBase(object):
pass
def arg_to_dict(self, arg):
return self.P.logic.arg_to_dict(arg)
def get_scheduler_name(self):
return f'{self.P.package_name}_{self.name}'
def process_discord_data(self, data):
pass
def start_celery(self, func, on_message=None, *args, page=None):
from framework import F
if F.config['use_celery']:
result = func.apply_async(args)
try:
if on_message != None:
ret = result.get(on_message=on_message, propagate=True)
else:
ret = result.get()
except:
ret = result.get()
else:
if on_message == None:
ret = func(*args)
else:
if page == None:
ret = func(self, *args)
else:
ret = func(page, *args)
return ret
@@ -153,7 +198,8 @@ class PluginPageBase(object):
arg = self.P.ModelSetting.to_dict()
return render_template(f'{self.P.package_name}_{self.parent.name}_{self.name}.html', arg=arg)
except Exception as e:
pass
self.P.logger.error(f'Exception:{str(e)}')
self.P.logger.error(traceback.format_exc())
return render_template('sample.html', title=f"PluginPageBase-process_menu --- {self.P.package_name}/{self.parent.name}/{self.name}")
@@ -176,6 +222,9 @@ class PluginPageBase(object):
def plugin_load(self):
pass
def plugin_load_celery(self):
pass
# logic
def plugin_unload(self):
pass
@@ -207,3 +256,41 @@ class PluginPageBase(object):
pass
def arg_to_dict(self, arg):
return self.P.logic.arg_to_dict(arg)
def get_page(self, page_name):
return self.parent.get_page(page_name)
def get_module(self, module_name):
return self.parent.get_module(module_name)
def process_discord_data(self, data):
pass
def db_delete(self, day):
if self.web_list_model != None:
return self.web_list_model.delete_all(day)
def start_celery(self, func, on_message, *args):
return self.parent.start_celery(func, on_message, *args, page=self)
"""
def start_celery(self, func, on_message=None, *args):
from framework import F
if F.config['use_celery']:
result = func.apply_async(args)
try:
if on_message != None:
ret = result.get(on_message=on_message, propagate=True)
else:
ret = result.get()
except:
ret = result.get()
else:
ret = func(*args)
return ret
"""

View File

@@ -1,3 +1,4 @@
import sqlite3
import traceback
from datetime import datetime, timedelta
@@ -45,7 +46,7 @@ class ModelBase(F.db.Model):
paging['next_page'] = False
paging['current_page'] = current_page
paging['count'] = count
F.logger.debug('paging : c:%s %s %s %s %s %s', count, paging['total_page'], paging['prev_page'], paging['next_page'] , paging['start_page'], paging['last_page'])
#F.logger.debug('paging : c:%s %s %s %s %s %s', count, paging['total_page'], paging['prev_page'], paging['next_page'] , paging['start_page'], paging['last_page'])
return paging
except Exception as e:
cls.P.logger.error(f'Exception:{str(e)}')
@@ -89,24 +90,30 @@ class ModelBase(F.db.Model):
return False
@classmethod
def delete_all(cls, days=None):
def delete_all(cls, day=None):
count = -1
try:
with F.app.app_context():
if days == None:
F.db.session.query(cls).delete()
F.db.session.commit()
if day == None or day in [0, '0']:
count = F.db.session.query(cls).delete()
else:
now = datetime.now()
ago = now - timedelta(days=int(days))
ago = now - timedelta(days=int(day))
#ago.hour = 0
#ago.minute = 0
count = F.db.session.query(cls).filter(cls.created_time > ago).delete()
cls.P.logger.info(f"delete_all {days=} {count=}")
return True
count = F.db.session.query(cls).filter(cls.created_time < ago).delete()
cls.P.logger.info(f"delete_all {day=} {count=}")
F.db.session.commit()
db_file = F.app.config['SQLALCHEMY_BINDS'][cls.P.package_name].replace('sqlite:///', '').split('?')[0]
connection = sqlite3.connect(db_file)
cursor = connection.cursor()
cursor.execute('VACUUM;')
connection.close()
except Exception as e:
cls.P.logger.error(f'Exception:{str(e)}')
cls.P.logger.error(traceback.format_exc())
return False
return count
@classmethod
@@ -135,12 +142,12 @@ class ModelBase(F.db.Model):
if cls.P.ModelSetting is not None and cls.__tablename__ is not None:
cls.P.ModelSetting.set(f'{cls.__tablename__}_last_list_option', f'{order}|{page}|{search}|{option1}|{option2}')
except Exception as e:
F.logger.error('Exception:%s', e)
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
F.logger.error(f'{cls.__tablename__}_last_list_option ERROR!' )
return ret
except Exception as e:
cls.P.logger.error('Exception:%s', e)
cls.P.logger.error(f"Exception:{str(e)}")
cls.P.logger.error(traceback.format_exc())
@@ -149,6 +156,10 @@ class ModelBase(F.db.Model):
def make_query(cls, req, order='desc', search='', option1='all', option2='all'):
with F.app.app_context():
query = F.db.session.query(cls)
if order == 'desc':
query = query.order_by(desc(cls.id))
else:
query = query.order_by(cls.id)
return query
@@ -171,3 +182,16 @@ class ModelBase(F.db.Model):
query = query.filter(field.like('%'+search+'%'))
#query = query1.union(query2)
return query
@classmethod
def get_list_by_status(cls, status):
try:
with F.app.app_context():
query = F.db.session.query(cls).filter(
cls.status == status,
)
query = query.order_by(cls.id)
return query.all()
except:
pass

View File

@@ -107,6 +107,8 @@ def get_model_setting(package_name, logger, table_name=None):
if ModelSetting.get(key) != value:
change_list.append(key)
entity = F.db.session.query(ModelSetting).filter_by(key=key).with_for_update().first()
if entity == None:
logger.warning(f"NOT exist setting key: {key}")
entity.value = value
F.db.session.commit()
return True, change_list
@@ -117,7 +119,8 @@ def get_model_setting(package_name, logger, table_name=None):
return False, []
@staticmethod
def get_list(key, delimeter='\n', comment=' #'):
def get_list(key, delimeter='\n', comment='#'):
value = None
try:
value = ModelSetting.get(key).replace('\n', delimeter)
if comment is None:

View File

@@ -27,7 +27,12 @@ def default_route(P):
def first_menu(sub):
try:
if P.ModelSetting is not None and (P.package_name == 'system' and sub != 'home'):
P.ModelSetting.set('recent_menu_plugin', '{}'.format(sub))
current_menu = sub
for except_menu in P.recent_menu_plugin_except_list:
if current_menu.startswith(except_menu) or current_menu == except_menu:
break
else:
P.ModelSetting.set('recent_menu_plugin', current_menu)
for module in P.module_list:
if sub == module.name:
first_menu = module.get_first_menu()
@@ -45,8 +50,8 @@ def default_route(P):
return redirect(f"/{P.package_name}/manual/{P.get_first_manual_path()}")
return render_template('sample.html', title='%s - %s' % (P.package_name, sub))
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/manual/<path:path>', methods=['GET', 'POST'])
@@ -57,16 +62,43 @@ def default_route(P):
filepath = os.path.join(plugin_root, *path.split('/'))
from support import SupportFile
data = SupportFile.read_file(filepath)
if filepath.endswith('.mdf'):
try:
from support import SupportSC
data = SupportSC.decode(data)
except:
pass
return render_template('manual.html', data=data)
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/<module_name>/manual/<path:path>', methods=['GET', 'POST'])
@login_required
def module_manual(module_name, path):
try:
plugin_root = os.path.dirname(P.blueprint.template_folder)
filepath = os.path.join(plugin_root, *path.split('/'))
from support import SupportFile
data = SupportFile.read_file(filepath)
return render_template('manual.html', data=data)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/<sub>/<sub2>', methods=['GET', 'POST'])
@login_required
def second_menu(sub, sub2):
if sub2 == 'null':
return
if P.ModelSetting is not None:
P.ModelSetting.set('recent_menu_plugin', '{}|{}'.format(sub, sub2))
current_menu = f"{sub}|{sub2}"
for except_menu in P.recent_menu_plugin_except_list:
if current_menu.startswith(except_menu) or current_menu == except_menu:
break
else:
P.ModelSetting.set('recent_menu_plugin', current_menu)
try:
for module in P.module_list:
if sub == module.name:
@@ -74,8 +106,8 @@ def default_route(P):
if sub == 'log':
return render_template('log.html', package=P.package_name)
return render_template('sample.html', title='%s - %s' % (P.package_name, sub))
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
#########################################################
@@ -84,7 +116,7 @@ def default_route(P):
@P.blueprint.route('/ajax/<sub>', methods=['GET', 'POST'])
@login_required
def ajax(sub):
P.logger.debug('AJAX %s %s', P.package_name, sub)
#P.logger.debug('AJAX %s %s', P.package_name, sub)
try:
# global
if sub == 'setting_save':
@@ -93,8 +125,8 @@ def default_route(P):
module.setting_save_after(change_list)
return jsonify(ret)
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/ajax/<module_name>/<cmd>', methods=['GET', 'POST'])
@@ -109,8 +141,9 @@ def default_route(P):
else:
P.logic.scheduler_stop(module_name)
return jsonify(go)
elif cmd == 'reset_db':
ret = P.logic.reset_db(module_name)
elif cmd == 'db_delete':
day = request.form['day']
ret = P.logic.db_delete(module_name, None, day)
return jsonify(ret)
elif cmd == 'one_execute':
ret = P.logic.one_execute(module_name)
@@ -118,18 +151,25 @@ def default_route(P):
elif cmd == 'immediately_execute':
ret = P.logic.immediately_execute(module_name)
return jsonify(ret)
elif cmd == 'web_list':
model = P.logic.get_module(module_name).web_list_model
if model != None:
return jsonify(model.web_list(request))
if module_name == module.name:
if cmd == 'command':
return module.process_command(request.form['command'], request.form.get('arg1'), request.form.get('arg2'), request.form.get('arg3'), request)
elif cmd == 'web_list':
model = P.logic.get_module(module_name).web_list_model
if model != None:
return jsonify(model.web_list(request))
elif cmd == 'db_delete_item':
db_id = request.form['db_id']
ret = False
if module.web_list_model != None:
ret = module.web_list_model.delete_by_id(db_id)
return jsonify(ret)
else:
return module.process_ajax(cmd, request)
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/ajax/<module_name>/<page_name>/<command>', methods=['GET', 'POST'])
@@ -148,10 +188,10 @@ def default_route(P):
else:
P.logic.scheduler_stop_sub(module_name, page_name)
return jsonify(go)
#elif command == 'reset_db':
# sub = request.form['sub']
# ret = P.logic.reset_db(sub)
# return jsonify(ret)
elif command == 'db_delete':
day = request.form['day']
ret = P.logic.db_delete(module_name, page_name, day)
return jsonify(ret)
elif command == 'one_execute':
ret = P.logic.one_execute_sub(module_name, page_name)
return jsonify(ret)
@@ -160,25 +200,30 @@ def default_route(P):
return jsonify(ret)
elif command == 'command':
return ins_page.process_command(request.form['command'], request.form.get('arg1'), request.form.get('arg2'), request.form.get('arg3'), request)
elif command == 'db_delete_item':
db_id = request.form['db_id']
ret = False
if ins_page.web_list_model != None:
ret = ins_page.web_list_model.delete_by_id(db_id)
return jsonify(ret)
else:
return ins_page.process_ajax(command, request)
P.logger.error(f"not process ajax : {P.package_name} {module_name} {page_name} {command}")
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
#########################################################
# API - 외부
#########################################################
# 단일 모듈인 경우 모듈이름을 붙이기 불편하여 추가.
@P.blueprint.route('/api/<sub2>', methods=['GET', 'POST'])
@P.blueprint.route('/api/<sub>', methods=['GET', 'POST'])
@F.check_api
def api_first(sub2):
def api_first(sub):
try:
for module in P.module_list:
return module.process_api(sub2, request)
except Exception as exception:
P.logger.error('Exception:%s', exception)
return P.module_list[0].process_api(sub, request)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/api/<sub>/<sub2>', methods=['GET', 'POST'])
@@ -188,8 +233,16 @@ def default_route(P):
for module in P.module_list:
if sub == module.name:
return module.process_api(sub2, request)
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/normal/<sub>', methods=['GET', 'POST'])
def normal_first(sub):
try:
return P.module_list[0].process_normal(sub, request)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/normal/<sub>/<sub2>', methods=['GET', 'POST'])
@@ -198,13 +251,13 @@ def default_route(P):
for module in P.module_list:
if sub == module.name:
return module.process_normal(sub2, request)
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
# default_route 끝
@@ -266,18 +319,14 @@ def default_route_single_module(P):
else:
P.logic.scheduler_stop(sub)
return jsonify(go)
elif sub == 'reset_db':
sub = request.form['sub']
ret = P.logic.reset_db(sub)
return jsonify(ret)
elif sub == 'one_execute':
sub = request.form['sub']
ret = P.logic.one_execute(sub)
return jsonify(ret)
else:
return P.module_list[0].process_ajax(sub, request)
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/api/<sub>', methods=['GET', 'POST'])
@@ -285,16 +334,16 @@ def default_route_single_module(P):
def api(sub):
try:
return P.module_list[0].process_api(sub, request)
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/normal/<sub>', methods=['GET', 'POST'])
def normal(sub):
try:
return P.module_list[0].process_normal(sub, request)
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@@ -330,14 +379,15 @@ def default_route_socketio_module(module, attach=''):
module.socketio_list = []
@F.socketio.on('connect', namespace=f'/{P.package_name}/{module.name}{attach}')
@F.login_required
def connect():
try:
P.logger.debug(f'socket_connect : {P.package_name} - {module.name}{attach}')
module.socketio_list.append(request.sid)
socketio_callback('start', '')
module.socketio_connect()
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@@ -347,8 +397,8 @@ def default_route_socketio_module(module, attach=''):
P.logger.debug(f'socket_disconnect : {P.package_name} - {module.name}{attach}')
module.socketio_list.remove(request.sid)
module.socketio_disconnect()
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@@ -357,7 +407,7 @@ def default_route_socketio_module(module, attach=''):
if encoding:
data = json.dumps(data, cls=AlchemyEncoder)
data = json.loads(data)
F.socketio.emit(cmd, data, namespace=f'/{P.package_name}/{module.name}{attach}', broadcast=True)
F.socketio.emit(cmd, data, namespace=f'/{P.package_name}/{module.name}{attach}')
module.socketio_callback = socketio_callback
@@ -392,9 +442,10 @@ def default_route_socketio_page(page):
page.socketio_list = []
@F.socketio.on('connect', namespace=f'/{P.package_name}/{module.name}/{page.name}')
@F.login_required
def page_socketio_connect():
try:
P.logger.debug(f'socket_connect : {P.package_name}/{module.name}/{page.name}')
#P.logger.debug(f'socket_connect : {P.package_name}/{module.name}/{page.name}')
page.socketio_list.append(request.sid)
page_socketio_socketio_callback('start', '')
except Exception as e:
@@ -405,7 +456,7 @@ def default_route_socketio_page(page):
@F.socketio.on('disconnect', namespace=f'/{P.package_name}/{module.name}/{page.name}')
def page_socketio_disconnect():
try:
P.logger.debug(f'socket_disconnect : {P.package_name}/{module.name}/{page.name}')
#P.logger.debug(f'socket_disconnect : {P.package_name}/{module.name}/{page.name}')
page.socketio_list.remove(request.sid)
except Exception as e:
P.logger.error(f'Exception:{str(e)}')
@@ -417,6 +468,6 @@ def default_route_socketio_page(page):
if encoding:
data = json.dumps(data, cls=AlchemyEncoder)
data = json.loads(data)
F.socketio.emit(cmd, data, namespace=f'/{P.package_name}/{module.name}/{page.name}', broadcast=True)
F.socketio.emit(cmd, data, namespace=f'/{P.package_name}/{module.name}/{page.name}')
page.socketio_callback = page_socketio_socketio_callback

BIN
lib/support/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -10,15 +10,19 @@ def d(data):
from .logger import get_logger
logger = get_logger()
#logger = get_logger()
import logging
logger = logging.getLogger('support')
from .base.aes import SupportAES
from .base.discord import SupportDiscord
from .base.file import SupportFile
from .base.os_command import SupportOSCommand
from .base.string import SupportString
from .base.sub_process import SupportSubprocess
from .base.support_sc import SupportSC
from .base.telegram import SupportTelegram
from .base.slack import SupportSlack
from .base.util import (AlchemyEncoder, SingletonClass, SupportUtil,
default_headers, pt)
from .base.yaml import SupportYaml

View File

@@ -18,8 +18,8 @@ class SupportAES(object):
def encrypt(cls, raw, mykey=None):
try:
Random.atfork()
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
raw = pad(raw)
@@ -31,8 +31,8 @@ class SupportAES(object):
cipher = AES.new(key if mykey is None else mykey, AES.MODE_CBC, iv )
try:
tmp = cipher.encrypt( raw )
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
tmp = cipher.encrypt( raw.encode() )
ret = base64.b64encode( iv + tmp )
@@ -64,8 +64,8 @@ class SupportAES(object):
def encrypt_(cls, raw, mykey=None, iv=None):
try:
Random.atfork()
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
raw = pad(raw)
if type(raw) == type(''):
@@ -79,8 +79,8 @@ class SupportAES(object):
cipher = AES.new(key if mykey is None else mykey, AES.MODE_CBC, iv )
try:
tmp = cipher.encrypt( raw )
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
tmp = cipher.encrypt( raw.encode() )
ret = base64.b64encode( tmp )

View File

@@ -1,4 +1,3 @@
import io
import os
import random
import time
@@ -11,31 +10,36 @@ try:
except:
os.system('pip3 install discord-webhook')
# 2023-10-13 by flaskfarm
# 웹훅 URL이 git에 노출되면 중단.
# base64로 인코딩.
import base64
from discord_webhook import DiscordEmbed, DiscordWebhook
from . import logger
webhook_list = [
#'https://discord.com/api/webhooks/933908493612744705/DGPWBQN8LiMnt2cnCSNVy6rCc5Gi_vj98QpJ3ZEeihohzsfOsCWvcixJU1A2fQuepGFq', # 1
#'https://discord.com/api/webhooks/932754078839234731/R2iFzQ7P8IKV-MGWp820ToWX07s5q8X-st-QsUJs7j3JInUj6ZlI4uDYKeR_cwIi98mf', # 2
#'https://discord.com/api/webhooks/932754171835351131/50RLrYa_B69ybk4BWoLruNqU7YlZ3pl3gpPr9bwuankWyTIGtRGbgf0CJ9ExJWJmvXwo', # 3
'https://discord.com/api/webhooks/794661043863027752/A9O-vZSHIgfQ3KX7wO5_e2xisqpLw5TJxg2Qs1stBHxyd5PK-Zx0IJbAQXmyDN1ixZ-n', # 4
'https://discord.com/api/webhooks/810373348776476683/h_uJLBBlHzD0w_CG0nUajFO-XEh3fvy-vQofQt1_8TMD7zHiR7a28t3jF-xBCP6EVlow', # 5
'https://discord.com/api/webhooks/810373405508501534/wovhf-1pqcxW5h9xy7iwkYaf8KMDjHU49cMWuLKtBWjAnj-tzS1_j8RJ7tsMyViDbZCE', # 6
'https://discord.com/api/webhooks/796558388326039552/k2VV356S1gKQa9ht-JuAs5Dqw5eVkxgZsLUzFoxmFG5lW6jqKl7zCBbbKVhs3pcLOetm', # 7
'https://discord.com/api/webhooks/810373566452858920/Qf2V8BoLOy2kQzlZGHy5HZ1nTj7lK72ol_UFrR3_eHKEOK5fyR_fQ8Yw8YzVh9EQG54o', # 8
'https://discord.com/api/webhooks/810373654411739157/SGgdO49OCkTNIlc_BSMSy7IXQwwXVonG3DsVfvBVE6luTCwvgCqEBpEk30WBeMMieCyI', # 9
'https://discord.com/api/webhooks/810373722341900288/FwcRJ4YxYjpyHpnRwF5f2an0ltEm8JPqcWeZqQi3Qz4QnhEY-kR2sjF9fo_n6stMGnf_', # 10
'https://discord.com/api/webhooks/931779811691626536/vvwCm1YQvE5tW4QJ4SNKRmXhQQrmOQxbjsgRjbTMMXOSiclB66qipiZaax5giAqqu2IB', # 11
'https://discord.com/api/webhooks/931779905631420416/VKlDwfxWQPJfIaj94-ww_hM1MNEayRKoMq0adMffCC4WQS60yoAub_nqPbpnfFRR3VU5', # 12
'https://discord.com/api/webhooks/931779947914231840/22amQuHSOI7wPijSt3U01mXwd5hTo_WHfVkeaowDQMawCo5tXVfeEMd6wAWf1n7CseiG', # 13
'https://discord.com/api/webhooks/810374294416654346/T3-TEdKIg7rwMZeDzNr46KPDvO7ZF8pRdJ3lfl39lJw2XEZamAG8uACIXagbNMX_B0YN', # 14
'https://discord.com/api/webhooks/810374337403289641/_esFkQXwlPlhxJWtlqDAdLg2Nujo-LjGPEG3mUmjiRZto69NQpkBJ0F2xtSNrCH4VAgb', # 15
'https://discord.com/api/webhooks/810374384736534568/mH5-OkBVpi7XqJioaQ8Ma-NiL-bOx7B5nYJpL1gZ03JaJaUaIW4bCHeCt5O_VGLJwAtj', # 16
'https://discord.com/api/webhooks/810374428604104724/Z1Tdxz3mb0ytWq5LHWi4rG5CeJnr9KWXy5aO_waeD0NcImQnhRXe7h7ra7UrIDRQ2jOg', # 17
'https://discord.com/api/webhooks/810374475773509643/QCPPN4djNzhuOmbS3DlrGBunK0SVR5Py9vMyCiPL-0T2VPgitFZS4YM6GCLfM2fkrn4-', # 18
'https://discord.com/api/webhooks/810374527652855819/5ypaKI_r-hYzwmdDlVmgAU6xNgU833L9tFlPnf3nw4ZDaPMSppjt77aYOiFks4KLGQk8', # 19
'https://discord.com/api/webhooks/810374587917402162/lHrG7CEysGUM_41DMnrxL2Q8eh1-xPjJXstYE68WWfLQbuUAV3rOfsNB9adncJzinYKi', # 20
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MTkwMTk2NzE1NTMwNS9uY01aaWZVVDY3ZTRISXdPeG8xM0dLdFBTNFBnVjZZSDBZaU1SQ2FMQkNfMU0yMHo3WmNFRjExM2xnY0NpRTFFdnhEZQ==').decode('utf-8'), # 1
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MjY1NjQwOTA3MTY0Ni9zUjlHZFJMbERrQV9Cc243UkdvQXQ3TmVSMU9SVFRxczVqUEc5UU9PYTJCbjllTTI1YnctV0FXZ2pYT1pYa183U0V4Wg==').decode('utf-8'), # 2
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MjkyNDExOTA0NDE4OC9wX3ZMN211eElKUmFWOXRDVG56S3c4LVJjY0R5V1JaSjdER2dYc1YwaXlLVGFjZEM4MVBiYmctWHFzY0NDTk5jdXpWeQ==').decode('utf-8'), # 3
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MzE2MjE1ODI0MzkwMS9KdDMwZjlTTTR6dWNfVmYwSVlmbzdZNTluOFI5T2RQazNXdTFtNG93MHZxZFJERXlneVZvb25Rdm1QbVRya1lOVTRyag==').decode('utf-8'), # 4
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MzMyNzY1MzE1ODk4Mi82Nk0zZVFyRGpSZG1UTzExaXZlMGhlTFFpNGwtckZUN1lRYTJ3elpmMjNBOGZPYm1CYjJSRmhxR2dNSHNlNUdHSFNLTA==').decode('utf-8'), # 5
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MzU1ODAzNzg5MzIxNC84aWFNLTJIdXJFOW1XM0RqY293dm9tUVhUeUxLOElrbWR5SnhsY1BFRzJ4MjBqOTNIN0FWNnY0dVJIak5XeGprcjg4Tw==').decode('utf-8'), # 6
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MzczMTQzNDYxMDc0OS9xRktGX0hSWDRYVHFYMVFPRzM5YWNJVkp6dmdRZXBzZjM2TEFEUlpNOWtiZ0pNUHVfd091OXZ4bXdZVVBRMUpkUjhhRg==').decode('utf-8'), # 7
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1Mzg1NTI0NjI3NDYwMS9vWGVxYVdhWENNZktkM19iZktEVjB0Ti1XQzUyLUxpVjU0VjQxWE1jNWd3XzJmQnpnekp4MzJNYS1wOWlvQkFpd1I3Mw==').decode('utf-8'), # 8
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1Mzk5MDgyNzE1MTQ2MS85a0xXTXZCY1FaNzZRcnRpZmVJck9DOXo5SXl1WGl4YnRmbldocHVjSlFRVUJqcGxSd0tIdzdDc0h3THJhQkRQM1h5ag==').decode('utf-8'), # 9
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDExMjQ1NzYzNzg5OC9ZVC1qblZTeWFxcjAxMjFtWUtVZFU1SjJaVFZHS0NOM2djUDI2RXEwWm5hR3RWeFllM3NZa0kyUG81RWhPd211WDd6aw==').decode('utf-8'), # 10
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDI1Mzg1MTk1NTMxMS9RVUt1cU5uWFFiaWkwU01FMWxkU0lEakxhZXh5RDRUZEZuLWdXejFuSXRlYy1mSFVCU3dxUDd3WHNBbDB1dXd2VVJTRw==').decode('utf-8'), # 11
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDM3NDMyNDgxMzkyNS9VR1Jsc3liY2dPQ3hoMVQ1Z0J0QVc2RGQyZ0dPaGVOXzcydy15QTBvZzU5aU1BcnB3WWxVRzhka0ZXTUxSVUZpaHFScw==').decode('utf-8'), # 12
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDUxNjE5NzI3Nzc2Ny9iOEFIN1FtY2JPSl9XcUVHZmtMOVNPbXBJMWluVThvcDF4amQwWGFjRXFFZW82ZURzbS0yYkpZYllmQ1RYclMxbHhUdQ==').decode('utf-8'), # 13
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDY0MDIzMTIzOTcwMS90bkFSTzFvYWo1SWRmb0U4UEVJejRZUVMxNFhKXzdpc0I5Q1otdzVyaXdDN0U0cVVzQ1B6V2pLRnM3WE9OazBvVEo5Qg==').decode('utf-8'), # 14
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDc1NTcxNzIwMTk4MS9WLWQwc0hvNl9QakJTdFpLVmtuSTdDS0RuQks1QzRhS2dPZUZ4azEwam41VE5oZk1PdFNOSFNHN3BpaGNWLVh6Y0kxZg==').decode('utf-8'), # 15
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDg4NDc4NDMyNDYxOS9XVEpHWWVjcjVKOHhtN0hTaUpCbmdnU01Uc3JkMUxiaDVwQzB2Vm5tYVptZWlvd2RRZWZQRHRuZHowRmViWE9xYkNoeA==').decode('utf-8'), # 16
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NTAxMTIxMzA5OTEyOS9neHVVenpsMTBpMUV4NWZtdU5jZGlOQ2FocHBEM3liQlpxaTR3Y3phdlpGeG1OUGx2VFRadU9CalZCMTBOZzJ2QWpLcA==').decode('utf-8'), # 17
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NTEzMjg4OTczMTE1My9YcTU4cXdCTGlOOEF4S1djQTl0MFJERkhIT0NDNjg4MlQ1aXBKbkJxY3VSOFVxMGowSzF4Rko3dUZWaGhRR0RFTjc3bw==').decode('utf-8'), # 18
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NTI5NzYzNzc5MzgxMy9pV3hoZkxRN190dHhkNENIVnNPWjA2ZHFOUjlkVTZUdlNfdHA2OHVnNlI2WmRIa2dESzJKb28xUVNSa3NrRDhLUXRyTg==').decode('utf-8'), # 19
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NTQ0NDk0MjAxMjQ4OC9zandtaFNDYjI0ZElYbjBVMWhwMmdJRzZDV2REcC1Kb3M0OW1Oc05jQllGenNDNm1KYVZJOVpoQm11dGt4cXd1bDc1ZA==').decode('utf-8'), # 20
]
@@ -44,20 +48,60 @@ class SupportDiscord(object):
@classmethod
def send_discord_message(cls, text, image_url=None, webhook_url=None):
try:
"""
webhook = DiscordWebhook(url=webhook_url, content=text)
if image_url is not None:
embed = DiscordEmbed()
embed.set_timestamp()
embed.set_image(url=image_url)
webhook.add_embed(embed)
response = webhook.execute()
return True
except Exception as exception:
logger.error('Exception:%s', exception)
"""
try:
if image_url is not None:
webhook = DiscordWebhook(url=webhook_url)
embed = DiscordEmbed()
embed.set_timestamp()
embed.set_image(url=image_url)
tmp = text.split('\n', 1)
embed.set_title(tmp[0])
embed.set_description(tmp[1])
webhook.add_embed(embed)
else:
if 'http://' in text or 'https://' in text:
webhook = DiscordWebhook(url=webhook_url, content= text)
else:
webhook = DiscordWebhook(url=webhook_url, content='```' + text + '```')
webhook.execute()
return True
except:
webhook = DiscordWebhook(url=webhook_url, content=text)
if image_url is not None:
embed = DiscordEmbed()
embed.set_timestamp()
embed.set_image(url=image_url)
webhook.add_embed(embed)
webhook.execute()
return True
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False
@classmethod
def send_discord_bot_message(cls, text, webhook_url, encryped=True):
try:
from support import SupportAES
if encryped:
text = '^' + SupportAES.encrypt(text)
return cls.send_discord_message(text, webhook_url=webhook_url)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False
@classmethod
def discord_proxy_image(cls, image_url, webhook_url=None, retry=True):
@@ -95,21 +139,21 @@ class SupportDiscord(object):
return image_url
else:
raise Exception(str(data))
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
if retry:
time.sleep(1)
return cls.discord_proxy_image(image_url, webhook_url=None, retry=False)
return cls.discord_proxy_image(image_url, webhook_url=webhook_url, retry=False)
else:
return image_url
@classmethod
def discord_proxy_image_localfile(cls, filepath, retry=True):
def discord_proxy_image_localfile(cls, filepath, webhook_url=None, retry=True):
data = None
webhook_url = webhook_list[random.randint(0,len(webhook_list)-1)]
if webhook_url is None or webhook_url == '':
webhook_url = webhook_list[random.randint(0,len(webhook_list)-1)]
try:
webhook = DiscordWebhook(url=webhook_url, content='')
import io
@@ -133,8 +177,8 @@ class SupportDiscord(object):
if retry:
time.sleep(1)
return cls.discord_proxy_image_localfile(filepath, retry=False)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
if retry:
@@ -143,15 +187,15 @@ class SupportDiscord(object):
@classmethod
def discord_proxy_image_bytes(cls, bytes, retry=True):
def discord_proxy_image_bytes(cls, bytes, retry=True, format='jpg', webhook_url=None):
data = None
idx = random.randint(0,len(webhook_list)-1)
webhook_url = webhook_list[idx]
if webhook_url is None or webhook_url == '':
webhook_url = webhook_list[random.randint(0,len(webhook_list)-1)]
try:
webhook = DiscordWebhook(url=webhook_url, content='')
webhook.add_file(file=bytes, filename='image.jpg')
webhook.add_file(file=bytes, filename=f'image.{format}')
embed = DiscordEmbed()
embed.set_image(url="attachment://image.jpg")
embed.set_image(url=f"attachment://image.{format}")
response = webhook.execute()
data = None
if type(response) == type([]):
@@ -168,8 +212,8 @@ class SupportDiscord(object):
if retry:
time.sleep(1)
return cls.discord_proxy_image_bytes(bytes, retry=False)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
if retry:
@@ -181,7 +225,7 @@ class SupportDiscord(object):
# RSS에서 자막 올린거
@classmethod
def discord_cdn(cls, byteio=None, filepath=None, filename=None, webhook_url=None, content='', retry=True):
def discord_cdn(cls, byteio=None, filepath=None, filename=None, webhook_url="https://discord.com/api/webhooks/1050549730964410470/ttge1ggOfIxrCSeTmYbIIsUWyMGAQj-nN6QBgwZTqLcHtUKcqjZ8wFWSWAhHmZne57t7", content='', retry=True):
data = None
if webhook_url is None:
webhook_url = webhook_list[random.randint(0,9)]
@@ -210,8 +254,8 @@ class SupportDiscord(object):
if retry:
time.sleep(1)
return cls.discord_proxy_image_localfile(filepath, retry=False)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
if retry:
time.sleep(1)

View File

@@ -3,6 +3,7 @@ import json
import os
import re
import traceback
import zipfile
from . import logger
@@ -16,8 +17,8 @@ class SupportFile(object):
data = ifp.read()
ifp.close()
return data
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@classmethod
@@ -27,8 +28,8 @@ class SupportFile(object):
ofp = codecs.open(filename, mode, encoding='utf8')
ofp.write(data)
ofp.close()
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@classmethod
@@ -37,8 +38,8 @@ class SupportFile(object):
with open(filepath, "r", encoding='utf8') as json_file:
data = json.load(json_file)
return data
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@classmethod
@@ -48,8 +49,8 @@ class SupportFile(object):
os.makedirs(os.path.dirname(filepath), exist_ok=True)
with open(filepath, "w", encoding='utf8') as json_file:
json.dump(data, json_file, indent=4, ensure_ascii=False)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@@ -71,8 +72,8 @@ class SupportFile(object):
response = requests.get(url, headers=headers) # get request
file_is.write(response.content) # write to file
return True
except Exception as exception:
logger.debug('Exception:%s', exception)
except Exception as e:
logger.debug(f"Exception:{str(e)}")
logger.debug(traceback.format_exc())
return False
@@ -82,118 +83,18 @@ class SupportFile(object):
#text = text.replace('/', '')
# 2021-07-31 X:X
#text = text.replace(':', ' ')
text = re.sub('[\\/:*?\"<>|]', ' ', text).strip()
text = re.sub('[\\/:*?\"<>|]', ' ', text).strip()
text = re.sub("\s{2,}", ' ', text)
return text
@classmethod
def size(cls, start_path = '.'):
total_size = 0
for dirpath, dirnames, filenames in os.walk(start_path):
for f in filenames:
fp = os.path.join(dirpath, f)
if not os.path.islink(fp):
total_size += os.path.getsize(fp)
return total_size
def unzip(cls, zip_filepath, extract_folderpath):
with zipfile.ZipFile(zip_filepath, 'r') as zip_ref:
zip_ref.extractall(extract_folderpath)
# 파일처리에서 사용. 중복이면 시간값
@classmethod
def file_move(cls, source_path, target_dir, target_filename):
try:
@@ -208,9 +109,160 @@ class SupportFile(object):
new_target_filename = f"{tmp[0]} {str(time.time()).split('.')[0]}{tmp[1]}"
target_path = os.path.join(target_dir, new_target_filename)
shutil.move(source_path, target_path)
except Exception as exception:
logger.debug('Exception:%s', exception)
except Exception as e:
logger.debug(f"Exception:{str(e)}")
logger.debug(traceback.format_exc())
@classmethod
def size(cls, start_path = '.'):
if os.path.exists(start_path):
if os.path.isdir(start_path):
total_size = 0
for dirpath, dirnames, filenames in os.walk(start_path):
for f in filenames:
fp = os.path.join(dirpath, f)
if not os.path.islink(fp):
total_size += os.path.getsize(fp)
return total_size
else:
return os.path.getsize(start_path)
return 0
@classmethod
def size_info(cls, start_path = '.'):
ret = {
'size':0,
'file_count':0,
'folder_count':0.
}
for dirpath, dirnames, filenames in os.walk(start_path):
for f in filenames:
fp = os.path.join(dirpath, f)
if not os.path.islink(fp):
ret['size'] += os.path.getsize(fp)
ret['folder_count'] += len(dirnames)
ret['file_count'] += len(filenames)
return ret
@classmethod
def rmtree(cls, folderpath):
import shutil
try:
for root, dirs, files in os.walk(folderpath):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
shutil.rmtree(os.path.join(root, name))
shutil.rmtree(folderpath)
except Exception as e:
logger.debug(f"Exception:{str(e)}")
logger.debug(traceback.format_exc())
return False
@classmethod
def file_move(cls, source_path, target_dir, target_filename):
try:
import shutil
import time
os.makedirs(target_dir, exist_ok=True)
target_path = os.path.join(target_dir, target_filename)
if source_path != target_path:
if os.path.exists(target_path):
tmp = os.path.splitext(target_filename)
new_target_filename = f"{tmp[0]} {str(time.time()).split('.')[0]}{tmp[1]}"
target_path = os.path.join(target_dir, new_target_filename)
shutil.move(source_path, target_path)
except Exception as e:
logger.debug(f"Exception:{str(e)}")
logger.debug(traceback.format_exc())
"""
@classmethod
@@ -234,13 +286,13 @@ class SupportFile(object):
import shutil
shutil.rmtree(zip_path)
return zipfilepath
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return
"""
"""
@classmethod
def rmtree(cls, folderpath):
import shutil
@@ -253,19 +305,9 @@ class SupportFile(object):
return True
except:
return False
"""
@classmethod
def rmtree2(cls, folderpath):
import shutil
try:
for root, dirs, files in os.walk(folderpath):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
shutil.rmtree(os.path.join(root, name))
except:
return False
@@ -279,8 +321,8 @@ class SupportFile(object):
try:
with open(filename, 'wb') as f:
f.write(data)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@@ -361,8 +403,8 @@ class SupportFile(object):
if isinstance(data, bytes):
data = data.decode('utf-8')
return data
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
"""

View File

@@ -1,7 +1,13 @@
import os, sys, traceback, requests
import os
import sys
import traceback
from io import BytesIO
import requests
from . import logger
class SupportImage(object):
@classmethod
@@ -20,6 +26,6 @@ class SupportImage(object):
from . import SupportDiscord
return SupportDiscord.discord_proxy_image_bytes(img_byte_arr)
except Exception as e:
logger.error('Exception:%s', e)
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())

View File

@@ -0,0 +1,44 @@
import os
import platform
from . import logger
class SupportOSCommand(object):
@classmethod
def get_size(cls, path):
from support import SupportFile, SupportSubprocess, SupportUtil
if platform.system() == 'Windows':
#https://docs.microsoft.com/en-us/sysinternals/downloads/du
"""
bin = r'C:\SJVA3\data\bin\du64.exe'
command = [bin, '-c', '-nobanner', f'"{path}"']
data = ToolSubprocess.execute_command_return(command, force_log=True)
logger.warning(data)
ret = {}
tmp = data.split('\t')
ret['target'] = tmp[1].strip()
ret['size'] = int(tmp[0].strip())
ret['sizeh'] = ToolUtil.sizeof_fmt(ret['size'])
"""
ret = {}
ret['target'] = path
if os.path.exists(path):
if os.path.isdir(path):
ret['size'] = SupportFile.size(start_path=path)
else:
ret['size'] = os.stat(path).st_size
ret['sizeh'] = SupportUtil.sizeof_fmt(ret['size'])
return ret
else:
command = ['du', '-bs', path]
data = SupportSubprocess.execute_command_return(command)
ret = {}
tmp = data['log'].split('\t')
ret['target'] = tmp[1].strip()
ret['size'] = int(tmp[0].strip())
ret['sizeh'] = SupportUtil.sizeof_fmt(ret['size'])
return ret

25
lib/support/base/slack.py Normal file
View File

@@ -0,0 +1,25 @@
import os
import traceback
try:
from slack_sdk.webhook import WebhookClient
except:
os.system('pip3 install slack-sdk')
from slack_sdk.webhook import WebhookClient
from . import logger
class SupportSlack:
@classmethod
def send_slack_message(cls, text, webhook_url=None, image_url=None, disable_notification=None):
try:
if webhook_url is None:
return False
webhook = WebhookClient(webhook_url)
if image_url is not None:
webhook.send(text=text, blocks=[{"type": "image", "title": {"type": "plain_text", "text": "Image", "emoji": True}, "image_url": image_url, "alt_text": "Image"}])
webhook.send(text=text)
return True
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False

View File

@@ -1,3 +1,6 @@
import re
import traceback
from . import logger
@@ -5,22 +8,78 @@ class SupportString(object):
@classmethod
def get_cate_char_by_first(cls, title): # get_first
value = ord(title[0].upper())
if value >= ord('0') and value <= ord('9'): return '0Z'
elif value >= ord('A') and value <= ord('Z'): return '0Z'
elif value >= ord('') and value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value <= ord(''): return ''
else: return '0Z'
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
return '0Z'
@classmethod
def is_include_hangul(cls, text):
try:
hanCount = len(re.findall(u'[\u3130-\u318F\uAC00-\uD7A3]+', text))
return hanCount > 0
except:
return False
@classmethod
def language_info(cls, text):
try:
text = text.strip().replace(' ', '')
all_count = len(text)
han_count = len(re.findall('[\u3130-\u318F\uAC00-\uD7A3]', text))
eng_count = len(re.findall('[a-zA-Z]', text))
etc_count = len(re.findall('[0-9]', text))
etc_count += len(re.findall('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\|\(\)\[\]\<\>`\'…》:]', text))
if all_count == etc_count:
return (0,0)
han_percent = int(han_count * 100 / (all_count-etc_count))
eng_percent = int(eng_count * 100 / (all_count-etc_count))
return (han_percent, eng_percent)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False
@classmethod
def remove_special_char(cls, text):
return re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\|\(\)\[\]\<\>`\'…》:]', '', text)
@classmethod
def remove_emoji(cls, text, char=''):
import re
emoji_pattern = re.compile("["
u"\U0001F600-\U0001F64F" # emoticons
u"\U0001F300-\U0001F5FF" # symbols & pictographs
u"\U0001F680-\U0001F6FF" # transport & map symbols
u"\U0001F1E0-\U0001F1FF" # flags (iOS)
u"\U00002500-\U00002BEF" # chinese char
u"\U00002702-\U000027B0"
u"\U00002702-\U000027B0"
#u"\U000024C2-\U0001F251"
u"\U0001f926-\U0001f937"
u"\U00010000-\U0010ffff"
u"\u2640-\u2642"
u"\u2600-\u2B55"
u"\u200d"
u"\u23cf"
u"\u23e9"
u"\u231a"
u"\ufe0f" # dingbats
u"\u3030"
"]+", flags=re.UNICODE)
# Remove emojis from the text
text = emoji_pattern.sub(char, text)
return text

View File

@@ -1,5 +1,6 @@
import io
import json
import locale
import os
import platform
import queue
@@ -20,7 +21,7 @@ def demote(user_uid, user_gid):
class SupportSubprocess(object):
@classmethod
def command_for_windows(cls, command: list) -> str or list:
def command_for_windows(cls, command: list):
if platform.system() == 'Windows':
tmp = []
if type(command) == type([]):
@@ -43,17 +44,32 @@ class SupportSubprocess(object):
iter_arg = ''
if platform.system() == 'Windows':
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=shell, env=env, encoding='utf8')
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=shell, env=env, encoding='utf8', bufsize=0)
else:
if uid == None:
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=shell, env=env, encoding='utf8')
else:
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=shell, env=env, preexec_fn=demote(uid, gid), encoding='utf8')
new_ret = {'status':'finish', 'log':None}
def func(ret):
with process.stdout:
try:
for line in iter(process.stdout.readline, iter_arg):
ret.append(line.strip())
if log:
logger.debug(ret[-1])
except:
pass
result = []
thread = threading.Thread(target=func, args=(result,))
thread.setDaemon(True)
thread.start()
#thread.join()
try:
#process.communicate()
process_ret = process.wait(timeout=timeout) # wait for the subprocess to exit
except:
import psutil
@@ -62,14 +78,17 @@ class SupportSubprocess(object):
proc.kill()
process.kill()
new_ret['status'] = "timeout"
#logger.error(process_ret)
thread.join()
#ret = []
#with process.stdout:
# for line in iter(process.stdout.readline, iter_arg):
# ret.append(line.strip())
# if log:
# logger.debug(ret[-1])
ret = []
with process.stdout:
for line in iter(process.stdout.readline, iter_arg):
ret.append(line.strip())
if log:
logger.debug(ret[-1])
ret = result
#logger.error(ret)
if format is None:
ret2 = '\n'.join(ret)
elif format == 'json':
@@ -82,20 +101,29 @@ class SupportSubprocess(object):
break
ret2 = json.loads(''.join(ret[index:]))
except:
ret2 = None
ret2 = ret
new_ret['log'] = ret2
return new_ret
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
logger.error('command : %s', command)
finally:
try:
if process.stdout:
process.stdout.close()
if process.stdin:
process.stdin.close()
except Exception as e:
pass
__instance_list = []
def __init__(self, command, print_log=False, shell=False, env=None, timeout=None, uid=None, gid=None, stdout_callback=None, call_id=None):
def __init__(self, command, print_log=False, shell=False, env=None, timeout=None, uid=None, gid=None, stdout_callback=None, call_id=None, callback_line=True):
self.command = command
self.print_log = print_log
self.shell = shell
@@ -108,6 +136,7 @@ class SupportSubprocess(object):
self.stdout_queue = None
self.call_id = call_id
self.timestamp = time.time()
self.callback_line = callback_line
def start(self, join=True):
@@ -127,13 +156,15 @@ class SupportSubprocess(object):
self.command = self.command_for_windows(self.command)
logger.debug(f"{self.command=}")
if platform.system() == 'Windows':
self.process = subprocess.Popen(self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=self.shell, env=self.env, encoding='utf8', bufsize=0)
self.process = subprocess.Popen(self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=self.shell, env=self.env, encoding='utf8', bufsize=0)
else:
if self.uid == None:
self.process = subprocess.Popen(self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=self.shell, env=self.env, encoding='utf8', bufsize=0)
self.process = subprocess.Popen(self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=self.shell, env=self.env, encoding='utf8', bufsize=0)
else:
self.process = subprocess.Popen(self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=self.shell, env=self.env, preexec_fn=demote(self.uid, self.gid), encoding='utf8', bufsize=0)
self.process = subprocess.Popen(self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=self.shell, env=self.env, preexec_fn=demote(self.uid, self.gid), encoding='utf8', bufsize=0)
SupportSubprocess.__instance_list.append(self)
self.send_stdout_callback(self.call_id, 'START', None)
self.__start_communicate()
self.__start_send_callback()
if self.process is not None:
@@ -142,17 +173,18 @@ class SupportSubprocess(object):
self.process_close()
else:
self.process.wait()
self.remove_instance(self)
logger.info(f"{self.command} END")
except Exception as e:
logger.error(f'Exception:{str(e)}')
logger.error(traceback.format_exc())
logger.warning(self.command)
if self.stdout_callback != None:
self.stdout_callback('error', str(e))
self.stdout_callback('error', str(traceback.format_exc()))
self.send_stdout_callback(self.call_id, 'ERROR', str(e))
self.send_stdout_callback(self.call_id, 'ERROR', str(traceback.format_exc()))
finally:
if self.stdout_callback != None:
self.stdout_callback('thread_end', None)
#self.stdout_callback(self.call_id, 'thread_end', None)
pass
def __start_communicate(self):
@@ -164,7 +196,11 @@ class SupportSubprocess(object):
def rdr():
while True:
buf = self.process.stdout.read(1)
try:
buf = self.process.stdout.read(1)
except:
continue
#print(buf)
if buf:
_queue.put( buf )
else:
@@ -192,7 +228,9 @@ class SupportSubprocess(object):
if r is not None:
#print(f"{r=}")
self.stdout_queue.put(r)
self.stdout_queue.put('\n')
self.stdout_queue.put('<END>')
self.stdout_queue.put('\n')
for tgt in [rdr, clct]:
th = threading.Thread(target=tgt)
th.setDaemon(True)
@@ -204,16 +242,36 @@ class SupportSubprocess(object):
def func():
while self.stdout_queue:
line = self.stdout_queue.get()
#logger.error(line)
if line == '<END>':
if self.stdout_callback != None:
self.stdout_callback('end', None)
self.send_stdout_callback(self.call_id, 'END', None)
break
else:
if self.stdout_callback != None:
self.stdout_callback('log', line)
self.send_stdout_callback(self.call_id, 'LOG', line)
self.remove_instance(self)
th = threading.Thread(target=func, args=())
def func_callback_line():
previous = ''
while self.stdout_queue:
receive = previous + self.stdout_queue.get()
lines = receive.split('\n')
previous = lines[-1]
for line in lines[:-1]:
line = line.strip()
# TODO
#logger.error(line)
if line == '<END>':
self.send_stdout_callback(self.call_id, 'END', None)
break
else:
self.send_stdout_callback(self.call_id, 'LOG', line)
self.remove_instance(self)
if self.callback_line:
th = threading.Thread(target=func_callback_line, args=())
else:
th = threading.Thread(target=func, args=())
th.setDaemon(True)
th.start()
@@ -243,6 +301,15 @@ class SupportSubprocess(object):
self.process.stdin.write(f'{cmd}\n')
self.process.stdin.flush()
def send_stdout_callback(self, call_id, mode, data):
try:
if self.stdout_callback != None:
self.stdout_callback(self.call_id, mode, data)
except Exception as e:
logger.error(f'Exception:{str(e)}')
logger.error(f"[{call_id}] [{mode}] [{data}]")
#logger.error(traceback.format_exc())
@classmethod
def all_process_close(cls):
@@ -272,3 +339,6 @@ class SupportSubprocess(object):
if instance.call_id == call_id:
return instance
@classmethod
def get_list(cls):
return cls.__instance_list

View File

@@ -1,5 +1,7 @@
import time
import traceback
import requests
from telepot_mod import Bot
from . import logger
@@ -12,11 +14,21 @@ class SupportTelegram:
try:
bot = Bot(bot_token)
if image_url is not None:
bot.sendPhoto(chat_id, image_url, disable_notification=disable_notification)
logger.debug(image_url)
for i in range(5):
if requests.get(image_url).status_code == 200:
break
else:
time.sleep(3)
try:
bot.sendPhoto(chat_id, image_url, disable_notification=disable_notification)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
bot.sendMessage(chat_id, text, disable_web_page_preview=True, disable_notification=disable_notification)
return True
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
logger.debug('Chatid:%s', chat_id)
return False

View File

@@ -1,8 +1,11 @@
import os, traceback, io, re, json, codecs
import json
import time
import traceback
from functools import wraps
from . import logger
from functools import wraps
import time
def pt(f):
@wraps(f)
def wrapper(*args, **kwds):
@@ -26,9 +29,9 @@ class SupportUtil(object):
def sizeof_fmt(cls, num, suffix='Bytes'):
for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
if abs(num) < 1024.0:
return "%3.1f%s%s" % (num, unit, suffix)
return "%3.1f %s%s" % (num, unit, suffix)
num /= 1024.0
return "%.1f%s%s" % (num, 'Y', suffix)
return "%.1f %s%s" % (num, 'Y', suffix)
@classmethod
def is_arm(cls):

View File

@@ -1,5 +1,11 @@
import os
import traceback
import yaml
from . import logger
class SupportYaml(object):
@classmethod
def write_yaml(cls, filepath, data):
@@ -11,3 +17,41 @@ class SupportYaml(object):
with open(filepath, encoding='utf8') as file:
data = yaml.load(file, Loader=yaml.FullLoader)
return data
@classmethod
def copy_section(cls, source_file, target_file, section_name):
from support import SupportFile
try:
if os.path.exists(source_file) == False:
return 'not_exist_source_file'
if os.path.exists(target_file) == False:
return 'not_exist_target_file'
lines = SupportFile.read_file(source_file).split('\n')
section = {}
current_section_name = None
current_section_data = None
for line in lines:
line = line.strip()
if line.startswith('# SECTION START : '):
current_section_name = line.split(':')[1].strip()
current_section_data = []
if current_section_data is not None:
current_section_data.append(line)
if line.startswith('# SECTION END'):
section[current_section_name] = current_section_data
current_section_name = current_section_data = None
if section_name not in section:
return 'not_include_section'
data = '\n'.join(section[section_name])
source_data = SupportFile.read_file(target_file)
source_data = source_data + f"\n{data}\n"
SupportFile.write_file(target_file, source_data)
return 'success'
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return 'exception'

View File

@@ -116,7 +116,11 @@ class SupportFfmpeg(object):
header_count = 0
if self.proxy is None:
if self.headers is None:
command = [self.__ffmpeg_path, '-y', '-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
if platform.system() == 'Windows':
command = [self.__ffmpeg_path, '-y', '-i', f'"{self.url}"', '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
else:
command = [self.__ffmpeg_path, '-y', '-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
else:
headers_command = []
tmp = ""
@@ -136,9 +140,15 @@ class SupportFfmpeg(object):
if len(tmp) > 0:
headers_command.append('-headers')
headers_command.append(f'{tmp}')
command = [self.__ffmpeg_path, '-y'] + headers_command + ['-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
if platform.system() == 'Windows':
command = [self.__ffmpeg_path, '-y'] + headers_command + ['-i', f'"{self.url}"', '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
else:
command = [self.__ffmpeg_path, '-y'] + headers_command + ['-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
else:
command = [self.__ffmpeg_path, '-y', '-http_proxy', self.proxy, '-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
if platform.system() == 'Windows':
command = [self.__ffmpeg_path, '-y', '-http_proxy', self.proxy, '-i', f'"{self.url}"', '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
else:
command = [self.__ffmpeg_path, '-y', '-http_proxy', self.proxy, '-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
if platform.system() == 'Windows':
@@ -159,7 +169,7 @@ class SupportFfmpeg(object):
return
except:
pass
#logger.error(' '.join(command))
logger.error(' '.join(command))
command = SupportSubprocess.command_for_windows(command)
if platform.system() == 'Windows' and header_count > 1:
@@ -216,8 +226,8 @@ SET CRLF=^
else:
if os.path.exists(self.temp_fullpath):
os.remove(self.temp_fullpath)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
arg = {'type':'last', 'status':self.status, 'data' : self.get_data()}
@@ -347,6 +357,7 @@ SET CRLF=^
return data
def send_to_listener(self, **arg):
print(arg)
if self.total_callback_function != None:
self.total_callback_function(**arg)
if self.callback_function is not None and self.callback_function != self.total_callback_function:

View File

@@ -0,0 +1,27 @@
import traceback
from support import SupportSubprocess, logger
class SupportFfprobe:
__ffprobe_path = 'ffprobe'
@classmethod
def initialize(cls, __ffprobe_path):
cls.__ffprobe_path = __ffprobe_path
@classmethod
def ffprobe(cls, filepath, ffprobe_path=None, option=None):
try:
if ffprobe_path == None:
ffprobe_path = cls.__ffprobe_path
command = [ffprobe_path, '-v', 'quiet', '-print_format', 'json', '-show_format', '-show_streams', filepath]
if option is not None:
command += option
logger.warning(' '.join(command))
ret = SupportSubprocess.execute_command_return(command, format='json')
return ret['log']
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())

View File

@@ -204,9 +204,9 @@ class GoogleSheetBase:
break
except gspread.exceptions.APIError:
self.sleep_exception()
except Exception as exception:
except Exception as e:
logger.error(f"{key} - {value}")
logger.error('Exception:%s', exception)
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
logger.error(self.header_info)
self.sleep_exception()

View File

@@ -0,0 +1,197 @@
import json
import os
import traceback
from support import SupportSubprocess, d, logger
class SupportRclone(object):
__instance_list = []
__rclone_path = 'rclone'
__rclone_config_path = 'rclone.conf'
@classmethod
def initialize(cls, __rclone_path, __rclone_config_path):
cls.__rclone_path = __rclone_path
cls.__rclone_config_path = __rclone_config_path
@classmethod
def get_rclone_path(cls):
return cls.__rclone_path
@classmethod
def __get_cmd(cls, config_path=None):
command = [cls.__rclone_path]
if config_path == None:
command += ['--config', cls.__rclone_config_path]
else:
command += ['--config', config_path]
return command
@classmethod
def rclone_cmd(cls):
return [cls.__rclone_path, '--config', cls.__rclone_config_path]
@classmethod
def get_version(cls, rclone_path=None):
try:
if rclone_path == None:
rclone_path = cls.__rclone_path
cmd = [rclone_path, '--version']
result = SupportSubprocess.execute_command_return(cmd)
if result != None and result['status'] == 'finish':
return result['log']
except Exception as e:
logger.error(f'Exception:{str(e)}')
logger.error(traceback.format_exc())
@classmethod
def config_list(cls, rclone_path=None, rclone_config_path=None, option=None):
try:
if rclone_path == None:
rclone_path = cls.__rclone_path
if rclone_config_path == None:
rclone_config_path = cls.__rclone_config_path
if os.path.exists(rclone_config_path) == False:
return
command = [rclone_path, '--config', rclone_config_path, 'config', 'dump']
if option is not None:
command += option
result = SupportSubprocess.execute_command_return(command, format='json')
for key, value in result['log'].items():
if 'token' in value and value['token'].startswith('{'):
value['token'] = json.loads(value['token'])
return result['log']
except Exception as e:
logger.error(f'Exception:{str(e)}')
logger.error(traceback.format_exc())
@classmethod
def get_config(cls, remote_name, rclone_path=None, rclone_config_path=None, option=None):
try:
data = cls.config_list(rclone_path=rclone_path, rclone_config_path=rclone_config_path, option=option)
return data.get(remote_name, None)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@classmethod
def lsjson(cls, remote_path, config_path=None, option=None):
return cls.__execute_one_param('lsjson', remote_path, config_path=config_path, option=option, format='json')
@classmethod
def lsf(cls, remote_path, config_path=None, option=None):
if option == None:
option = ['--max-depth=1']
return cls.__execute_one_param('lsf', remote_path, config_path=config_path, option=option, format='json')
@classmethod
def size(cls, remote_path, config_path=None, option=None):
if option == None:
option = ['--json']
return cls.__execute_one_param('size', remote_path, config_path=config_path, option=option, format='json')
@classmethod
def mkdir(cls, remote_path, config_path=None, option=None):
return cls.__execute_one_param('mkdir', remote_path, config_path=config_path, option=option, format='json')
@classmethod
def purge(cls, remote_path, config_path=None, option=None):
return cls.__execute_one_param('purge', remote_path, config_path=config_path, option=option, format='json')
@classmethod
def __execute_one_param(cls, command, remote_path, config_path=None, option=None, format=None):
try:
command = cls.__get_cmd(config_path) + [command, remote_path]
if option is not None:
command += option
result = SupportSubprocess.execute_command_return(command, format=format)
ret = None
if result != None and result['status'] == 'finish':
ret = result['log']
return ret
except Exception as e:
logger.error(f'Exception:{str(e)}')
logger.error(traceback.format_exc())
@classmethod
def copy(cls, src, tar, config_path=None, option=None):
return cls.__execute_two_param('copy', src, tar, config_path=config_path, option=option)
@classmethod
def copy_server_side(cls, src, tar, config_path=None, option=None):
if option == None:
option = ['--drive-server-side-across-configs=true', '--delete-empty-src-dirs']
return cls.__execute_two_param('copy', src, tar, config_path=config_path, option=option)
@classmethod
def move(cls, src, tar, config_path=None, option=None):
return cls.__execute_two_param('move', src, tar, config_path=config_path, option=option)
@classmethod
def move_server_side(cls, src, tar, config_path=None, option=None):
if option == None:
option = ['--drive-server-side-across-configs=true', '--delete-empty-src-dirs']
return cls.__execute_two_param('move', src, tar, config_path=config_path, option=option)
@classmethod
def __execute_two_param(cls, command, src, tar, config_path=None, option=None, format=None):
try:
command = cls.__get_cmd(config_path) + [command, src, tar]
if option is not None:
command += option
result = SupportSubprocess.execute_command_return(command, format=format)
ret = None
if result != None and result['status'] == 'finish':
ret = result['log']
return ret
except Exception as e:
logger.error(f'Exception:{str(e)}')
logger.error(traceback.format_exc())
@classmethod
def getid(cls, remote_path, config_path=None, option=None):
try:
command = cls.__get_cmd(config_path) + ['backend', 'getid', remote_path]
if option is not None:
command += option
result = SupportSubprocess.execute_command_return(command)
ret = None
if result != None and result['status'] == 'finish':
ret = result['log']
if ret is not None and (len(ret.split(' ')) > 1 or ret == ''):
ret = None
return ret
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@classmethod
def chpar(cls, src, tar, config_path=None, option=None):
try:
command = cls.__get_cmd(config_path) + ['backend', 'chpar', src, tar, '-o', 'depth=1', '-o', 'delete-empty-src-dir', '--drive-use-trash=false']
if option is not None:
command += option
result = SupportSubprocess.execute_command_return(command)
ret = None
if result != None and result['status'] == 'finish':
ret = result['log']
return True
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False

View File

@@ -0,0 +1,114 @@
import os
from support import d, logger
try:
from selenium import webdriver
except:
os.system("pip install --upgrade selenium")
from selenium import webdriver
import base64
import threading
import time
import traceback
from selenium import webdriver
from selenium.common.exceptions import UnexpectedAlertPresentException
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select, WebDriverWait
from webdriver_manager.chrome import ChromeDriverManager
class SupportSimpleSelenium(object):
def __init__(self, P, mode="local", headless=False, remote=None):
self.P = P
self.driver = None
self.timeout = 5
self.driver_init(mode=mode, headless=headless, remote=remote)
def driver_init(self, mode='local', headless=False, remote=None):
if mode == 'local':
from selenium.webdriver.chrome.options import Options
options = Options()
if headless:
options.add_argument('headless')
self.driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
elif mode == 'remote':
from selenium.webdriver.chrome.options import Options
options = Options()
#options.set_preference("general.useragent.override", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36")
#options.set_preference("general.platform.override", "Win32")
self.driver = webdriver.Remote(remote, options=options)
def get_pagesoruce(self, url, wait_xpath="/html/body", retry=True):
try:
self.driver.get(url)
WebDriverWait(self.driver, self.timeout).until(lambda driver: driver.find_element(By.XPATH, wait_xpath))
return self.driver.page_source
except Exception as e:
logger.error('Exception:%s', e)
logger.error(traceback.format_exc())
self.driver_quit()
if retry:
return self.get_pagesoruce(url, wait_xpath=wait_xpath, retry=False)
def driver_quit(self):
if self.driver != None:
def func():
self.driver.quit()
self.driver = None
#self.logger.debug('driver quit..')
th = threading.Thread(target=func, args=())
th.setDaemon(True)
th.start()
def get_downloaded_files(self):
if not self.driver.current_url.startswith("chrome://downloads"):
self.driver.get("chrome://downloads/")
#driver.implicitly_wait(4)
self.driver.implicitly_wait(2)
return self.driver.execute_script( \
"return document.querySelector('downloads-manager') "
" .shadowRoot.querySelector('#downloadsList') "
" .items.filter(e => e.state === 'COMPLETE') "
" .map(e => e.filePath || e.file_path || e.fileUrl || e.file_url); ")
def get_file_content(self, path):
elem = self.driver.execute_script( \
"var input = window.document.createElement('INPUT'); "
"input.setAttribute('type', 'file'); "
"input.hidden = true; "
"input.onchange = function (e) { e.stopPropagation() }; "
"return window.document.documentElement.appendChild(input); " )
elem._execute('sendKeysToElement', {'value': [ path ], 'text': path})
result = self.driver.execute_async_script( \
"var input = arguments[0], callback = arguments[1]; "
"var reader = new FileReader(); "
"reader.onload = function (ev) { callback(reader.result) }; "
"reader.onerror = function (ex) { callback(ex.message) }; "
"reader.readAsDataURL(input.files[0]); "
"input.remove(); "
, elem)
if not result.startswith('data:') :
raise Exception("Failed to get file content: %s" % result)
return base64.b64decode(result[result.find('base64,') + 7:])
# docker run -d --name selenium_chromium -it -p 4446:4444 -p 5902:5900 -p 7902:7900 --shm-size 2g seleniarm/standalone-chromium:latest

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -36,7 +36,7 @@ class CustomFormatter(logging.Formatter):
# pathname filename
#format = "[%(asctime)s|%(name)s|%(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
__format = '[{yellow}%(asctime)s{reset}|{color}%(levelname)s{reset}|{green}%(name)s{reset} %(pathname)s:%(lineno)s] {color}%(message)s{reset}' if os.environ.get('LOGGER_PATHNAME', "False") == "True" else '[{yellow}%(asctime)s{reset}|{color}%(levelname)s{reset}|{green}%(name)s{reset} %(filename)s:%(lineno)s] {color}%(message)s{reset}'
__format = '[{yellow}%(asctime)s{reset}|{color}%(levelname)s{reset}|{green}%(name)s{reset}|%(pathname)s:%(lineno)s] {color}%(message)s{reset}' if os.environ.get('LOGGER_PATHNAME', "False") == "True" else '[{yellow}%(asctime)s{reset}|{color}%(levelname)s{reset}|{green}%(name)s{reset}|%(filename)s:%(lineno)s] {color}%(message)s{reset}'
FORMATS = {
logging.DEBUG: __format.format(color=grey, reset=reset, yellow=yellow, green=green),
@@ -53,15 +53,13 @@ class CustomFormatter(logging.Formatter):
def get_logger(name=None, log_path=None):
if os.environ.get('FF') == 'true':
name = 'framework'
if name == None:
name = sys.argv[0].rsplit('.', 1)[0]
logger = logging.getLogger(name)
if not logger.handlers:
level = logging.DEBUG
logger.setLevel(level)
formatter = logging.Formatter(u'[%(asctime)s|%(levelname)s|%(filename)s:%(lineno)s] %(message)s')
formatter = logging.Formatter(u'[%(asctime)s|%(levelname)s|%(name)s|%(filename)s:%(lineno)s] %(message)s')
def customTime(*args):
utc_dt = utc.localize(datetime.utcnow())
my_tz = timezone("Asia/Seoul")

View File

@@ -4,6 +4,7 @@ from support import SupportSC
try:
if os.path.exists(os.path.join(os.path.dirname(__file__), 'tving.py')):
#from .cppl import SupportCppl
from .kakaotv import SupportKakaotv
from .seezn import SupportSeezn
from .tving import SupportTving
@@ -13,5 +14,6 @@ try:
SupportWavve = SupportSC.load_module_f(__file__, 'wavve').SupportWavve
SupportSeezn = SupportSC.load_module_f(__file__, 'seezn').SupportSeezn
SupportKakaotv = SupportSC.load_module_f(__file__, 'kakaotv').SupportKakaotv
#SupportCppl = SupportSC.load_module_f(__file__, 'cppl').SupportCppl
except:
pass

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,277 @@
- name: "기본"
list:
- title: "파일 매니저"
package_name: "flaskfilemanager"
developer: "stevelittlefish"
home: "https://github.com/flaskfarm/flaskfilemanager"
description: "RichFilemanager를 Flask에서 동작하도록 한 FlaskFileManager 포크"
- title: "터미널"
package_name: "terminal"
developer: "joyful"
home: "https://github.com/flaskfarm/terminal"
description: "리눅스 전용 심플 터미널. xterm.js client"
- title: "편집기"
package_name: "flaskcode"
developer: "sujeetkv"
home: "https://github.com/flaskfarm/flaskcode"
description: "flaskcode를 fork한 문서 편집기"
- name: "보조 & 라이브러리"
list:
- title: "번역"
package_name: "trans"
developer: "FlaskFarm"
home: "https://github.com/flaskfarm/trans"
description: "번역 관련 설정 및 API 제공"
- title: "support_site"
package_name: "support_site"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/support_site"
description: "사이트 크롤링 라이브러리"
- title: "Gdrive 라이브러리 for FlaskFarm"
package_name: "libgdrive"
developer: "orial"
home: "https://github.com/byorial/libgdrive"
description: "구글드라이브 API 라이브러리"
- name: "외부 연결"
list:
- title: "Rclone"
package_name: "rclone"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/rclone"
description: "Rclone을 좀 더 쉽게 사용하기 위한 플러그인"
- title: "PLEX MATE"
package_name: "plex_mate"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/plex_mate"
description: "PLEX와 동일 기기에서 동작하는 툴"
- title: "vnStat"
package_name: "vnStat"
developer: "by275"
home: "https://github.com/by275/vnStat"
description: "vnStat 정보를 보여주는 플러그인"
- title: "115 TOOL"
package_name: "tool_115"
developer: "soju6jan"
home: "https://github.com/soju6jan/tool_115"
description: "115 TOOL"
- name: "도구"
list:
- title: "디스코드 봇"
package_name: "discord_bot"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/discord_bot"
description: "디스코드 봇"
- title: "정적 호스트"
package_name: "static_host"
developer: "by275"
description: "정적 웹을 호스팅 하는 플러그인"
home: "https://github.com/by275/static_host"
- title: 'Flaskfarmaider'
package_name: 'flaskfarmaider'
developer: 'halfaider'
description: 'Flaskfarm 보조 플러그인'
home: 'https://github.com/halfaider/flaskfarmaider'
- title: "메타데이터"
package_name: "metadata"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/metadata"
description: "VIDEO 메타데이터 제공"
- title: "lotto"
package_name: "lotto"
developer: "honeypig5"
home: "https://github.com/hudulgi/lotto"
description: "로또 구매"
- title: "핫딜 알람"
package_name: "hotdeal_alarm"
developer: "dbswnschl"
home: "https://github.com/dbswnschl/hotdeal_alarm"
description: "핫딜 알리미"
- name: "파일처리"
list:
- title: "국내TV 파일처리"
package_name: "fp_ktv"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/fp_ktv"
description: "국내TV 영상 파일 전용 파일처리"
- title: "영화 파일처리"
package_name: "fp_movie"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/fp_movie"
description: "영화 파일처리"
- title: "musicProc2"
package_name: "musicProc2"
developer: "dyllislev"
home: "https://github.com/dyllisLev/musicProc2"
description: "음악정리"
- title: "해외TV 파일처리"
package_name: "fp_ftv"
developer: "kihyyo"
home: "https://github.com/kihyyo/fp_ftv"
description: "해외TV 영상 파일 전용 파일처리"
- title: "자막 툴"
package_name: "subtitle_tool"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/subtitle_tool"
description: "자막 관련 툴"
- name: "토렌트 & RSS"
list:
- title: "토렌트 정보"
package_name: "torrent_info"
developer: "by275"
home: "https://github.com/by275/torrent_info"
description: "토렌트 마그넷/파일 정보를 보여주는 플러그인"
- title: "티프리카"
package_name: "tfreeca"
developer: "by275"
home: "https://github.com/by275/tfreeca"
description: "티프리카 게시판 뷰어"
- name: "OTT"
list:
- title: "티빙 검색"
package_name: "tving_search"
developer: "by275"
home: "https://github.com/by275/tving_search"
description: "티빙 검색 플러그인"
- title: "FFMPEG"
package_name: "ffmpeg"
developer: "FlaskFarm"
home: "https://github.com/flaskfarm/ffmpeg"
description: "m3u8 다운로드 & 비디오파일 분석"
- title: "웨이브"
package_name: "wavve"
developer: "halfaider"
home: "https://github.com/halfaider/wavve"
description: "웨이브 VOD 다운로더"
- title: "티빙"
package_name: "tving"
developer: "kihyyo"
home: "https://github.com/kihyyo/tving"
description: "티빙 VOD 다운로더"
- title: "쿠팡플레이"
package_name: "cppl"
developer: "soju6jan"
home: "https://github.com/soju6jan/cppl"
description: "쿠팡플레이 VOD 다운로더"
- title: "DRM 다운로드"
package_name: "wv_tool"
developer: "soju6jan"
home: "https://github.com/soju6jan/wv_tool"
description: "DRM 영상 다운로드"
- title: "make_yaml"
package_name: "make_yaml"
developer: "kihyyo"
home: "https://github.com/kihyyo/make_yaml"
description: "OTT 정보로 직접 yaml로 만드는 플러그인"
- title: "RE_tool"
package_name: "RE_tool"
developer: "kihyyo"
home: "https://github.com/kihyyo/RE_tool"
description: "M3U8_RE 다운로드"
- name: "라이브"
list:
- title: "ALive"
package_name: "alive"
developer: "by275"
home: "https://github.com/by275/alive"
description: "라이브 방송 플러그인"
- title: "EPG"
package_name: "epg"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/epg"
description: "EPG 생성"
- title: "MY EPG"
package_name: "myepg"
developer: "include"
home: "https://github.com/myepg/myepg"
description: "EPG API 플러그인"
- title: "HDHomerun"
package_name: "hdhomerun"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/hdhomerun"
description: "HDHomerun 도구"
- title: "스포TV"
package_name: "ff_spotv"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_spotv"
description: "스포TV 방송 FF플러그인"
- title: "삼성TV플러스"
package_name: "ff_sstvplus"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_sstvplus"
description: "삼성TV플러스 방송 FF플러그인"
- title: "네이버스포츠"
package_name: "ff_nsports"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_nsports"
description: "네이버스포츠 방송 FF플러그인"
- title: "쿠팡플레이"
package_name: "ff_cpp"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_cpp"
description: "쿠팡플레이 방송 FF플러그인"
- title: "아프리카TV"
package_name: "ff_afrtv"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_afrtv"
description: "아프리카TV 방송 FF플러그인"
- title: "REYSTREAM"
package_name: "ff_reystream"
developer: "ssagajikorea"
description: "REYSTREAM 방송 FF플러그인"
home: "https://github.com/ssagajikorea/ff_reystream"
- title: "팝콘TV"
package_name: "ff_pktv"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_pktv"
description: "팝콘TV 방송 FF플러그인"
- title: "팬더TV"
package_name: "ff_pdtv"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_pdtv"
description: "팬더TV 방송 FF플러그인"
- title: "NEXTCAST"
package_name: "ff_nextcast"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_nextcast"
description: "NEXTCAST 방송 FF플러그인"
- title: "LIFETV365"
package_name: "ff_lifetv365"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_lifetv365"
description: "LIFETV365 방송 FF플러그인"
- title: "KLive+"
package_name: "klive_plus"
developer: "soju6jan"
home: "https://github.com/soju6jan/klive_plus"
description: "KLive+"
- name: "SJVA"
list:
- title: "SJVA"
package_name: "sjva"
developer: "soju6jan"
home: "https://github.com/soju6jan/sjva"
description: "SJVA 인증 & 사이트 연동"
- title: "구드공 툴"
package_name: "gds_tool"
developer: "soju6jan"
home: "https://github.com/soju6jan/gds_tool"
description: "복사요청, 제공, GDS 변경사항"
- title: "봇 다운로더"
package_name: "bot_downloader"
developer: "flaskfarm"
home: "https://github.com/soju6jan/bot_downloader"
description: "봇 다운로더"
# name: "기타"
# list:
# - title: "숫자 야구"
# package_name: "number_baseball"
# developer: "FlaskFarm"
# home: "https://github.com/flaskfarm/number_baseball"
# description: "숫자 야구 - 샘플 플러그인"
# - title: "샘플 플러그인"
# package_name: "sample"
# developer: "FlaskFarm"
# home: "https://github.com/flaskfarm/sample"
# description: "샘플 플러그인"

View File

@@ -0,0 +1,9 @@
### 알림
##### 텔레그램
* @BotFather 에게서 Bot 생성
* https://api.telegram.org/bot봇토큰/getUpdates 접속
* 본인의 봇과 대화
* https://api.telegram.org/bot봇토큰/getUpdates 접속
"from":{"id":879500000, 숫자가 본인의 Chat ID

View File

@@ -31,7 +31,7 @@ class ModuleHome(PluginModuleBase):
for key, value in F.app.config.items():
if key not in ['SECRET_KEY']:
data[key] = str(value)
ret = {'json':{'Framework':F.config, 'Flask':data}}
ret = {'json':{'Framework':F.config, 'Flask':data}, 'title':'config'}
return jsonify(ret)
@@ -56,7 +56,7 @@ class ModuleHome(PluginModuleBase):
ret = {}
ret['system'] = self.get_info()
ret['scheduler'] = scheduler.get_job_list_info()
F.socketio.emit("status", ret, namespace=f'/{P.package_name}/{name}', broadcast=True)
F.socketio.emit("status", ret, namespace=f'/{P.package_name}/{name}')
def get_info(self, mode=''):

View File

@@ -22,14 +22,11 @@ class ModuleLog(PluginModuleBase):
log_list.append(x)
arg['log_list'] = '|'.join(log_list)
arg['all_list'] = '|'.join(log_files)
arg['filename'] = 'framework.log'
print(request.form)
print(request.form)
print(request.form)
print(request.form)
arg['filename'] = 'all.log'
if 'filename' in request.form:
arg['filename'] = request.form['filename']
arg['filename'] = req.args.get('filename', arg['filename'])
return render_template(f'{__package__}_{name}.html', arg=arg)
except Exception as e:
P.logger.error(f'Exception:{str(e)}')

View File

@@ -1,14 +1,16 @@
import shutil
from support import SupportFile
from support import SupportFile, SupportYaml
from .setup import *
name = 'plugin'
class ModulePlugin(PluginModuleBase):
def __init__(self, P):
super(ModulePlugin, self).__init__(P, name=name, first_menu='list')
self.all_plugin_list = None
def process_menu(self, page, req):
@@ -36,14 +38,16 @@ class ModulePlugin(PluginModuleBase):
"""
for name, entity in F.PluginManager.all_package_list.items():
try:
if entity.get('version') == '3':
#data.append(entity)
data.append({'package_name':name})
else:
if 'P' in entity:
data.append(entity['P'].plugin_info)
data[-1]['loading'] = entity.get('loading')
data[-1]['status'] = entity.get('status')
data[-1]['log'] = entity.get('log')
else:
data.append({'package_name':name})
data[-1]['loading'] = entity.get('loading')
data[-1]['status'] = entity.get('status')
data[-1]['log'] = entity.get('log')
except Exception as e:
data.append({'package_name':name})
P.logger.error(f'Exception:{str(e)}')
@@ -64,6 +68,36 @@ class ModulePlugin(PluginModuleBase):
else:
ret['msg'] = info['path'] + "<br>폴더가 없습니다."
ret['ret'] = 'danger'
elif command == 'get_plugin_list_all':
if self.all_plugin_list == None:
filepath = os.path.join(os.path.dirname(__file__), 'files', 'all_plugin.yaml')
self.all_plugin_list = SupportYaml.read_yaml(filepath)
def get_plugin(_name):
for _cate in self.all_plugin_list:
for _plugin in _cate['list']:
if _plugin['package_name'] == _name:
P.logger.info(_name)
if _name == 'ff_reystream':
P.logger.info(_name)
return _plugin
for name, entity in F.PluginManager.all_package_list.items():
try:
_plugin = get_plugin(name)
if _plugin != None:
_plugin['loading'] = entity.get('loading')
_plugin['status'] = entity.get('status')
_plugin['log'] = entity.get('log')
_plugin['version'] = entity['P'].plugin_info['version']
except Exception as e:
data.append({'package_name':name})
P.logger.error(f'Exception:{str(e)}')
P.logger.error(traceback.format_exc())
ret['data'] = self.all_plugin_list
return jsonify(ret)

View File

@@ -53,7 +53,7 @@ class ModuleRoute(PluginModuleBase):
@F.socketio.on('connect', namespace=f'/{P.package_name}/restart')
def restart_socket_connect():
F.socketio.emit('connect', {}, namespace='/{P.package_name}/restart', broadcast=True)
F.socketio.emit('connect', {}, namespace='/{P.package_name}/restart')
def process_menu(self, page, req):

View File

@@ -2,8 +2,8 @@ import random
import string
import time
from support import (SupportDiscord, SupportFile, SupportSubprocess,
SupportTelegram)
from support import (SupportDiscord, SupportFile, SupportSlack,
SupportSubprocess, SupportTelegram, SupportYaml)
from tool import ToolModalCommand
from .setup import *
@@ -12,7 +12,7 @@ name = 'setting'
class ModuleSetting(PluginModuleBase):
db_default = {
'db_version' : '1',
'db_version' : '1.1',
'port' : '9999',
'ddns' : 'http://localhost:9999',
'use_login' : 'False',
@@ -34,6 +34,8 @@ class ModuleSetting(PluginModuleBase):
'notify_telegram_disable_notification' : 'False',
'notify_discord_use' : 'False',
'notify_discord_webhook' : '',
'notify_slack_use' : 'False',
'notify_slack_webhook' : '',
'notify_advaned_use' : 'False',
'notify.yaml': '', #직접 사용하지 않으나 저장 편의상.
'command_text': '',
@@ -91,10 +93,15 @@ class ModuleSetting(PluginModuleBase):
ret['msg'] = 'export.sh 파일이 없습니다.'
elif command == 'menu_save':
SupportFile.write_file(F.config['menu_yaml_filepath'], arg1 )
ret['msg'] = '저장하였습니다.'
from framework.init_menu import MenuManager
MenuManager.init_menu()
F.socketio.emit("refresh", {}, namespace='/framework', broadcast=True)
try:
SupportYaml.read_yaml(F.config['menu_yaml_filepath'])
ret['msg'] = '저장하였습니다.'
from framework.init_menu import MenuManager
MenuManager.init_menu()
F.socketio.emit("refresh", {}, namespace='/framework')
except:
ret['ret'] = "danger"
ret['msg'] = "yaml 형식에 맞지 않습니다"
elif command == 'notify_test':
if arg1 == 'telegram':
token, chatid, sound, text = arg2.split('||')
@@ -104,6 +111,9 @@ class ModuleSetting(PluginModuleBase):
elif arg1 == 'discord':
SupportDiscord.send_discord_message(arg3, webhook_url=arg2)
ret['msg'] = '메시지를 전송했습니다.'
elif arg1 == 'slack':
SupportSlack.send_slack_message(arg3, webhook_url=arg2)
ret['msg'] = '메시지를 전송했습니다.'
elif arg1 == 'advanced':
from tool import ToolNotify
ToolNotify.send_advanced_message(arg3, message_id=arg2)
@@ -122,7 +132,31 @@ class ModuleSetting(PluginModuleBase):
ret['type'] = 'warning'
elif command == 'command_run':
ret['msg'] = arg1
pass
SystemModelSetting.set('command_text', arg1)
# db이름 set/get key value
try:
tmp = arg1.strip().split(' ')
if tmp[0].startswith('setting'):
plugin = F.PluginManager.get_plugin_instance(tmp[1])
if len(tmp) == 2 or tmp[2] == 'all':
ret['json'] = plugin.ModelSetting.to_dict()
ret['ret'] = 'success'
elif tmp[2] == 'get':
ret['msg'] = plugin.ModelSetting.get(tmp[3])
ret['ret'] = 'success'
elif tmp[2] == 'set':
value = ""
if len(tmp) == 5:
value = tmp[4]
plugin.ModelSetting.set(tmp[3], value)
ret['msg'] = f"{tmp[1]} DB에 {tmp[3]}={value} 저장"
except Exception as e:
P.logger.error(f'Exception:{str(e)}')
P.logger.error(traceback.format_exc())
ret['msg'] = f"실행 실패: {str(e)}"
ret['type'] = 'danger'
elif command == 'celery_execute':
self.celery_execute(arg1, mode='foreground')
elif command == 'celery_execute_back':
@@ -138,8 +172,7 @@ class ModuleSetting(PluginModuleBase):
try:
if F.config['run_flask'] == False:
return
if SystemModelSetting.get_bool('celery_start_by_web'):
self.celery_execute()
if F.config['arg_repeat'] == 0 or SystemModelSetting.get('system_start_time') == '':
SystemModelSetting.set('system_start_time', datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
SystemModelSetting.set('repeat', str(F.config['arg_repeat']))
@@ -162,7 +195,11 @@ class ModuleSetting(PluginModuleBase):
from tool import ToolNotify
msg = f"시스템이 시작되었습니다.\n재시작: {F.config['arg_repeat']}"
ToolNotify.send_message(msg, message_id='system_start')
if SystemModelSetting.get_bool('celery_start_by_web'):
# 2022-11-14 DB는 flask가 만드는데 만들기전 celery를 실행해버림
from threading import Timer
Timer(10, self.celery_execute).start()
#self.celery_execute()
except Exception as e:
P.logger.error(f'Exception:{str(e)}')
@@ -173,9 +210,14 @@ class ModuleSetting(PluginModuleBase):
def setting_save_after(self, change_list):
if 'theme' in change_list or 'web_title' in change_list:
F.socketio.emit("refresh", {}, namespace='/framework', broadcast=True)
F.socketio.emit("refresh", {}, namespace='/framework')
elif 'notify.yaml' in change_list:
SupportFile.write_file(F.config['notify_yaml_filepath'], SystemModelSetting.get('notify.yaml'))
try:
SupportFile.write_file(F.config['notify_yaml_filepath'], SystemModelSetting.get('notify.yaml'))
SupportYaml.read_yaml(F.config['notify_yaml_filepath'])
except:
data = {'type':'danger', 'msg' : "알림 정책이 yaml 형식에 맞지 않습니다."}
F.socketio.emit("notify", data, namespace='/framework')
elif 'web_pw' in change_list:
import hashlib
enc = hashlib.md5()
@@ -185,6 +227,9 @@ class ModuleSetting(PluginModuleBase):
if SystemModelSetting.get('restart_interval') == '':
SystemModelSetting.set('restart_interval', '0')
self.__set_restart_scheduler()
elif 'log_level' in change_list:
F.set_level(SystemModelSetting.get_int('log_level'))
def __set_restart_scheduler(self):
@@ -258,6 +303,7 @@ class ModuleSetting(PluginModuleBase):
try:
time.sleep(1)
data = '정상입니다. 이 메시지는 celery 에서 반환됩니다. '
P.logger.info(data)
return data
except Exception as e:
P.logger.error(f'Exception:{str(e)}')

View File

@@ -1,4 +1,5 @@
import queue
import shlex
from support import SupportSubprocess
from tool import ToolModalCommand
@@ -25,7 +26,7 @@ class PageCommand(PluginPageBase):
ret = {'ret':'success'}
if command == 'foreground_command':
P.ModelSetting.set(f'{self.parent.name}_{self.name}_recent', arg1)
self.__foreground_execute(arg1, arg1.split(' '))
self.__foreground_execute(arg1, shlex.split(arg1))
return jsonify('')
elif command == 'job_new':
@@ -54,7 +55,7 @@ class PageCommand(PluginPageBase):
elif command == 'job_fore_execute':
db_item = ModelCommand.get_by_id(arg1)
cmd = (db_item.command + ' ' + db_item.args).strip()
self.__foreground_execute(f"Command ID: {db_item.id}", cmd.split(' '), db_item.id)
self.__foreground_execute(f"Command ID: {db_item.id}", shlex.split(cmd), db_item.id)
elif command == 'job_back_execute':
self.execute_thread_start(arg1)
ret['msg'] = "실행 요청을 하였습니다.<br>로그를 확인하세요."
@@ -64,8 +65,8 @@ class PageCommand(PluginPageBase):
ret['ret'] = 'danger'
ret['msg'] = "로그 파일이 없습니다."
elif command == 'task_sched':
job_id = req.form['arg1']
flag = (req.form['arg2'] == 'true')
job_id = arg1
flag = (arg2 == 'true')
scheduler_id = f'command_{job_id}'
if flag and F.scheduler.is_include(scheduler_id):
ret['msg'] = '이미 스케쥴러에 등록되어 있습니다.'
@@ -92,7 +93,7 @@ class PageCommand(PluginPageBase):
if command[0] != 'LOAD':
ToolModalCommand.start(title, [command])
else:
F.socketio.emit("command_modal_show", title, namespace='/framework', broadcast=True)
F.socketio.emit("command_modal_show", title, namespace='/framework')
def start_communicate_load(load_log_list):
def func():
while True:
@@ -100,7 +101,7 @@ class PageCommand(PluginPageBase):
load_log_list.truncate(0)
if logs:
P.logger.error(logs)
F.socketio.emit("command_modal_add_text", logs.strip() + '\n', namespace='/framework', broadcast=True)
F.socketio.emit("command_modal_add_text", logs.strip() + '\n', namespace='/framework')
if logs == '<<END>>':
break
time.sleep(0.3)
@@ -154,11 +155,12 @@ class PageCommand(PluginPageBase):
th = threading.Thread(target=self.execute_thread_function_by_job_id, args=(job_id,))
th.setDaemon(True)
th.start()
return th
def execute_thread_function_by_job_id(self, *args, **kwargs):
P.logger.error(d(args))
P.logger.error(d(kwargs))
#P.logger.error(d(args))
#P.logger.error(d(kwargs))
db_item = ModelCommand.get_by_id(args[0])
kwargs['id'] = args[0]
self.execute_thread_function((db_item.command + ' ' + db_item.args).strip(), **kwargs)
@@ -166,7 +168,7 @@ class PageCommand(PluginPageBase):
def execute_thread_function(self, command, **kwargs):
try:
cmd = command.split(' ')
cmd = shlex.split(command)
if cmd[0] == 'LOAD':
command_logger = F.get_logger(f"command_{kwargs['id']}")
@@ -177,8 +179,8 @@ class PageCommand(PluginPageBase):
def __init__(self, logger):
self.logger = logger
def stdout_callback(self, mode, text):
if mode == 'log':
def stdout_callback(self, call_id, mode, text):
if mode == 'LOG':
self.logger.debug(text)
else:
self.logger.debug(mode)
@@ -194,14 +196,17 @@ class PageCommand(PluginPageBase):
def plugin_load(self):
def plugin_load_thread():
try:
while F.config['loading_completed'] == False:
time.sleep(1)
db_items = ModelCommand.get_list()
for db_item in db_items:
if db_item.schedule_mode == 'startup':
self.execute_thread_start(db_item.id)
elif db_item.schedule_mode == 'scheduler' and db_item.schedule_auto_start:
self.__sched_add(db_item.id, db_item=db_item)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
try:
th = threading.Thread(target=plugin_load_thread)
@@ -219,7 +224,7 @@ class PageCommand(PluginPageBase):
job_id = f"command_{db_item.id}"
if scheduler.is_include(job_id):
return
job = Job(self.P.package_name, job_id, db_item.schedule_interval, self.execute_thread_function_by_job_id, db_item.description, args=db_item.id)
job = Job(self.P.package_name, job_id, db_item.schedule_interval, self.execute_thread_function_by_job_id, db_item.description, args=(db_item.id,))
scheduler.add_job_instance(job)
return True
except Exception as e:
@@ -286,6 +291,6 @@ class ModelCommand(ModelBase):
item['process'] = (SupportSubprocess.get_instance_by_call_id(f"command_{item['id']}") != None)
return data
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())

View File

@@ -14,6 +14,7 @@ __menu = {
{'uri': 'export', 'name': 'export.sh 파일'},
{'uri': 'celery', 'name': '비동기 작업(celery)'},
{'uri': 'notify', 'name': '알림'},
{'uri': 'manual/files/매뉴얼_설정.md', 'name': '매뉴얼'},
],
},
{
@@ -22,7 +23,7 @@ __menu = {
'list': [
#{'uri': 'setting', 'name': '설정'},
{'uri': 'list', 'name': '로딩 플러그인'},
#{'uri': 'all', 'name': '플러그인 목록'},
{'uri': 'all', 'name': '전체 플러그인 목록'},
],
},
{

View File

@@ -1,33 +1,9 @@
{% extends "base.html" %}
{% block content %}
</style>
<div>
{{ macros.setting_select_empty('log_select1', '로그 파일 선택 (.log)') }}
<!--{{ macros.setting_select_empty('log_select2', '로그 파일 선택 (.logX)') }}-->
<nav>
{{ macros.m_tab_head_start() }}
{{ macros.m_tab_head('old', '이전', true) }}
{{ macros.m_tab_head('new', '실시간', false) }}
{{ macros.m_tab_head_end() }}
</nav>
<div class="tab-content" id="nav-tabContent">
{{ macros.m_tab_content_start('old', true) }}
<div>
<textarea id="log" class="col-md-12" rows="30" charswidth="23" disabled style="background-color:#ffffff;visibility:hidden"></textarea>
</div>
{{ macros.m_tab_content_end() }}
{{ macros.m_tab_content_start('new', false) }}
<div>
<textarea id="add" class="col-md-12" rows="30" charswidth="23" disabled style="background-color:#ffffff;visibility:visible"></textarea>
</div>
<div class="form-inline">
<label class="form-check-label" for="auto_scroll">자동 스크롤</label>
<input id="auto_scroll" name="auto_scroll" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
<span class='text-left' style="padding-left:25px; padding-top:0px">
<button id="clear" class="btn btn-sm btn-outline-success">리셋</button>
</span>
</div>
{{ macros.m_tab_content_end() }}
<div id="log_div" class="bg-dark" style="overflow:auto; border-color: blue; border: solid 1px;">
</div>
</div>
@@ -56,14 +32,6 @@ function make_form() {
str += '<option value="' + data[i] + '">' + data[i] + '</option>';
}
$("#log_select1_div").html(str);
/*
str = '<select id="log_select" name="log_select" class="form-control form-control-sm">';
data = all_list.split('|')
for(var i in data) {
str += '<option value="' + data[i] + '">' + data[i] + '</option>';
}
$("#log_select2_div").html(str);
*/
}
$("body").on('change', '#log_select', function(e){
@@ -75,11 +43,9 @@ $("body").on('change', '#log_select', function(e){
socket.emit("start", {'filename':filename} );
});
function ResizeTextAreaAllLog() {
ClientHeight = window.innerHeight
$("#log").height(ClientHeight-300);
$("#add").height(ClientHeight-320);
$("#log_div").height(ClientHeight-180);
}
$(window).resize(function() {
@@ -88,17 +54,20 @@ $(window).resize(function() {
socket.on('on_start', function(data){
document.getElementById("log").innerHTML += data.data;
document.getElementById("log").scrollTop = document.getElementById("log").scrollHeight;
document.getElementById("log").style.visibility = 'visible';
lines = splitLines(data.data);
var html = '';
for (i in lines) {
html += logline(lines[i]);
}
$('#log_div').html(html)
document.getElementById("log_div").scrollTop = document.getElementById("log_div").scrollHeight;
$('#loading').hide();
});
socket.on('add', function(data){
if (data.filename == current_filename) {
var chk = $('#auto_scroll').is(":checked");
document.getElementById("add").innerHTML += data.data;
if (chk) document.getElementById("add").scrollTop = document.getElementById("add").scrollHeight;
$('#log_div').append(logline(data.data.trim()));
document.getElementById("log_div").scrollTop = document.getElementById("log_div").scrollHeight;
}
});

View File

@@ -8,7 +8,7 @@
<div>
<div class="row">
<div class="col-sm-12">
<canvas id="mycanvas" height="100vh"></canvas>
<h3>시스템</h3>
<hr>
{{ macros.info_text_and_buttons('python_version', 'Python', [['globalLinkBtn', '패키지 관리', [('url','/system/tool/python')]]], info['python_version']) }}
@@ -45,21 +45,102 @@
<h3>스케쥴</h3>
<div id="scheduler_list_div"></div>
</div> <!--전체-->
</div>
<!--전체-->
<script src="{{ url_for('static', filename='js/chartjs-utils.js') }}"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
<script>
const Utils = ChartUtils.init()
</script>
<script src="https://cdn.jsdelivr.net/npm/luxon@3.0.4"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@1.2.0"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-streaming@2.0.0"></script>
<script type="text/javascript">
$(document).ready(function(){
var socket = io.connect(window.location.href);
Chart.defaults.set("plugins.streaming", {
duration: 20000,
})
socket.on('start', function(data){
});
// used for example purposes
function getRandomIntInclusive(min, max) {
min = Math.ceil(min)
max = Math.floor(max)
return Math.floor(Math.random() * (max - min + 1)) + min
}
socket.on('status', function(data) {
make_system(data.system);
make_scheduler_list(data.scheduler);
});
});
$(document).ready(function () {
var socket = io.connect(window.location.href)
var postId = 1
socket.on("start", function (data) {})
socket.on("status", function (data) {
console.log(data.system.cpu_percent)
const now = Date.now()
//myChart.data.labels.push("T " + postId++)
/* */
myChart.data.datasets[0].data.push({ x: now, y: data.system.cpu_percent.replace(/.%/g, "") })
/* */
myChart.update()
make_system(data.system)
make_scheduler_list(data.scheduler)
})
/* const onRefresh = (chart) => {
const now = Date.now()
chart.data.datasets.forEach((dataset) => {
dataset.data.push({
x: now,
y: Utils.rand(0, 100),
})
})
}
*/
var ctx_live = document.getElementById("mycanvas")
var myChart = new Chart(ctx_live, {
type: "line",
data: {
labels: [],
datasets: [
{
label: "CPU",
backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
borderColor: Utils.CHART_COLORS.blue,
cubicInterpolationMode: "monotone",
data: [],
},
],
},
options: {
plugins: {
// Change options for ALL axes of THIS CHART
streaming: {
duration: 20000,
},
},
scales: {
x: {
type: "realtime",
realtime: {
duration: 60000,
refresh: 1000,
delay: 1000,
//onRefresh: onRefresh,
},
},
y: {
title: {
display: true,
//text: "Value",
},
},
},
interaction: {
intersect: false,
},
},
})
})
$("body").on('click', '#recent_version_btn', function(e){
e.preventDefault();
@@ -70,7 +151,7 @@ $("body").on('click', '#recent_version_btn', function(e){
$("body").on('click', '#config_show_btn', function(e){
e.preventDefault();
globalSendCommand('get_config', null, null, null, 'Config');
globalSendCommand('get_config');
});

Some files were not shown because too many files have changed in this diff Show More