Compare commits
10 Commits
695d26767e
...
526bd36c5a
| Author | SHA1 | Date | |
|---|---|---|---|
| 526bd36c5a | |||
| def4e9eeb8 | |||
| 6e747abf86 | |||
| 2512161203 | |||
| 2681f5a096 | |||
| cf19d79ef8 | |||
| 3a9765f7ea | |||
| 97d203cb86 | |||
| 0c53e1d2f2 | |||
| af9a38a973 |
21
.dockerignore
Normal file
21
.dockerignore
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Docker 무시 파일
|
||||||
|
# 빌드 시 컨테이너에 복사하지 않을 파일들
|
||||||
|
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
.venv
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.DS_Store
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# 데이터 폴더는 볼륨으로 마운트
|
||||||
|
data/
|
||||||
|
|
||||||
|
# 개발용 파일
|
||||||
|
*.md
|
||||||
|
*.log
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -129,7 +129,7 @@ dmypy.json
|
|||||||
|
|
||||||
|
|
||||||
# FlaksFarm
|
# FlaksFarm
|
||||||
config.yaml
|
# config.yaml
|
||||||
lib2/
|
lib2/
|
||||||
.vscode/
|
.vscode/
|
||||||
memo.txt
|
memo.txt
|
||||||
@@ -141,7 +141,7 @@ pre_start.sh
|
|||||||
*.code-workspace
|
*.code-workspace
|
||||||
false
|
false
|
||||||
*copy.py
|
*copy.py
|
||||||
*.sh
|
# *.sh
|
||||||
data/
|
data/
|
||||||
tmp/
|
tmp/
|
||||||
lib/support/site/tving.py
|
lib/support/site/tving.py
|
||||||
|
|||||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal 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
12
.idea/dataSources.xml
generated
Normal 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
20
.idea/flaskfarm.iml
generated
Normal 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>
|
||||||
76
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
76
.idea/inspectionProfiles/Project_Default.xml
generated
Normal 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>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal 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
4
.idea/misc.xml
generated
Normal 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
8
.idea/modules.xml
generated
Normal 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>
|
||||||
0
.idea/sonarlint/issuestore/0/3/03875fef6dc33ed50c8bc5f25df52c02e352a134
generated
Normal file
0
.idea/sonarlint/issuestore/0/3/03875fef6dc33ed50c8bc5f25df52c02e352a134
generated
Normal file
0
.idea/sonarlint/issuestore/0/c/0c528d2f014ab7c32dd27d4e5d79396e74f3e62c
generated
Normal file
0
.idea/sonarlint/issuestore/0/c/0c528d2f014ab7c32dd27d4e5d79396e74f3e62c
generated
Normal file
9
.idea/sonarlint/issuestore/0/e/0e68783ed60c8d2f67617374a92c1652fb6bdaee
generated
Normal file
9
.idea/sonarlint/issuestore/0/e/0e68783ed60c8d2f67617374a92c1652fb6bdaee
generated
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
cpython:S6019"FFix this reluctant quantifier that will only ever match 0 repetitions.(¨†…™üÿÿÿÿ
|
||||||
|
cpython:S6019"FFix this reluctant quantifier that will only ever match 0 repetitions.(Ìé©èùÿÿÿÿ
|
||||||
|
cpython: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.(‹Ò•–
|
||||||
|
6python:S125!"Remove this commented out code.(¡‚ÙÈ
|
||||||
|
Tpython:S5754)"<Specify an exception class to catch or reraise the exception(ˆÊÉ·
|
||||||
|
6python:S1252"Remove this commented out code.(ý<>™Ë
|
||||||
6
.idea/sonarlint/issuestore/5/8/58836750c643ef469da17133f44914292a82f3b3
generated
Normal file
6
.idea/sonarlint/issuestore/5/8/58836750c643ef469da17133f44914292a82f3b3
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
qpython:S3776
|
||||||
|
"TRefactor this function to reduce its Cognitive Complexity from 22 to the 15 allowed.(úÈœ‚ÿÿÿÿÿ
|
||||||
|
6python:S125"Remove this commented out code.(ÑêÏà
|
||||||
|
Tpython:S5754/"<Specify an exception class to catch or reraise the exception(ˆÊÉ·
|
||||||
|
6python:S125("Remove this commented out code.(£ÌÎæ
|
||||||
2
.idea/sonarlint/issuestore/6/2/627433fe5c5c7210e3062642e7963227a319d5c6
generated
Normal file
2
.idea/sonarlint/issuestore/6/2/627433fe5c5c7210e3062642e7963227a319d5c6
generated
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
lpython:S3776"TRefactor this function to reduce its Cognitive Complexity from 25 to the 15 allowed.(”ÒÑ
|
||||||
10
.idea/sonarlint/issuestore/8/2/82544e7bcd3de23afaf278a62d1180d65a1ef456
generated
Normal file
10
.idea/sonarlint/issuestore/8/2/82544e7bcd3de23afaf278a62d1180d65a1ef456
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
epython:S1192'"MDefine a constant instead of duplicating this literal 'Exception:%s' 9 times.(ï°Ð½
|
||||||
|
Lpython:S10669"/Merge this if statement with the enclosing one.(Íìáöüÿÿÿÿ
|
||||||
|
lpython:S3776"TRefactor this function to reduce its Cognitive Complexity from 50 to the 15 allowed.(<28>Øê
|
||||||
|
6python:S125/"Remove this commented out code.(“Ä¡–
|
||||||
|
lpython:S3776k"TRefactor this function to reduce its Cognitive Complexity from 39 to the 15 allowed.(ûãô”
|
||||||
|
rpython:S3776á"TRefactor this function to reduce its Cognitive Complexity from 58 to the 15 allowed.(ó‚‚åýÿÿÿÿ
|
||||||
|
Upython:S5754ü"<Specify an exception class to catch or reraise the exception(ˆÊÉ·
|
||||||
|
<python:S125¼"Remove this commented out code.(ÁêÕúûÿÿÿÿ
|
||||||
|
Upython:S5754é"<Specify an exception class to catch or reraise the exception(ˆÊÉ·
|
||||||
7
.idea/sonarlint/issuestore/9/5/95519e06d92ec11e26cf873c3c30e2fcedf78892
generated
Normal file
7
.idea/sonarlint/issuestore/9/5/95519e06d92ec11e26cf873c3c30e2fcedf78892
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
Bpython:S1481"%Remove the unused local variable "e".(¼–¯¸ÿÿÿÿÿ
|
||||||
|
6python:S125"Remove this commented out code.(™èÇÅ
|
||||||
|
;python:S125"Remove this commented out code.(à髈üÿÿÿÿ
|
||||||
|
6python:S125T"Remove this commented out code.(»—Ö
|
||||||
|
Cpython:S5806Y"+Rename this variable; it shadows a builtin.(Ç¡¡
|
||||||
|
6python:S125Z"Remove this commented out code.(¾§Ç¡
|
||||||
33
.idea/sonarlint/issuestore/e/e/eeb13886aba87bd6947610e1cba3283d308baf0c
generated
Normal file
33
.idea/sonarlint/issuestore/e/e/eeb13886aba87bd6947610e1cba3283d308baf0c
generated
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
epython:S5797I"HReplace this expression; used as a condition it will always be constant.(™¼ï€üÿÿÿÿ
|
||||||
|
Mpython:S1066ã"/Merge this if statement with the enclosing one.(„Õý“þÿÿÿÿ
|
||||||
|
vpython:S1163"ZRename this field "SystemModelSetting" to match the regular expression ^[_a-z][_a-z0-9]*$.(òªÆÓýÿÿÿÿ
|
||||||
|
bpython:S1164"KRename this field "Job" to match the regular expression ^[_a-z][_a-z0-9]*$.(„è´’
|
||||||
|
rpython:S116Ù"URename this field "PluginManager" to match the regular expression ^[_a-z][_a-z0-9]*$.(æ¥Ë÷ùÿÿÿÿ
|
||||||
|
6python:S125/"Remove this commented out code.(´ÔÔí
|
||||||
|
6python:S125_"Remove this commented out code.(„ÿ’È
|
||||||
|
6python:S125"Remove this commented out code.(æ‰Èì
|
||||||
|
[python:S112‰">Replace this generic exception class with a more specific one.(¢…›°þÿÿÿÿ
|
||||||
|
ypython: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_]*$.(çÉÍ¥úÿÿÿÿ
|
||||||
|
rpython:S3776÷"TRefactor this function to reduce its Cognitive Complexity from 26 to the 15 allowed.(<28><>›†øÿÿÿÿ
|
||||||
|
7python:S125ü"Remove this commented out code.(‚ðû<C3B0>
|
||||||
|
Apython:S108–")Either remove or fill this block of code.(ã‘Û¾
|
||||||
|
Upython:S5754 "<Specify an exception class to catch or reraise the exception(ˆÊÉ·
|
||||||
|
<python:S125¼"Remove this commented out code.(ÛÑ“¹üÿÿÿÿ
|
||||||
|
rpython:S3776Ç"TRefactor this function to reduce its Cognitive Complexity from 21 to the 15 allowed.(û´´¨ýÿÿÿÿ
|
||||||
|
<python:S125Ê"Remove this commented out code.(ˆà¡×ÿÿÿÿÿ
|
||||||
|
Upython:S5754þ"<Specify an exception class to catch or reraise the exception(ˆÊÉ·
|
||||||
|
tpython:S117²"\Rename this local variable "fileHandler" to match the regular expression ^[_a-z][a-z0-9_]*$.(ºŽ™…
|
||||||
|
vpython:S117¶"^Rename this local variable "streamHandler" to match the regular expression ^[_a-z][a-z0-9_]*$.(<28>½ÝÀ
|
||||||
|
mpython:S3776<18>"TRefactor this function to reduce its Cognitive Complexity from 24 to the 15 allowed.(¸ì¶Ê
|
||||||
|
Upython:S5754¢"<Specify an exception class to catch or reraise the exception(ˆÊÉ·
|
||||||
|
Upython:S5754–"<Specify an exception class to catch or reraise the exception(ˆÊÉ·
|
||||||
|
npython:S1542¥"PRename function "customTime" to match the regular expression ^[a-z_][a-z0-9_]*$.(ŠˆíÖúÿÿÿÿ
|
||||||
|
Upython:S5754À"<Specify an exception class to catch or reraise the exception(–’Ê<E28099>
|
||||||
|
Upython:S5754Â"<Specify an exception class to catch or reraise the exception(–’Ê<E28099>
|
||||||
|
Upython:S5754Ä"<Specify an exception class to catch or reraise the exception(–’Ê<E28099>
|
||||||
|
Upython:S5754Æ"<Specify an exception class to catch or reraise the exception(–’Ê<E28099>
|
||||||
|
Upython:S5754È"<Specify an exception class to catch or reraise the exception(–’Ê<E28099>
|
||||||
|
Upython:S5754Ï"<Specify an exception class to catch or reraise the exception(ˆÊÉ·
|
||||||
|
Upython:S5754<18>"<Specify an exception class to catch or reraise the exception(ˆÊÉ·
|
||||||
17
.idea/sonarlint/issuestore/index.pb
generated
Normal file
17
.idea/sonarlint/issuestore/index.pb
generated
Normal 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
13
.idea/vcs.xml
generated
Normal 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>
|
||||||
89
Dockerfile
Normal file
89
Dockerfile
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# FlaskFarm Docker Image v3.16
|
||||||
|
# Ubuntu/Debian + Python 3.14 for maximum performance
|
||||||
|
# Python 3.14.2 stable release
|
||||||
|
|
||||||
|
FROM python:3.14-slim
|
||||||
|
|
||||||
|
LABEL maintainer="yommi"
|
||||||
|
LABEL description="FlaskFarm with sc module support"
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
# Install system dependencies and Korean locales
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
ffmpeg \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
gcc \
|
||||||
|
python3-dev \
|
||||||
|
wget \
|
||||||
|
gnupg \
|
||||||
|
libxml2-dev \
|
||||||
|
libxslt1-dev \
|
||||||
|
zlib1g-dev \
|
||||||
|
libjpeg-dev \
|
||||||
|
libnss3 \
|
||||||
|
libatk-bridge2.0-0 \
|
||||||
|
libxcomposite1 \
|
||||||
|
libxdamage1 \
|
||||||
|
libxrandr2 \
|
||||||
|
libgbm1 \
|
||||||
|
libasound2 \
|
||||||
|
libasound2-dev \
|
||||||
|
libpangocairo-1.0-0 \
|
||||||
|
libgtk-3-0 \
|
||||||
|
pkg-config \
|
||||||
|
libbz2-dev \
|
||||||
|
libreadline-dev \
|
||||||
|
libffi-dev \
|
||||||
|
libssl-dev \
|
||||||
|
build-essential \
|
||||||
|
locales \
|
||||||
|
&& sed -i -e 's/# ko_KR.UTF-8 UTF-8/ko_KR.UTF-8 UTF-8/' /etc/locale.gen \
|
||||||
|
&& locale-gen \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ENV LC_ALL=ko_KR.UTF-8 \
|
||||||
|
LANG=ko_KR.UTF-8 \
|
||||||
|
LANGUAGE=ko_KR.UTF-8
|
||||||
|
|
||||||
|
# Install Google Chrome Stable (amd64) or Chromium (arm64)
|
||||||
|
ARG TARGETARCH
|
||||||
|
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||||
|
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-chrome.gpg && \
|
||||||
|
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list && \
|
||||||
|
apt-get update && apt-get install -y google-chrome-stable; \
|
||||||
|
else \
|
||||||
|
apt-get update && apt-get install -y chromium chromium-driver; \
|
||||||
|
fi && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set working directory to /root
|
||||||
|
WORKDIR /root
|
||||||
|
|
||||||
|
# Copy requirements first for layer caching
|
||||||
|
COPY ff_3_14_requirements.txt .
|
||||||
|
|
||||||
|
# Install Python dependencies (including camoufox/zendriver)
|
||||||
|
RUN grep -v "FlaskFarm" ff_3_14_requirements.txt > requirements_docker.txt \
|
||||||
|
&& pip install --no-cache-dir -r requirements_docker.txt
|
||||||
|
|
||||||
|
# Copy FlaskFarm application
|
||||||
|
COPY . .
|
||||||
|
RUN mkdir -p /data/plugins /data/db
|
||||||
|
COPY gommi.sh /root/gommi.sh
|
||||||
|
COPY config.yaml /data/config.yaml
|
||||||
|
RUN chmod +x /root/gommi.sh
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV TZ=Asia/Seoul
|
||||||
|
|
||||||
|
# Health check (Matching EXPOSE port 9999)
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||||
|
CMD curl -f http://localhost:9999/ || exit 1
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 9999/tcp
|
||||||
|
|
||||||
|
# Run FlaskFarm via gommi.sh in /root
|
||||||
|
ENTRYPOINT ["/root/gommi.sh"]
|
||||||
44
Dockerfile.3.10.bak
Normal file
44
Dockerfile.3.10.bak
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# FlaskFarm Docker Image
|
||||||
|
# Ubuntu 22.04 + Python 3.10 for sc module support on ARM64/x86_64 Linux
|
||||||
|
|
||||||
|
FROM python:3.10-slim-bullseye
|
||||||
|
|
||||||
|
LABEL maintainer="yommi"
|
||||||
|
LABEL description="FlaskFarm with sc module support"
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
ffmpeg \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
gcc \
|
||||||
|
python3-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy requirements first for layer caching
|
||||||
|
COPY ff_3_10_requirements.txt .
|
||||||
|
|
||||||
|
# Install Python dependencies (skip FlaskFarm package - running from source)
|
||||||
|
RUN grep -v "FlaskFarm" ff_3_10_requirements.txt > requirements_docker.txt \
|
||||||
|
&& pip install --no-cache-dir -r requirements_docker.txt \
|
||||||
|
&& pip install --no-cache-dir curl_cffi yt-dlp loguru
|
||||||
|
|
||||||
|
# Copy FlaskFarm application
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 9099
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV TZ=Asia/Seoul
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||||
|
CMD curl -f http://localhost:9099/ || exit 1
|
||||||
|
|
||||||
|
# Run FlaskFarm
|
||||||
|
CMD ["python", "main.py"]
|
||||||
74
all_files.txt
Normal file
74
all_files.txt
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
너 따위가 마왕을 이길 수 있다고 생각하지 마 라며 용사 파티에서 추방되었으니 왕도에서 멋대로 살고 싶다 (2026)
|
||||||
|
내가 연인이 될 수 있을 리 없잖아, 무리무리! (※무리가 아니었다 !) 속편 (2026)
|
||||||
|
소꿉친구와는 러브 코미디를 할 수 없어 (2026)
|
||||||
|
어느 날 공주가 되어 버렸다 - 중국어 (2025)
|
||||||
|
타몬 군 지금 어느 쪽 ! (2026)
|
||||||
|
치토세 군은 라무네 병 속에 (2025)
|
||||||
|
친구의 여동생이 나한테만 짜증나게 군다 (2025)
|
||||||
|
토지마 탄자부로는 가면라이더가 되고 싶어 (2025)
|
||||||
|
이세계 사정은 사축 하기 나름 (2026)
|
||||||
|
무사태평 영주의 즐거운 영지 방어 ~생산계 마법으로 이름 없는 마을을 최강의 성채 도시로~ (2026)
|
||||||
|
용사 파티에 귀여운 애가 있어서, 고백해봤다. (2026)
|
||||||
|
내가 너무 귀여운 걸 어쩌겠어! (2025)
|
||||||
|
더 파이팅 뉴 챌린저 (2009)
|
||||||
|
볼룸에 오신 것을 환영합니다 (2017)
|
||||||
|
아르마는 가족이 되고 싶어 (2025)
|
||||||
|
미소가 끊이지 않는 직장입니다 (2025)
|
||||||
|
악식 영애와 광혈 공작 (2025)
|
||||||
|
온화한 귀족의 휴가의 권장 (2026)
|
||||||
|
용사 파티에서 쫓겨난 다재무능 (2026)
|
||||||
|
밤은 고양이와 함께 시즌 3 (2024)
|
||||||
|
미남 고교 지구방위부 하이칼라! (2025)
|
||||||
|
마왕의 딸은 너무 친절해!! (2026)
|
||||||
|
사망 유희로 밥을 먹는다. (2026)
|
||||||
|
정반대의 너와 나 (2026)
|
||||||
|
마술사 쿠논은 보인다 (2026)
|
||||||
|
고문 아르바이트의 일상 (2026)
|
||||||
|
카야는 무섭지 않아 (2026)
|
||||||
|
페이트 스트레인지 페이크 (2026)
|
||||||
|
전생했더니 드래곤의 알이었다 ~최강이 아니면 목표로 하지 않아~ (2026)
|
||||||
|
공주님 “고문“의 시간입니다 2기 (2026)
|
||||||
|
나를 먹고 싶은, 괴물 (2025)
|
||||||
|
비밀의 아이프리 (2024)
|
||||||
|
마루는 강쥐 (2025)
|
||||||
|
울트라맨 오메가 (2025)
|
||||||
|
트라이건 스타게이즈 (2026)
|
||||||
|
아름다운 그대에게 (2026)
|
||||||
|
아름다운 초저녁달 (2026)
|
||||||
|
용사형에 처함 (2026)
|
||||||
|
데드 어카운트 (2026)
|
||||||
|
에리스의 성배 (2026)
|
||||||
|
한밤중 하트튠 (2026)
|
||||||
|
헬 모드 ~파고들기 좋아하는 게이머는 폐급 설정 이세계에서 무쌍한다~ (2026)
|
||||||
|
귀족 전생 ~축복받은 태생으로 최강의 힘을 손에 넣다~ (2026)
|
||||||
|
투명남과 인간녀 ~곧 부부가 될 두 사람~ (2026)
|
||||||
|
라디앙 시즌 2 (2019)
|
||||||
|
마도정병의 슬레이브 2 (2026)
|
||||||
|
【최애의 아이】 3기 (2026)
|
||||||
|
불꽃 소방대 3기 part 2 (2026)
|
||||||
|
무한의 주인 IMMORTAL (2019)
|
||||||
|
푸른 오케스트라 Season 2 (2025)
|
||||||
|
불멸의 그대에게 Season 3 (2025)
|
||||||
|
와타리 군의 XX가 붕괴 직전 (2025)
|
||||||
|
비질랜티 -나의 히어로 아카데미아 ILLEGALS- Season 2 (2026)
|
||||||
|
아빠는 영웅, 엄마는 정령, 딸인 나는 전생자. (2025)
|
||||||
|
그노시아 (2025)
|
||||||
|
가치아쿠타 (2025)
|
||||||
|
위국일기 (2026)
|
||||||
|
주술회전 3기 사멸회유 전편 (2025)
|
||||||
|
주술회전 3기 사멸회유 전편 (2026)
|
||||||
|
원펀맨 3기 (2025)
|
||||||
|
용족 Ⅱ-The Mourner's Eyes- - 중국어 (2025)
|
||||||
|
용족 Ⅱ-The Mourner's Eyes- - 일본어 (2025)
|
||||||
|
어차피, 사랑하고 만다. 2기 (2026)
|
||||||
|
안녕! 틴틴팅클 (2025)
|
||||||
|
(자막) 달의 요정 세일러 문 (2021)
|
||||||
|
(더빙) 카쿠리요의 여관밥 (2018)
|
||||||
|
(더빙) 디지몬 비트브레이크 (2025)
|
||||||
|
(자막) 장송의 프리렌 2기 (2026)
|
||||||
|
(자막) 원피스 33기 (2024)
|
||||||
|
(더빙) 하나Doll (2025)
|
||||||
|
DARK MOON 달의 제단 (2026)
|
||||||
|
SI-VIS The Sound of Heroes (2025)
|
||||||
|
WORKING!!! (3기) (2015)
|
||||||
|
WWW.WORKING!! (2016)
|
||||||
24
check_status.py
Normal file
24
check_status.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# FF 경로 설정
|
||||||
|
sys.path.append('/Volumes/WD/Users/Work/python/flaskfarm')
|
||||||
|
from framework import F
|
||||||
|
from gds_dviewer.logic import LogicExplorer
|
||||||
|
|
||||||
|
def check():
|
||||||
|
try:
|
||||||
|
logic = LogicExplorer(None) # 인스턴스 생성 (싱글톤 패턴일 경우 기존 인스턴스 접근 필요할 수도)
|
||||||
|
# 실제로는 LogicExplorer.instance 같은 게 있는지 확인 필요
|
||||||
|
# 하지만 gds_dviewer는 보통 P.logic에 저장됨.
|
||||||
|
|
||||||
|
# P instance 가져오기
|
||||||
|
from gds_dviewer.plugin import P
|
||||||
|
indexer = P.logic.explorer.indexer
|
||||||
|
print(f"Is Running: {indexer.is_running}")
|
||||||
|
print(f"Progress: {indexer.progress}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
check()
|
||||||
52
cleanup_duplicates.py
Normal file
52
cleanup_duplicates.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import unicodedata
|
||||||
|
sys.path.append('/Volumes/WD/Users/Work/python/flaskfarm')
|
||||||
|
from framework import app, db
|
||||||
|
from system.logic import SystemLogic
|
||||||
|
|
||||||
|
# 플러그인 모듈 로드
|
||||||
|
from data.plugins.gds_dviewer.model_file_index import FileIndex
|
||||||
|
|
||||||
|
def cleanup_duplicates(parent_path):
|
||||||
|
with app.app_context():
|
||||||
|
# 해당 폴더의 모든 항목 조회
|
||||||
|
items = FileIndex.query.filter_by(parent_path=parent_path).all()
|
||||||
|
print(f"Total items in {parent_path}: {len(items)}")
|
||||||
|
|
||||||
|
# NFC 이름 기준으로 그룹화
|
||||||
|
groups = {}
|
||||||
|
for item in items:
|
||||||
|
nfc_name = unicodedata.normalize('NFC', item.name)
|
||||||
|
if nfc_name not in groups:
|
||||||
|
groups[nfc_name] = []
|
||||||
|
groups[nfc_name].append(item)
|
||||||
|
|
||||||
|
deleted_count = 0
|
||||||
|
for name, group in groups.items():
|
||||||
|
if len(group) > 1:
|
||||||
|
print(f"Found duplicate: {name} (Count: {len(group)})")
|
||||||
|
|
||||||
|
# 우선순위: 메타데이터 있는 것 > ID가 작은 것(오래된 것)
|
||||||
|
# 정렬: 메타데이터 있나? (내림차순 True=1, False=0), ID (오름차순)
|
||||||
|
group.sort(key=lambda x: (1 if x.meta_id else 0, -x.id), reverse=True)
|
||||||
|
|
||||||
|
# 첫 번째(가장 좋은 것)를 남기고 나머지 삭제
|
||||||
|
keep = group[0]
|
||||||
|
remove_list = group[1:]
|
||||||
|
|
||||||
|
print(f" Keep: ID={keep.id}, Meta={keep.meta_id}, Name={keep.name}")
|
||||||
|
for rm in remove_list:
|
||||||
|
print(f" REMOVE: ID={rm.id}, Meta={rm.meta_id}, Name={rm.name}")
|
||||||
|
db.session.delete(rm)
|
||||||
|
deleted_count += 1
|
||||||
|
|
||||||
|
if deleted_count > 0:
|
||||||
|
db.session.commit()
|
||||||
|
print(f"Deleted {deleted_count} duplicate items.")
|
||||||
|
else:
|
||||||
|
print("No duplicates found to delete.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cleanup_duplicates('VIDEO/방송중/라프텔 애니메이션')
|
||||||
43
cli/encode.py
Normal file
43
cli/encode.py
Normal 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()
|
||||||
51
config.yaml
Normal file
51
config.yaml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
path_data: '/data'
|
||||||
|
##########################################################################
|
||||||
|
# 데이터 폴더 루트 경로
|
||||||
|
# 윈도우의 경우 폴더 구분 기호 \ 를 두개 사용
|
||||||
|
# 예) data_folder: "C:\\work\\data"
|
||||||
|
# 현재 폴더인 경우 .
|
||||||
|
#path_data: "."
|
||||||
|
|
||||||
|
# 개발용 플러그인 경로
|
||||||
|
path_dev: '/Volumes/WD/Users/Work/python/ff_dev_plugins'
|
||||||
|
|
||||||
|
# gevent 사용여부
|
||||||
|
# 플러그인 개발이나 termux 환경에서의 실행 같이 특수한 경우에만 false로 사용.
|
||||||
|
# 실행환경에 gevent 관련 패키지가 설치되어 있지 않는다면 값과 상관 없이 false로 동작.
|
||||||
|
use_gevent: true
|
||||||
|
|
||||||
|
# celery 사용 여부
|
||||||
|
use_celery: true
|
||||||
|
|
||||||
|
# redis port
|
||||||
|
# celery를 사용하는 경우 사용하는 redis 포트
|
||||||
|
# 환경변수 REDIS_PORT 값이 있는 경우 무시됨.
|
||||||
|
#redis_port: 6379
|
||||||
|
|
||||||
|
# 포트
|
||||||
|
# 생략시 DB 값을 사용.
|
||||||
|
port: 9999
|
||||||
|
|
||||||
|
# 소스 수정시 재로딩
|
||||||
|
# 두번 로딩되는 것을 감안하여 코딩해야 함.
|
||||||
|
# debug: true
|
||||||
|
debug: false
|
||||||
|
|
||||||
|
# 플러그인 업데이트 여부
|
||||||
|
# - true인 경우 로딩시 플러그인을 업데이트 함.
|
||||||
|
# /data/plugins 폴더에 있는 플러그인 만을 대상으로 함.
|
||||||
|
# - debug 값이 true인 경우에는 항상 false
|
||||||
|
plugin_update: false
|
||||||
|
|
||||||
|
# running_type
|
||||||
|
# termux, entware 인 경우 입력 함.
|
||||||
|
running_type: 'docker'
|
||||||
|
# 개발용 폴더만 로딩할 경우 사용
|
||||||
|
# plugin_loading_only_devpath: true
|
||||||
|
# 로딩할 플러그인 package 명
|
||||||
|
# 타 플러그인과 연동되는 플러그인 개발시 사용.
|
||||||
|
# import 로 런타임에 로딩할 수 있지만 타 패키지 메뉴 등은 표시되지 않음.
|
||||||
|
#plugin_loading_list: ['command', 'flaskcode']
|
||||||
|
|
||||||
|
# 로딩 제외할 플러그인 package 명
|
||||||
|
plugin_except_list: ['.idea', '.git', '.vscode', '.nova', '.mypy_cache']
|
||||||
51
config_mac.yaml
Normal file
51
config_mac.yaml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
path_data: 'data'
|
||||||
|
##########################################################################
|
||||||
|
# 데이터 폴더 루트 경로
|
||||||
|
# 윈도우의 경우 폴더 구분 기호 \ 를 두개 사용
|
||||||
|
# 예) data_folder: "C:\\work\\data"
|
||||||
|
# 현재 폴더인 경우 .
|
||||||
|
#path_data: "."
|
||||||
|
|
||||||
|
# 개발용 플러그인 경로
|
||||||
|
path_dev: '/Volumes/WD/Users/Work/python/ff_dev_plugins/anime_downloader'
|
||||||
|
|
||||||
|
# gevent 사용여부
|
||||||
|
# 플러그인 개발이나 termux 환경에서의 실행 같이 특수한 경우에만 false로 사용.
|
||||||
|
# 실행환경에 gevent 관련 패키지가 설치되어 있지 않는다면 값과 상관 없이 false로 동작.
|
||||||
|
use_gevent: true
|
||||||
|
|
||||||
|
# celery 사용 여부
|
||||||
|
use_celery: true
|
||||||
|
|
||||||
|
# redis port
|
||||||
|
# celery를 사용하는 경우 사용하는 redis 포트
|
||||||
|
# 환경변수 REDIS_PORT 값이 있는 경우 무시됨.
|
||||||
|
#redis_port: 6379
|
||||||
|
|
||||||
|
# 포트
|
||||||
|
# 생략시 DB 값을 사용.
|
||||||
|
port: 9099
|
||||||
|
|
||||||
|
# 소스 수정시 재로딩
|
||||||
|
# 두번 로딩되는 것을 감안하여 코딩해야 함.
|
||||||
|
# debug: true
|
||||||
|
debug: true
|
||||||
|
|
||||||
|
# 플러그인 업데이트 여부
|
||||||
|
# - true인 경우 로딩시 플러그인을 업데이트 함.
|
||||||
|
# /data/plugins 폴더에 있는 플러그인 만을 대상으로 함.
|
||||||
|
# - debug 값이 true인 경우에는 항상 false
|
||||||
|
plugin_update: false
|
||||||
|
|
||||||
|
# running_type
|
||||||
|
# termux, entware 인 경우 입력 함.
|
||||||
|
running_type: 'native'
|
||||||
|
# 개발용 폴더만 로딩할 경우 사용
|
||||||
|
# plugin_loading_only_devpath: true
|
||||||
|
# 로딩할 플러그인 package 명
|
||||||
|
# 타 플러그인과 연동되는 플러그인 개발시 사용.
|
||||||
|
# import 로 런타임에 로딩할 수 있지만 타 패키지 메뉴 등은 표시되지 않음.
|
||||||
|
#plugin_loading_list: ['command', 'flaskcode']
|
||||||
|
|
||||||
|
# 로딩 제외할 플러그인 package 명
|
||||||
|
plugin_except_list: ['.idea', '.git', '.vscode', '.nova', '.mypy_cache']
|
||||||
23
docker-compose.yml
Normal file
23
docker-compose.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# FlaskFarm Docker Compose
|
||||||
|
# Usage:
|
||||||
|
# docker compose up -d # 시작
|
||||||
|
# docker compose down # 중지
|
||||||
|
# docker compose logs -f # 로그 보기
|
||||||
|
|
||||||
|
services:
|
||||||
|
flaskfarm:
|
||||||
|
build: .
|
||||||
|
container_name: flaskfarm
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "9099:9099"
|
||||||
|
volumes:
|
||||||
|
# FlaskFarm data 폴더 (DB, 설정, 다운로드 등)
|
||||||
|
- ./data:/data
|
||||||
|
# 플러그인 폴더 (외부 마운트)
|
||||||
|
- ../ff_dev_plugins:/data/plugins
|
||||||
|
environment:
|
||||||
|
- TZ=Asia/Seoul
|
||||||
|
- PYTHONUNBUFFERED=1
|
||||||
|
# M1/M2 Mac에서 ARM64 Linux 이미지 사용
|
||||||
|
platform: linux/arm64
|
||||||
90
ff_3_10_requirements.txt
Normal file
90
ff_3_10_requirements.txt
Normal 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
|
||||||
97
ff_3_14_requirements.txt
Normal file
97
ff_3_14_requirements.txt
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
aiohttp>=3.9.0
|
||||||
|
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.4.0
|
||||||
|
certifi>=2022.9.24
|
||||||
|
charset-normalizer==2.1.1
|
||||||
|
click>=8.1.7
|
||||||
|
click-didyoumean==0.3.0
|
||||||
|
click-plugins==1.1.1
|
||||||
|
click-repl==0.2.0
|
||||||
|
cloudscraper==1.2.64
|
||||||
|
Deprecated>=1.2.14
|
||||||
|
discord-webhook==0.17.0
|
||||||
|
EditorConfig==0.12.3
|
||||||
|
exceptiongroup==1.0.0rc9
|
||||||
|
Flask>=3.0.0
|
||||||
|
Flask-Cors>=3.0.10
|
||||||
|
Flask-Dropzone>=1.6.0
|
||||||
|
Flask-Login>=0.6.3
|
||||||
|
Flask-Markdown==0.3
|
||||||
|
Flask-SocketIO>=5.3.6
|
||||||
|
Flask-SQLAlchemy>=3.0.2
|
||||||
|
FlaskFarm==4.0.47
|
||||||
|
frozenlist>=1.4.1
|
||||||
|
gevent>=24.2.1
|
||||||
|
gevent-websocket==0.10.1
|
||||||
|
greenlet>=3.2.2
|
||||||
|
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.3.0
|
||||||
|
lxml>=4.9.4
|
||||||
|
Markdown==3.4.1
|
||||||
|
MarkupSafe>=2.1.4
|
||||||
|
multidict>=6.0.5
|
||||||
|
outcome==1.2.0
|
||||||
|
packaging==21.3
|
||||||
|
Pillow>=10.0.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.8.0
|
||||||
|
python-socketio>=5.10.0
|
||||||
|
pytz==2022.5
|
||||||
|
pytz-deprecation-shim==0.1.0.post0
|
||||||
|
PyYAML>=6.0.2
|
||||||
|
redis>=4.3.4
|
||||||
|
requests==2.28.1
|
||||||
|
requests-cache==0.9.6
|
||||||
|
requests-toolbelt==0.10.1
|
||||||
|
selenium>=4.20.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.1.0
|
||||||
|
wcwidth==0.2.5
|
||||||
|
webdriver-manager>=4.0.0
|
||||||
|
Werkzeug>=3.0.0
|
||||||
|
wrapt==1.14.1
|
||||||
|
wsproto==1.2.0
|
||||||
|
yarl>=1.9.4
|
||||||
|
zipp==3.10.0
|
||||||
|
zope.event>=5.0
|
||||||
|
zope.interface>=7.0
|
||||||
|
zendriver
|
||||||
|
camoufox
|
||||||
|
curl_cffi
|
||||||
|
yt-dlp
|
||||||
|
loguru
|
||||||
|
shazamio
|
||||||
|
shazamio-core
|
||||||
74
files.txt
Normal file
74
files.txt
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
너 따위가 마왕을 이길 수 있다고 생각하지 마 라며 용사 파티에서 추방되었으니 왕도에서 멋대로 살고 싶다 (2026)
|
||||||
|
내가 연인이 될 수 있을 리 없잖아, 무리무리! (※무리가 아니었다 !) 속편 (2026)
|
||||||
|
소꿉친구와는 러브 코미디를 할 수 없어 (2026)
|
||||||
|
어느 날 공주가 되어 버렸다 - 중국어 (2025)
|
||||||
|
타몬 군 지금 어느 쪽 ! (2026)
|
||||||
|
치토세 군은 라무네 병 속에 (2025)
|
||||||
|
친구의 여동생이 나한테만 짜증나게 군다 (2025)
|
||||||
|
토지마 탄자부로는 가면라이더가 되고 싶어 (2025)
|
||||||
|
이세계 사정은 사축 하기 나름 (2026)
|
||||||
|
무사태평 영주의 즐거운 영지 방어 ~생산계 마법으로 이름 없는 마을을 최강의 성채 도시로~ (2026)
|
||||||
|
용사 파티에 귀여운 애가 있어서, 고백해봤다. (2026)
|
||||||
|
내가 너무 귀여운 걸 어쩌겠어! (2025)
|
||||||
|
더 파이팅 뉴 챌린저 (2009)
|
||||||
|
볼룸에 오신 것을 환영합니다 (2017)
|
||||||
|
아르마는 가족이 되고 싶어 (2025)
|
||||||
|
미소가 끊이지 않는 직장입니다 (2025)
|
||||||
|
악식 영애와 광혈 공작 (2025)
|
||||||
|
온화한 귀족의 휴가의 권장 (2026)
|
||||||
|
용사 파티에서 쫓겨난 다재무능 (2026)
|
||||||
|
밤은 고양이와 함께 시즌 3 (2024)
|
||||||
|
미남 고교 지구방위부 하이칼라! (2025)
|
||||||
|
마왕의 딸은 너무 친절해!! (2026)
|
||||||
|
사망 유희로 밥을 먹는다. (2026)
|
||||||
|
정반대의 너와 나 (2026)
|
||||||
|
마술사 쿠논은 보인다 (2026)
|
||||||
|
고문 아르바이트의 일상 (2026)
|
||||||
|
카야는 무섭지 않아 (2026)
|
||||||
|
페이트 스트레인지 페이크 (2026)
|
||||||
|
전생했더니 드래곤의 알이었다 ~최강이 아니면 목표로 하지 않아~ (2026)
|
||||||
|
공주님 “고문“의 시간입니다 2기 (2026)
|
||||||
|
나를 먹고 싶은, 괴물 (2025)
|
||||||
|
비밀의 아이프리 (2024)
|
||||||
|
마루는 강쥐 (2025)
|
||||||
|
울트라맨 오메가 (2025)
|
||||||
|
트라이건 스타게이즈 (2026)
|
||||||
|
아름다운 그대에게 (2026)
|
||||||
|
아름다운 초저녁달 (2026)
|
||||||
|
용사형에 처함 (2026)
|
||||||
|
데드 어카운트 (2026)
|
||||||
|
에리스의 성배 (2026)
|
||||||
|
한밤중 하트튠 (2026)
|
||||||
|
헬 모드 ~파고들기 좋아하는 게이머는 폐급 설정 이세계에서 무쌍한다~ (2026)
|
||||||
|
귀족 전생 ~축복받은 태생으로 최강의 힘을 손에 넣다~ (2026)
|
||||||
|
투명남과 인간녀 ~곧 부부가 될 두 사람~ (2026)
|
||||||
|
라디앙 시즌 2 (2019)
|
||||||
|
마도정병의 슬레이브 2 (2026)
|
||||||
|
【최애의 아이】 3기 (2026)
|
||||||
|
불꽃 소방대 3기 part 2 (2026)
|
||||||
|
무한의 주인 IMMORTAL (2019)
|
||||||
|
푸른 오케스트라 Season 2 (2025)
|
||||||
|
불멸의 그대에게 Season 3 (2025)
|
||||||
|
와타리 군의 XX가 붕괴 직전 (2025)
|
||||||
|
비질랜티 -나의 히어로 아카데미아 ILLEGALS- Season 2 (2026)
|
||||||
|
아빠는 영웅, 엄마는 정령, 딸인 나는 전생자. (2025)
|
||||||
|
그노시아 (2025)
|
||||||
|
가치아쿠타 (2025)
|
||||||
|
위국일기 (2026)
|
||||||
|
주술회전 3기 사멸회유 전편 (2025)
|
||||||
|
주술회전 3기 사멸회유 전편 (2026)
|
||||||
|
원펀맨 3기 (2025)
|
||||||
|
용족 Ⅱ-The Mourner's Eyes- - 중국어 (2025)
|
||||||
|
용족 Ⅱ-The Mourner's Eyes- - 일본어 (2025)
|
||||||
|
어차피, 사랑하고 만다. 2기 (2026)
|
||||||
|
안녕! 틴틴팅클 (2025)
|
||||||
|
(자막) 달의 요정 세일러 문 (2021)
|
||||||
|
(더빙) 카쿠리요의 여관밥 (2018)
|
||||||
|
(더빙) 디지몬 비트브레이크 (2025)
|
||||||
|
(자막) 장송의 프리렌 2기 (2026)
|
||||||
|
(자막) 원피스 33기 (2024)
|
||||||
|
(더빙) 하나Doll (2025)
|
||||||
|
DARK MOON 달의 제단 (2026)
|
||||||
|
SI-VIS The Sound of Heroes (2025)
|
||||||
|
WORKING!!! (3기) (2015)
|
||||||
|
WWW.WORKING!! (2016)
|
||||||
@@ -1,18 +1,12 @@
|
|||||||
# 카테고리
|
# 카테고리
|
||||||
# uri 가 plugin인 경우 name 값은 대체
|
# uri 가 plugin인 경우 name 값은 대체
|
||||||
|
|
||||||
#- name: "토렌트"
|
|
||||||
# list:
|
|
||||||
# - uri: "rss"
|
|
||||||
|
|
||||||
|
|
||||||
#- name: "기본 기능"
|
#- name: "기본 기능"
|
||||||
# list:
|
# list:
|
||||||
# - uri: "terminal"
|
# - uri: "terminal"
|
||||||
# - uri: "command"
|
|
||||||
# - uri: "flaskfilemanager"
|
# - uri: "flaskfilemanager"
|
||||||
# - uri: "flaskcode"
|
# - uri: "flaskcode"
|
||||||
# - uri: "number_baseball"
|
|
||||||
|
|
||||||
|
|
||||||
#- name: "링크"
|
#- name: "링크"
|
||||||
@@ -32,6 +26,8 @@
|
|||||||
name: "확장 설정"
|
name: "확장 설정"
|
||||||
- uri: "system/plugin"
|
- uri: "system/plugin"
|
||||||
name: "플러그인 관리"
|
name: "플러그인 관리"
|
||||||
|
- uri: "system/tool/command"
|
||||||
|
name: "Command 관리"
|
||||||
- uri: "-"
|
- uri: "-"
|
||||||
- uri: "system/logout"
|
- uri: "system/logout"
|
||||||
name: "로그아웃"
|
name: "로그아웃"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# message id 가 없을 경우 DEFAULT 값 사용
|
# message id 가 없을 경우 DEFAULT 값 사용
|
||||||
# 공통: type, enable_time (시작시간-종료시간. 항상 받을 경우 생략)
|
# 공통: type, enable_time (시작시간-종료시간. 항상 받을 경우 생략)
|
||||||
|
|
||||||
- DEFAULT:
|
DEFAULT:
|
||||||
- type: 'telegram'
|
- type: 'telegram'
|
||||||
token: ''
|
token: ''
|
||||||
chat_id: ''
|
chat_id: ''
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
enable_time: '09-23'
|
enable_time: '09-23'
|
||||||
|
|
||||||
|
|
||||||
- system_start:
|
system_start:
|
||||||
- type: 'telegram'
|
- type: 'telegram'
|
||||||
token: ''
|
token: ''
|
||||||
chat_id: ''
|
chat_id: ''
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ Flask
|
|||||||
Flask-SQLAlchemy
|
Flask-SQLAlchemy
|
||||||
Flask-Login
|
Flask-Login
|
||||||
Flask-Cors
|
Flask-Cors
|
||||||
Flask-Markdown
|
#Flask-Markdown
|
||||||
Flask-SocketIO
|
Flask-SocketIO
|
||||||
python-engineio
|
python-engineio
|
||||||
python-socketio
|
python-socketio<5.8.0
|
||||||
Werkzeug
|
Werkzeug<3.0
|
||||||
Jinja2
|
Jinja2
|
||||||
|
|
||||||
# common util
|
# common util
|
||||||
@@ -25,4 +25,5 @@ pillow
|
|||||||
|
|
||||||
gevent
|
gevent
|
||||||
gevent-websocket
|
gevent-websocket
|
||||||
pycryptodome
|
pycryptodome
|
||||||
|
json_fix
|
||||||
|
|||||||
12
flower.sh
Executable file
12
flower.sh
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# GDS-DViewer Flower Dashboard Startup Script
|
||||||
|
# This script bypasses main.py to avoid gevent monkey-patch conflicts (infinite loading).
|
||||||
|
|
||||||
|
cd /Volumes/WD/Users/Work/python/flaskfarm
|
||||||
|
|
||||||
|
# Kill existing processes
|
||||||
|
pkill -f flower
|
||||||
|
|
||||||
|
# Execute integrated startup command
|
||||||
|
/Users/yommi/.pyenv/versions/FF_3.10/bin/python3 -c "import sys, os; sys.path.append(os.path.join(os.getcwd(), 'lib')); sys.argv=['celery', '--config_filepath=data/config_mac.yaml', '--running_type=local']; from framework import initiaize; celery=initiaize().celery; celery.start(['flower', '--address=0.0.0.0', '--port=5556'])"
|
||||||
60
gommi.sh
Executable file
60
gommi.sh
Executable file
@@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Python 3.14 + gevent fork 경고 억제
|
||||||
|
export GEVENT_NOWAITPID=1
|
||||||
|
export PYTHONWARNINGS="ignore::DeprecationWarning"
|
||||||
|
|
||||||
|
CONFIGFILE="/data/config.yaml"
|
||||||
|
COUNT=0
|
||||||
|
|
||||||
|
# 🌐 Camoufox 브라우저 캐시 경로 설정 (마운트된 data 폴더 사용으로 이미지 용량 절감)
|
||||||
|
export CAMOUFOX_CACHE_DIR="/data/.camoufox"
|
||||||
|
|
||||||
|
# 🔧 서버 시작 전에 플러그인 업데이트 및 브라우저 확인
|
||||||
|
update_plugins() {
|
||||||
|
# Camoufox 브라우저 체크 및 다운로드 (컨테이너 최초 실행 시 1회)
|
||||||
|
if [ ! -d "$CAMOUFOX_CACHE_DIR" ]; then
|
||||||
|
echo "Fetching Camoufox binaries to $CAMOUFOX_CACHE_DIR..."
|
||||||
|
camoufox fetch
|
||||||
|
fi
|
||||||
|
|
||||||
|
PLUGINS_DIR="/data/plugins"
|
||||||
|
if [ -d "$PLUGINS_DIR" ]; then
|
||||||
|
for dir in "$PLUGINS_DIR"/*/; do
|
||||||
|
if [ -d "$dir/.git" ]; then
|
||||||
|
echo "Updating plugin: $dir"
|
||||||
|
git -C "$dir" reset --hard HEAD 2>/dev/null
|
||||||
|
git -C "$dir" pull 2>/dev/null & # 병렬 실행
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
wait # 모든 git pull 완료 대기
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
# 첫 실행 시 또는 --update 옵션일 때만
|
||||||
|
if [ "$COUNT" = "0" ]; then
|
||||||
|
update_plugins
|
||||||
|
fi
|
||||||
|
|
||||||
|
while true;
|
||||||
|
do
|
||||||
|
echo "------------------------------------------------"
|
||||||
|
echo "Starting FlaskFarm Python Process (COUNT: ${COUNT})"
|
||||||
|
echo "Config: ${CONFIGFILE}"
|
||||||
|
echo "------------------------------------------------"
|
||||||
|
|
||||||
|
python main.py --repeat ${COUNT} --config ${CONFIGFILE}
|
||||||
|
RESULT=$?
|
||||||
|
|
||||||
|
echo "------------------------------------------------"
|
||||||
|
echo "PYTHON EXIT CODE : ${RESULT}"
|
||||||
|
echo "------------------------------------------------"
|
||||||
|
|
||||||
|
if [ "$RESULT" = "1" ]; then
|
||||||
|
echo 'Restarting... (RESULT=1)'
|
||||||
|
update_plugins
|
||||||
|
else
|
||||||
|
echo "Exiting... (RESULT=${RESULT})"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
COUNT=`expr $COUNT + 1`
|
||||||
|
done
|
||||||
101
gommi_mac.sh
Executable file
101
gommi_mac.sh
Executable file
@@ -0,0 +1,101 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# pyenv 초기화 (Warp/iTerm 등 비-인터랙티브 셸에서도 작동하도록)
|
||||||
|
export PYENV_ROOT="$HOME/.pyenv"
|
||||||
|
export PATH="$PYENV_ROOT/bin:$PATH"
|
||||||
|
if command -v pyenv &> /dev/null; then
|
||||||
|
eval "$(pyenv init -)"
|
||||||
|
eval "$(pyenv virtualenv-init -)" 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Python 3.14 + gevent fork 경고 억제
|
||||||
|
export GEVENT_NOWAITPID=1
|
||||||
|
export PYTHONWARNINGS="ignore::DeprecationWarning"
|
||||||
|
|
||||||
|
# Ctrl+C (SIGINT) 한 번에 종료되도록 설정
|
||||||
|
cleanup() {
|
||||||
|
echo ""
|
||||||
|
echo "Stopping FlaskFarm..."
|
||||||
|
# Python 프로세스 종료
|
||||||
|
if [ -n "$PYTHON_PID" ]; then
|
||||||
|
kill -TERM $PYTHON_PID 2>/dev/null
|
||||||
|
sleep 0.5
|
||||||
|
kill -9 $PYTHON_PID 2>/dev/null
|
||||||
|
fi
|
||||||
|
# 모든 자식 프로세스 종료
|
||||||
|
pkill -P $$ 2>/dev/null
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
trap cleanup SIGINT SIGTERM
|
||||||
|
|
||||||
|
CONFIGFILE="data/config_mac.yaml"
|
||||||
|
COUNT=0
|
||||||
|
|
||||||
|
# 🌐 Camoufox 브라우저 캐시 경로 설정 및 유지
|
||||||
|
# macOS에서는 /Users/yommi/Library/Caches/camoufox가 기본값이나, 프로젝트 data 폴더로 강제 리다이렉션
|
||||||
|
CAMOUFOX_DEFAULT_DIR="$HOME/Library/Caches/camoufox"
|
||||||
|
CAMOUFOX_PERSISTENT_DIR="$(pwd)/data/.camoufox"
|
||||||
|
|
||||||
|
# 심볼릭 링크를 통해 data 폴더와 동기화
|
||||||
|
if [ ! -L "$CAMOUFOX_DEFAULT_DIR" ]; then
|
||||||
|
echo "Configuring Camoufox persistence link..."
|
||||||
|
mkdir -p "$CAMOUFOX_PERSISTENT_DIR"
|
||||||
|
if [ -d "$CAMOUFOX_DEFAULT_DIR" ] && [ ! -L "$CAMOUFOX_DEFAULT_DIR" ]; then
|
||||||
|
cp -R "$CAMOUFOX_DEFAULT_DIR/" "$CAMOUFOX_PERSISTENT_DIR/" 2>/dev/null
|
||||||
|
rm -rf "$CAMOUFOX_DEFAULT_DIR"
|
||||||
|
fi
|
||||||
|
mkdir -p "$(dirname "$CAMOUFOX_DEFAULT_DIR")"
|
||||||
|
ln -s "$CAMOUFOX_PERSISTENT_DIR" "$CAMOUFOX_DEFAULT_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 🔧 서버 시작 전에 플러그인 업데이트 및 브라우저 확인
|
||||||
|
update_plugins() {
|
||||||
|
# Camoufox 브라우저 체크 (실제 설치된 폴더 확인)
|
||||||
|
if [ ! -d "$CAMOUFOX_DEFAULT_DIR/Camoufox.app" ]; then
|
||||||
|
echo "Fetching Camoufox binaries to $CAMOUFOX_PERSISTENT_DIR..."
|
||||||
|
camoufox fetch
|
||||||
|
fi
|
||||||
|
|
||||||
|
PLUGINS_DIR="data/plugins"
|
||||||
|
if [ -d "$PLUGINS_DIR" ]; then
|
||||||
|
for dir in "$PLUGINS_DIR"/*/; do
|
||||||
|
if [ -d "$dir/.git" ]; then
|
||||||
|
echo "Updating plugin: $dir"
|
||||||
|
git -C "$dir" reset --hard HEAD 2>/dev/null
|
||||||
|
git -C "$dir" pull 2>/dev/null & # 병렬 실행
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
wait # 모든 git pull 완료 대기
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 첫 실행 시 또는 --update 옵션일 때만
|
||||||
|
if [ "$COUNT" = "0" ]; then
|
||||||
|
update_plugins
|
||||||
|
fi
|
||||||
|
|
||||||
|
while true;
|
||||||
|
do
|
||||||
|
echo "------------------------------------------------"
|
||||||
|
echo "Starting FlaskFarm Python Process (COUNT: ${COUNT})"
|
||||||
|
echo "Config: ${CONFIGFILE}"
|
||||||
|
echo "------------------------------------------------"
|
||||||
|
|
||||||
|
python main.py --repeat ${COUNT} --config ${CONFIGFILE} &
|
||||||
|
PYTHON_PID=$!
|
||||||
|
wait $PYTHON_PID
|
||||||
|
RESULT=$?
|
||||||
|
|
||||||
|
echo "------------------------------------------------"
|
||||||
|
echo "PYTHON EXIT CODE : ${RESULT}"
|
||||||
|
echo "------------------------------------------------"
|
||||||
|
|
||||||
|
if [ "$RESULT" = "1" ]; then
|
||||||
|
echo 'Restarting... (RESULT=1)'
|
||||||
|
update_plugins
|
||||||
|
else
|
||||||
|
echo "Exiting... (RESULT=${RESULT})"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
COUNT=$(expr $COUNT + 1)
|
||||||
|
done
|
||||||
32
gunicorn_config.py
Normal file
32
gunicorn_config.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Gunicorn 최적화 설정 (Bottleproof v2)
|
||||||
|
import multiprocessing
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 1. 네트워크 설정
|
||||||
|
bind = "0.0.0.0:9099"
|
||||||
|
backlog = 2048
|
||||||
|
|
||||||
|
# 2. 프로세스 관리
|
||||||
|
# GeventWebSocketWorker: SocketIO(WebSocket) 지원을 위해 필수
|
||||||
|
worker_class = "geventwebsocket.gunicorn.workers.GeventWebSocketWorker"
|
||||||
|
workers = 1 # SocketIO 세션 일관성을 위해 1개 권장 (Redis message queue 미사용 시)
|
||||||
|
worker_connections = 1000
|
||||||
|
timeout = 600
|
||||||
|
keepalive = 5
|
||||||
|
|
||||||
|
# 3. PID 관리 (정밀한 프로세스 제어를 위해 추가)
|
||||||
|
pidfile = "data/gunicorn.pid"
|
||||||
|
|
||||||
|
# 4. 로깅 설정
|
||||||
|
# data/log 폴더 자동 생성 보장
|
||||||
|
log_dir = "data/log"
|
||||||
|
if not os.path.exists(log_dir):
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
|
||||||
|
accesslog = os.path.join(log_dir, "gunicorn_access.log")
|
||||||
|
errorlog = os.path.join(log_dir, "gunicorn_error.log")
|
||||||
|
loglevel = "info"
|
||||||
|
capture_output = True
|
||||||
|
|
||||||
|
# 5. 개발 편의성
|
||||||
|
reload = False # 운영 환경에서는 False
|
||||||
BIN
lib/.DS_Store
vendored
Normal file
BIN
lib/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
lib/framework/.DS_Store
vendored
Normal file
BIN
lib/framework/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -1,25 +1,70 @@
|
|||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except:
|
||||||
|
import os
|
||||||
|
try:
|
||||||
|
os.system("pip install pyyaml")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
from .init_main import Framework
|
from .init_main import Framework
|
||||||
from .version import VERSION
|
from .version import VERSION
|
||||||
|
|
||||||
frame = Framework.get_instance()
|
# 2024.06.13
|
||||||
F = frame
|
# 잘못된 설계로 인해 import 만으로 초기화 되버려 lib을 사용할 수 없다.
|
||||||
logger = frame.logger
|
# 분리.
|
||||||
app = frame.app
|
|
||||||
celery = frame.celery
|
F = None
|
||||||
db = frame.db
|
frame = None
|
||||||
scheduler = frame.scheduler
|
logger = None
|
||||||
socketio = frame.socketio
|
app = None
|
||||||
path_app_root = frame.path_app_root
|
celery = None
|
||||||
path_data = frame.path_data
|
db = None
|
||||||
get_logger = frame.get_logger
|
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 flask_login import login_required
|
||||||
from support import d
|
from support import d
|
||||||
|
|
||||||
from .init_declare import User, check_api
|
from .init_declare import User, check_api
|
||||||
from .scheduler import Job
|
from .scheduler import Job
|
||||||
|
|
||||||
frame.initialize_system()
|
|
||||||
from system.setup import SystemModelSetting
|
|
||||||
|
|
||||||
frame.initialize_plugin()
|
|
||||||
|
|
||||||
73
lib/framework/init_cache_manager.py
Normal file
73
lib/framework/init_cache_manager.py
Normal 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)
|
||||||
@@ -13,13 +13,13 @@ def check_api(original_function):
|
|||||||
#logger.warning(request.url)
|
#logger.warning(request.url)
|
||||||
#logger.warning(request.form)
|
#logger.warning(request.form)
|
||||||
try:
|
try:
|
||||||
if F.SystemModelSetting.get_bool('auth_use_apikey'):
|
if F.SystemModelSetting.get_bool('use_apikey'):
|
||||||
if request.method == 'POST':
|
try:
|
||||||
apikey = request.form['apikey']
|
d = request.get_json()
|
||||||
else:
|
except Exception:
|
||||||
apikey = request.args.get('apikey')
|
d = request.form.to_dict() if request.method == 'POST' else request.args.to_dict()
|
||||||
#apikey = request.args.get('apikey')
|
apikey = d.get('apikey')
|
||||||
if apikey is None or apikey != F.SystemModelSetting.get('auth_apikey'):
|
if apikey is None or apikey != F.SystemModelSetting.get('apikey'):
|
||||||
F.logger.warning('CHECK API : ABORT no match ({})'.format(apikey))
|
F.logger.warning('CHECK API : ABORT no match ({})'.format(apikey))
|
||||||
F.logger.warning(request.environ.get('HTTP_X_REAL_IP', request.remote_addr))
|
F.logger.warning(request.environ.get('HTTP_X_REAL_IP', request.remote_addr))
|
||||||
abort(403)
|
abort(403)
|
||||||
@@ -31,7 +31,7 @@ def check_api(original_function):
|
|||||||
return original_function(*args, **kwargs) #2
|
return original_function(*args, **kwargs) #2
|
||||||
return wrapper_function
|
return wrapper_function
|
||||||
|
|
||||||
# Suuport를 logger 생성전에 쓰지 않기 위해 중복 선언
|
# Support를 logger 생성전에 쓰지 않기 위해 중복 선언
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ class CustomFormatter(logging.Formatter):
|
|||||||
# pathname filename
|
# pathname filename
|
||||||
#format = "[%(asctime)s|%(name)s|%(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
|
#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 = {
|
FORMATS = {
|
||||||
logging.DEBUG: __format.format(color=grey, reset=reset, yellow=yellow, green=green),
|
logging.DEBUG: __format.format(color=grey, reset=reset, yellow=yellow, green=green),
|
||||||
|
|||||||
@@ -8,14 +8,15 @@ import time
|
|||||||
import traceback
|
import traceback
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
import redis
|
||||||
import yaml
|
import yaml
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from flask_login import LoginManager, login_required
|
from flask_login import LoginManager, login_required
|
||||||
from flask_socketio import SocketIO
|
from flask_socketio import SocketIO
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from flaskext.markdown import Markdown
|
|
||||||
from pytz import timezone, utc
|
from pytz import timezone, utc
|
||||||
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||||
|
|
||||||
from .init_declare import CustomFormatter, check_api
|
from .init_declare import CustomFormatter, check_api
|
||||||
|
|
||||||
@@ -37,12 +38,15 @@ class Framework:
|
|||||||
self.db = None
|
self.db = None
|
||||||
self.scheduler = None
|
self.scheduler = None
|
||||||
self.socketio = None
|
self.socketio = None
|
||||||
|
self.rd = None
|
||||||
self.path_app_root = None
|
self.path_app_root = None
|
||||||
self.path_data = None
|
self.path_data = None
|
||||||
self.users = {}
|
self.users = {}
|
||||||
|
self.get_cache = None
|
||||||
|
|
||||||
self.__level_unset_logger_list = []
|
self.__level_unset_logger_list = []
|
||||||
self.__logger_list = []
|
self.__logger_list = []
|
||||||
|
self.all_log_filehandler = None
|
||||||
self.__exit_code = -1
|
self.__exit_code = -1
|
||||||
self.login_manager = None
|
self.login_manager = None
|
||||||
#self.plugin_instance_list = {}
|
#self.plugin_instance_list = {}
|
||||||
@@ -59,14 +63,17 @@ class Framework:
|
|||||||
def __initialize(self):
|
def __initialize(self):
|
||||||
os.environ["PYTHONUNBUFFERED"] = "1"
|
os.environ["PYTHONUNBUFFERED"] = "1"
|
||||||
os.environ['FF'] = "true"
|
os.environ['FF'] = "true"
|
||||||
|
os.environ['FF_PYTHON'] = sys.executable
|
||||||
self.__config_initialize("first")
|
self.__config_initialize("first")
|
||||||
self.__make_default_dir()
|
self.__make_default_dir()
|
||||||
|
|
||||||
self.logger = self.get_logger(__package__)
|
self.logger = self.get_logger(__package__)
|
||||||
|
self.get_logger('support')
|
||||||
import support
|
import support
|
||||||
|
|
||||||
self.__prepare_starting()
|
self.__prepare_starting()
|
||||||
self.app = Flask(__name__)
|
self.app = Flask(__name__)
|
||||||
|
self.app.wsgi_app = ProxyFix(self.app.wsgi_app, x_proto=1)
|
||||||
self.__config_initialize('flask')
|
self.__config_initialize('flask')
|
||||||
|
|
||||||
self.__init_db()
|
self.__init_db()
|
||||||
@@ -82,7 +89,6 @@ class Framework:
|
|||||||
self.socketio = SocketIO(self.app, cors_allowed_origins="*", async_mode='threading')
|
self.socketio = SocketIO(self.app, cors_allowed_origins="*", async_mode='threading')
|
||||||
|
|
||||||
CORS(self.app)
|
CORS(self.app)
|
||||||
Markdown(self.app)
|
|
||||||
|
|
||||||
self.login_manager = LoginManager()
|
self.login_manager = LoginManager()
|
||||||
self.login_manager.init_app(self.app)
|
self.login_manager.init_app(self.app)
|
||||||
@@ -94,10 +100,11 @@ class Framework:
|
|||||||
self.app.config.update(
|
self.app.config.update(
|
||||||
DROPZONE_MAX_FILE_SIZE = 102400,
|
DROPZONE_MAX_FILE_SIZE = 102400,
|
||||||
DROPZONE_TIMEOUT = 5*60*1000,
|
DROPZONE_TIMEOUT = 5*60*1000,
|
||||||
#DROPZONE_ALLOWED_FILE_CUSTOM = True,
|
DROPZONE_ALLOWED_FILE_CUSTOM = True,
|
||||||
#DROPZONE_ALLOWED_FILE_TYPE = 'default, image, audio, video, text, app, *.*',
|
DROPZONE_ALLOWED_FILE_TYPE = "image/*, audio/*, video/*, text/*, application/*, *.*",
|
||||||
)
|
)
|
||||||
self.dropzone = Dropzone(self.app)
|
self.dropzone = Dropzone(self.app)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def __init_db(self):
|
def __init_db(self):
|
||||||
@@ -131,19 +138,20 @@ class Framework:
|
|||||||
|
|
||||||
|
|
||||||
def __init_celery(self):
|
def __init_celery(self):
|
||||||
|
redis_port = 6379
|
||||||
try:
|
try:
|
||||||
from celery import Celery
|
|
||||||
|
|
||||||
#if frame.config['use_celery'] == False or platform.system() == 'Windows':
|
|
||||||
if self.config['use_celery'] == False:
|
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)
|
redis_port = os.environ.get('REDIS_PORT', None)
|
||||||
if redis_port == None:
|
if redis_port == None:
|
||||||
redis_port = self.config.get('redis_port', None)
|
redis_port = self.config.get('redis_port', None)
|
||||||
if redis_port == None:
|
if redis_port == None:
|
||||||
redis_port = '6379'
|
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_BROKER_URL'] = 'redis://localhost:%s/0' % redis_port
|
||||||
self.app.config['CELERY_RESULT_BACKEND'] = '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: {running_type}")
|
||||||
#F.logger.info(f"celery running_type: {options}")
|
#F.logger.info(f"celery running_type: {options}")
|
||||||
celery.steps['worker'].add(CustomArgs)
|
celery.steps['worker'].add(CustomArgs)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if self.config['use_celery']:
|
if self.config['use_celery']:
|
||||||
self.logger.error('CELERY!!!')
|
self.logger.error('CELERY!!!')
|
||||||
@@ -187,6 +196,14 @@ class Framework:
|
|||||||
if len(args) > 0 and type(args[0]) == type(dummy_func):
|
if len(args) > 0 and type(args[0]) == type(dummy_func):
|
||||||
return args[0]
|
return args[0]
|
||||||
self.f(*args, **kwargs)
|
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
|
return celery
|
||||||
|
|
||||||
|
|
||||||
@@ -201,11 +218,13 @@ class Framework:
|
|||||||
self.logger.error(f'Exception:{str(e)}')
|
self.logger.error(f'Exception:{str(e)}')
|
||||||
self.logger.error(traceback.format_exc())
|
self.logger.error(traceback.format_exc())
|
||||||
self.SystemModelSetting = SystemInstance.ModelSetting
|
self.SystemModelSetting = SystemInstance.ModelSetting
|
||||||
SystemInstance.plugin_load()
|
if self.config['run_flask']:
|
||||||
|
SystemInstance.plugin_load()
|
||||||
self.app.register_blueprint(SystemInstance.blueprint)
|
self.app.register_blueprint(SystemInstance.blueprint)
|
||||||
self.config['flag_system_loading'] = True
|
self.config['flag_system_loading'] = True
|
||||||
self.__config_initialize('member')
|
self.__config_initialize('member')
|
||||||
self.__config_initialize('system_loading_after')
|
self.__config_initialize('system_loading_after')
|
||||||
|
self.set_level(self.SystemModelSetting.get_int('log_level'))
|
||||||
|
|
||||||
|
|
||||||
def initialize_plugin(self):
|
def initialize_plugin(self):
|
||||||
@@ -232,6 +251,7 @@ class Framework:
|
|||||||
|
|
||||||
self.__make_default_logger()
|
self.__make_default_logger()
|
||||||
self.__config_initialize("last")
|
self.__config_initialize("last")
|
||||||
|
self.config['loading_completed'] = True
|
||||||
self.logger.info('### LAST')
|
self.logger.info('### LAST')
|
||||||
self.logger.info(f"### PORT: {self.config.get('port')}")
|
self.logger.info(f"### PORT: {self.config.get('port')}")
|
||||||
self.logger.info('### Now you can access App by webbrowser!!')
|
self.logger.info('### Now you can access App by webbrowser!!')
|
||||||
@@ -248,6 +268,7 @@ class Framework:
|
|||||||
def __config_initialize(self, mode):
|
def __config_initialize(self, mode):
|
||||||
if mode == "first":
|
if mode == "first":
|
||||||
self.config = {}
|
self.config = {}
|
||||||
|
self.config['loading_completed'] = False
|
||||||
self.config['os'] = platform.system()
|
self.config['os'] = platform.system()
|
||||||
self.config['flag_system_loading'] = False
|
self.config['flag_system_loading'] = False
|
||||||
#self.config['run_flask'] = True if sys.argv[0].endswith('main.py') else 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['export_filepath'] = os.path.join(self.config['path_app'], 'export.sh')
|
||||||
self.config['exist_export'] = os.path.exists(self.config['export_filepath'])
|
self.config['exist_export'] = os.path.exists(self.config['export_filepath'])
|
||||||
self.config['recent_version'] = '--'
|
self.config['recent_version'] = '--'
|
||||||
|
from .version import VERSION
|
||||||
|
self.config['version'] = VERSION
|
||||||
self.__process_args()
|
self.__process_args()
|
||||||
self.__load_config()
|
self.__load_config()
|
||||||
self.__init_define()
|
self.__init_define()
|
||||||
@@ -270,7 +293,7 @@ class Framework:
|
|||||||
self.config['notify_yaml_filepath'] = os.path.join(self.config['path_data'], 'db', 'notify.yaml')
|
self.config['notify_yaml_filepath'] = os.path.join(self.config['path_data'], 'db', 'notify.yaml')
|
||||||
if 'running_type' not in self.config:
|
if 'running_type' not in self.config:
|
||||||
self.config['running_type'] = 'native'
|
self.config['running_type'] = 'native'
|
||||||
|
self.pip_install()
|
||||||
elif mode == "flask":
|
elif mode == "flask":
|
||||||
self.app.secret_key = os.urandom(24)
|
self.app.secret_key = os.urandom(24)
|
||||||
self.app.config['TEMPLATES_AUTO_RELOAD'] = True
|
self.app.config['TEMPLATES_AUTO_RELOAD'] = True
|
||||||
@@ -295,9 +318,7 @@ class Framework:
|
|||||||
self.config['DEFINE'] = {}
|
self.config['DEFINE'] = {}
|
||||||
# 이건 필요 없음
|
# 이건 필요 없음
|
||||||
self.config['DEFINE']['GIT_VERSION_URL'] = 'https://raw.githubusercontent.com/flaskfarm/flaskfarm/main/lib/framework/version.py'
|
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'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def __process_args(self):
|
def __process_args(self):
|
||||||
# celery 에서 args 처리시 문제 발생.
|
# celery 에서 args 처리시 문제 발생.
|
||||||
@@ -363,6 +384,9 @@ class Framework:
|
|||||||
self.config['debug'] = False
|
self.config['debug'] = False
|
||||||
if self.config.get('plugin_update') == None:
|
if self.config.get('plugin_update') == None:
|
||||||
self.config['plugin_update'] = True
|
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:
|
if self.config.get('plugin_loading_only_devpath') == None:
|
||||||
self.config['plugin_loading_only_devpath'] = False
|
self.config['plugin_loading_only_devpath'] = False
|
||||||
if self.config.get('plugin_loading_list') == None:
|
if self.config.get('plugin_loading_list') == None:
|
||||||
@@ -402,8 +426,8 @@ class Framework:
|
|||||||
try:
|
try:
|
||||||
if self.config['flag_system_loading']:
|
if self.config['flag_system_loading']:
|
||||||
try:
|
try:
|
||||||
from system import SystemModelSetting
|
#from system import SystemModelSetting
|
||||||
level = SystemModelSetting.get_int('log_level')
|
level = self.SystemModelSetting.get_int('log_level')
|
||||||
except:
|
except:
|
||||||
level = logging.DEBUG
|
level = logging.DEBUG
|
||||||
if self.__level_unset_logger_list is not None:
|
if self.__level_unset_logger_list is not None:
|
||||||
@@ -426,7 +450,7 @@ class Framework:
|
|||||||
return converted.timetuple()
|
return converted.timetuple()
|
||||||
|
|
||||||
if from_command == False:
|
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:
|
else:
|
||||||
file_formatter = logging.Formatter(u'[%(asctime)s] %(message)s')
|
file_formatter = logging.Formatter(u'[%(asctime)s] %(message)s')
|
||||||
|
|
||||||
@@ -435,10 +459,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 = 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)
|
fileHandler.setFormatter(file_formatter)
|
||||||
logger.addHandler(fileHandler)
|
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:
|
if from_command == False:
|
||||||
streamHandler = logging.StreamHandler()
|
streamHandler = logging.StreamHandler()
|
||||||
streamHandler.setFormatter(CustomFormatter())
|
streamHandler.setFormatter(CustomFormatter())
|
||||||
logger.addHandler(streamHandler)
|
logger.addHandler(streamHandler)
|
||||||
|
|
||||||
|
if self.all_log_filehandler != None:
|
||||||
|
logger.addHandler(self.all_log_filehandler)
|
||||||
|
|
||||||
return logger
|
return logger
|
||||||
|
|
||||||
|
|
||||||
@@ -459,7 +491,7 @@ class Framework:
|
|||||||
def set_level(self, level):
|
def set_level(self, level):
|
||||||
try:
|
try:
|
||||||
for l in self.__logger_list:
|
for l in self.__logger_list:
|
||||||
l.setLevel(level)
|
l.setLevel(int(level))
|
||||||
self.__make_default_logger()
|
self.__make_default_logger()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -468,7 +500,7 @@ class Framework:
|
|||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
host = '0.0.0.0'
|
host = '0.0.0.0'
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
try:
|
try:
|
||||||
#self.logger.debug(d(self.config))
|
#self.logger.debug(d(self.config))
|
||||||
# allow_unsafe_werkzeug=True termux nohup 실행시 필요함
|
# allow_unsafe_werkzeug=True termux nohup 실행시 필요함
|
||||||
@@ -517,8 +549,8 @@ class Framework:
|
|||||||
PluginManager.plugin_unload()
|
PluginManager.plugin_unload()
|
||||||
with self.app.test_request_context():
|
with self.app.test_request_context():
|
||||||
self.socketio.stop()
|
self.socketio.stop()
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
self.logger.error('Exception:%s', exception)
|
self.logger.error(f"Exception:{str(e)}")
|
||||||
self.logger.error(traceback.format_exc())
|
self.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
def get_recent_version(self):
|
def get_recent_version(self):
|
||||||
@@ -532,3 +564,11 @@ class Framework:
|
|||||||
self.logger.error(traceback.format_exc())
|
self.logger.error(traceback.format_exc())
|
||||||
self.config['recent_version'] = '확인 실패'
|
self.config['recent_version'] = '확인 실패'
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# dev 도커용. package는 setup에 포함.
|
||||||
|
def pip_install(self):
|
||||||
|
try:
|
||||||
|
import json_fix
|
||||||
|
except:
|
||||||
|
os.system('pip install json_fix')
|
||||||
|
|||||||
@@ -1,93 +1,144 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from framework import F, logger
|
||||||
from support import SupportYaml, d
|
from support import SupportYaml, d
|
||||||
|
|
||||||
from framework import F
|
|
||||||
|
|
||||||
|
|
||||||
class MenuManager:
|
class MenuManager:
|
||||||
menu_map = None
|
menu_map = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __load_menu_yaml(cls):
|
def __load_menu_yaml(cls):
|
||||||
menu_yaml_filepath = os.path.join(F.config['path_data'], 'db', 'menu.yaml')
|
try:
|
||||||
if os.path.exists(menu_yaml_filepath) == False:
|
menu_yaml_filepath = os.path.join(F.config['path_data'], 'db', 'menu.yaml')
|
||||||
shutil.copy(
|
if os.path.exists(menu_yaml_filepath) == False:
|
||||||
os.path.join(F.config['path_app'], 'files', 'menu.yaml.template'),
|
shutil.copy(
|
||||||
menu_yaml_filepath
|
os.path.join(F.config['path_app'], 'files', 'menu.yaml.template'),
|
||||||
)
|
menu_yaml_filepath
|
||||||
cls.menu_map = SupportYaml.read_yaml(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
|
@classmethod
|
||||||
def init_menu(cls):
|
def init_menu(cls):
|
||||||
cls.__load_menu_yaml()
|
cls.__load_menu_yaml()
|
||||||
from .init_plugin import PluginManager
|
if cls.__init_menu() == False:
|
||||||
plugin_menus = PluginManager.plugin_menus
|
cls.menu_map = SupportYaml.read_yaml(os.path.join(F.config['path_app'], 'files', 'menu.yaml.template'))
|
||||||
copy_map = []
|
cls.__init_menu()
|
||||||
|
|
||||||
for category in cls.menu_map:
|
@classmethod
|
||||||
if 'uri' in category:
|
def __init_menu(cls):
|
||||||
copy_map.append(category)
|
try:
|
||||||
continue
|
from .init_plugin import PluginManager
|
||||||
cate_count = 0
|
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 = []
|
tmp_cate_list = []
|
||||||
for item in category['list']:
|
for item in category['list']:
|
||||||
if item['uri'] in plugin_menus:
|
if item['uri'] in plugin_menus:
|
||||||
plugin_menus[item['uri']]['match'] = True
|
plugin_menus[item['uri']]['match'] = True
|
||||||
tmp_cate_list.append(plugin_menus[item['uri']]['menu'])
|
tmp_cate_list.append(plugin_menus[item['uri']]['menu'])
|
||||||
cate_count += 1
|
cate_count += 1
|
||||||
elif item['uri'].startswith('http'):
|
elif item['uri'].startswith('http'):
|
||||||
tmp_cate_list.append({
|
tmp_cate_list.append({
|
||||||
'uri': item['uri'],
|
'uri': item['uri'],
|
||||||
'name': item['name'],
|
'name': item['name'],
|
||||||
'target': item.get('target', '_blank')
|
'target': item.get('target', '_blank')
|
||||||
})
|
})
|
||||||
cate_count += 1
|
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 ['-']:
|
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.append({
|
tmp_cate_list.append({
|
||||||
'uri': item['uri'],
|
'uri': item['uri'],
|
||||||
'name': item.get('name', ''),
|
'name': item.get('name', ''),
|
||||||
'list': PluginManager.setting_menus
|
|
||||||
})
|
})
|
||||||
|
cate_count += 1
|
||||||
if cate_count > 0:
|
elif item['uri'] == 'setting':
|
||||||
copy_map.append({
|
# 2024.06.04
|
||||||
'name': category['name'],
|
# 확장설정도 메뉴 구성
|
||||||
'list': tmp_cate_list,
|
if len(PluginManager.setting_menus) > 0:
|
||||||
'count': cate_count
|
set_tmp = item.get('list')
|
||||||
})
|
if set_tmp:
|
||||||
cls.menu_map = copy_map
|
cp = PluginManager.setting_menus.copy()
|
||||||
|
include = []
|
||||||
make_dummy_cate = False
|
for set_ch in set_tmp:
|
||||||
for name, plugin_menu in plugin_menus.items():
|
if set_ch.get('uri') and (set_ch.get('uri') == '-' or set_ch.get('uri').startswith('http')):
|
||||||
#F.logger.info(d(plugin_menu))
|
include.append(set_ch)
|
||||||
#if 'uri' not in plugin_menu['menu']:
|
continue
|
||||||
# continue
|
|
||||||
if plugin_menu['match'] == False:
|
for i, ps in enumerate(cp):
|
||||||
if make_dummy_cate == False:
|
if set_ch.get('plugin') != None and set_ch.get('plugin') == ps.get('plugin'):
|
||||||
make_dummy_cate = True
|
include.append(ps)
|
||||||
cls.menu_map.insert(len(cls.menu_map)-1, {
|
del cp[i]
|
||||||
'name':'미분류', 'count':0, 'list':[]
|
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':[]
|
||||||
|
})
|
||||||
|
|
||||||
c = cls.menu_map[-2]
|
c = cls.menu_map[-2]
|
||||||
c['count'] += 1
|
c['count'] += 1
|
||||||
c['list'].append(plugin_menu['menu'])
|
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))
|
#F.logger.warning(d(cls.menu_map))
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_menu_map(cls):
|
def get_menu_map(cls):
|
||||||
#F.logger.warning(d(cls.menu_map))
|
|
||||||
return 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
|
||||||
|
|
||||||
@@ -3,13 +3,13 @@ import platform
|
|||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from support import SupportFile, SupportSubprocess, SupportYaml
|
|
||||||
|
|
||||||
from framework import F
|
from framework import F
|
||||||
|
from support import SupportFile, SupportSubprocess, SupportYaml
|
||||||
|
|
||||||
|
|
||||||
class PluginManager:
|
class PluginManager:
|
||||||
@@ -30,13 +30,13 @@ class PluginManager:
|
|||||||
tmps = os.listdir(plugin_path)
|
tmps = os.listdir(plugin_path)
|
||||||
add_plugin_list = []
|
add_plugin_list = []
|
||||||
for t in tmps:
|
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)
|
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)}
|
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
|
plugins = plugins + add_plugin_list
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
F.logger.error('Exception:%s', exception)
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
F.logger.error(traceback.format_exc())
|
F.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
if F.config.get('plugin_loading_only_devpath', None) == True:
|
if F.config.get('plugin_loading_only_devpath', None) == True:
|
||||||
@@ -59,12 +59,12 @@ class PluginManager:
|
|||||||
tmps = os.listdir(__)
|
tmps = os.listdir(__)
|
||||||
add_plugin_list = []
|
add_plugin_list = []
|
||||||
for t in tmps:
|
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)
|
add_plugin_list.append(t)
|
||||||
cls.all_package_list[t] = {'pos':'dev', 'path':os.path.join(__, t), 'loading':True}
|
cls.all_package_list[t] = {'pos':'dev', 'path':os.path.join(__, t), 'loading':True}
|
||||||
plugins = plugins + add_plugin_list
|
plugins = plugins + add_plugin_list
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
F.logger.error('Exception:%s', exception)
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
F.logger.error(traceback.format_exc())
|
F.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
# plugin_loading_list
|
# plugin_loading_list
|
||||||
@@ -79,8 +79,8 @@ class PluginManager:
|
|||||||
cls.all_package_list[_]['loading'] = False
|
cls.all_package_list[_]['loading'] = False
|
||||||
cls.all_package_list[_]['status'] = 'not_include_loading_list'
|
cls.all_package_list[_]['status'] = 'not_include_loading_list'
|
||||||
plugins = new_plugins
|
plugins = new_plugins
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
F.logger.error('Exception:%s', exception)
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
F.logger.error(traceback.format_exc())
|
F.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
# plugin_except_list
|
# plugin_except_list
|
||||||
@@ -95,8 +95,8 @@ class PluginManager:
|
|||||||
cls.all_package_list[_]['loading'] = False
|
cls.all_package_list[_]['loading'] = False
|
||||||
cls.all_package_list[_]['status'] = 'include_except_list'
|
cls.all_package_list[_]['status'] = 'include_except_list'
|
||||||
plugins = new_plugins
|
plugins = new_plugins
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
F.logger.error('Exception:%s', exception)
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
F.logger.error(traceback.format_exc())
|
F.logger.error(traceback.format_exc())
|
||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
@@ -111,45 +111,32 @@ class PluginManager:
|
|||||||
|
|
||||||
F.logger.debug(plugins)
|
F.logger.debug(plugins)
|
||||||
for plugin_name in plugins:
|
for plugin_name in plugins:
|
||||||
F.logger.debug(f'[+] PLUGIN LOADING Start.. [{plugin_name}]')
|
F.logger.info(f'[+] PLUGIN IMPORT Start.. [{plugin_name}]')
|
||||||
|
import_start_time = time.time()
|
||||||
entity = cls.all_package_list[plugin_name]
|
entity = cls.all_package_list[plugin_name]
|
||||||
entity['version'] = '3'
|
|
||||||
try:
|
try:
|
||||||
mod = __import__('%s' % (plugin_name), fromlist=[])
|
|
||||||
mod_plugin_info = None
|
|
||||||
try:
|
try:
|
||||||
mod_plugin_info = getattr(mod, 'plugin_info')
|
mod = __import__(f'{plugin_name}.setup', fromlist=['setup'])
|
||||||
entity['module'] = mod
|
except Exception as e:
|
||||||
except Exception as exception:
|
F.logger.error(f'Exception:{str(e)}')
|
||||||
F.logger.info(f'[!] PLUGIN_INFO not exist : [{plugin_name}] - is FF')
|
F.logger.error(traceback.format_exc())
|
||||||
|
F.logger.warning(f'[!] NOT normal plugin : [{plugin_name}]')
|
||||||
if mod_plugin_info == None:
|
continue
|
||||||
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}]')
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if entity['version'] != '4':
|
entity['setup_mod'] = mod
|
||||||
mod_blue_print = getattr(mod, 'blueprint')
|
entity['P'] = getattr(mod, 'P')
|
||||||
else:
|
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:
|
if mod_blue_print:
|
||||||
F.app.register_blueprint(mod_blue_print)
|
F.app.register_blueprint(mod_blue_print)
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
#logger.error('Exception:%s', exception)
|
|
||||||
#logger.error(traceback.format_exc())
|
|
||||||
F.logger.warning(f'[!] BLUEPRINT not exist : [{plugin_name}]')
|
F.logger.warning(f'[!] BLUEPRINT not exist : [{plugin_name}]')
|
||||||
|
|
||||||
|
import_elapsed_time = time.time() - import_start_time
|
||||||
|
F.logger.info(f'[+] PLUGIN IMPORT End.. [{plugin_name}] ({import_elapsed_time:.3f}s)')
|
||||||
cls.plugin_list[plugin_name] = entity
|
cls.plugin_list[plugin_name] = entity
|
||||||
#system.LogicPlugin.current_loading_plugin_list[plugin_name]['status'] = 'success'
|
except Exception as e:
|
||||||
#system.LogicPlugin.current_loading_plugin_list[plugin_name]['info'] = mod_plugin_info
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
except Exception as exception:
|
|
||||||
F.logger.error('Exception:%s', exception)
|
|
||||||
F.logger.error(traceback.format_exc())
|
F.logger.error(traceback.format_exc())
|
||||||
F.logger.debug('no blueprint')
|
F.logger.debug('no blueprint')
|
||||||
cls.all_package_list[plugin_name]['loading'] = False
|
cls.all_package_list[plugin_name]['loading'] = False
|
||||||
@@ -157,36 +144,44 @@ class PluginManager:
|
|||||||
cls.all_package_list[plugin_name]['log'] = traceback.format_exc()
|
cls.all_package_list[plugin_name]['log'] = traceback.format_exc()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if not F.config['run_celery']:
|
if not F.config['run_celery']:
|
||||||
try:
|
try:
|
||||||
with F.app.app_context():
|
with F.app.app_context():
|
||||||
F.db.create_all()
|
F.db.create_all()
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
F.logger.error('Exception:%s', exception)
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
F.logger.error(traceback.format_exc())
|
F.logger.error(traceback.format_exc())
|
||||||
F.logger.debug('db.create_all error')
|
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:
|
||||||
|
F.logger.info(f'[!] plugin_load_celery start : [{key}]')
|
||||||
|
mod_plugin_load()
|
||||||
|
F.logger.info(f'[!] plugin_load_celery end : [{key}]')
|
||||||
|
except Exception as e:
|
||||||
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
|
F.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
if not F.config['run_flask']:
|
if not F.config['run_flask']:
|
||||||
return
|
return
|
||||||
|
|
||||||
for key, entity in cls.plugin_list.items():
|
for key, entity in cls.plugin_list.items():
|
||||||
try:
|
try:
|
||||||
mod_plugin_load = None
|
mod_plugin_load = getattr(entity['P'], 'plugin_load')
|
||||||
if entity['version'] == '3':
|
|
||||||
mod_plugin_load = getattr(entity['module'], 'plugin_load')
|
|
||||||
elif entity['version'] == '4':
|
|
||||||
mod_plugin_load = getattr(entity['P'], 'plugin_load')
|
|
||||||
if mod_plugin_load:
|
if mod_plugin_load:
|
||||||
def func(mod_plugin_load, key):
|
def func(mod_plugin_load, key):
|
||||||
try:
|
try:
|
||||||
F.logger.debug(f'[!] plugin_load threading start : [{key}]')
|
load_start_time = time.time()
|
||||||
#mod.plugin_load()
|
F.logger.info(f'[!] plugin_load threading start : [{key}]')
|
||||||
mod_plugin_load()
|
mod_plugin_load()
|
||||||
F.logger.debug(f'[!] plugin_load threading end : [{key}]')
|
load_elapsed_time = time.time() - load_start_time
|
||||||
except Exception as exception:
|
F.logger.info(f'[!] plugin_load threading end : [{key}] ({load_elapsed_time:.3f}s)')
|
||||||
|
except Exception as e:
|
||||||
F.logger.error('### plugin_load exception : %s', key)
|
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())
|
F.logger.error(traceback.format_exc())
|
||||||
cls.all_package_list[key]['loading'] = False
|
cls.all_package_list[key]['loading'] = False
|
||||||
cls.all_package_list[key]['status'] = 'plugin_load error'
|
cls.all_package_list[key]['status'] = 'plugin_load error'
|
||||||
@@ -199,42 +194,29 @@ class PluginManager:
|
|||||||
MenuManager.init_menu()
|
MenuManager.init_menu()
|
||||||
F.logger.info(f"플러그인 로딩 실패로 메뉴 삭제2 : {key}")
|
F.logger.info(f"플러그인 로딩 실패로 메뉴 삭제2 : {key}")
|
||||||
|
|
||||||
|
t = threading.Thread(target=func, args=(mod_plugin_load, key))
|
||||||
|
t.setDaemon(True)
|
||||||
|
t.start()
|
||||||
|
|
||||||
# mod는 위에서 로딩
|
except Exception as e:
|
||||||
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:
|
|
||||||
F.logger.debug(f'[!] PLUGIN_LOAD function not exist : [{key}]')
|
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:
|
try:
|
||||||
mod_menu = None
|
mod_menu = getattr(entity['P'], 'menu')
|
||||||
if entity['version'] == '3':
|
|
||||||
mod_menu = getattr(entity['module'], 'menu')
|
|
||||||
elif entity['version'] == '4':
|
|
||||||
mod_menu = getattr(entity['P'], 'menu')
|
|
||||||
|
|
||||||
if mod_menu and cls.all_package_list[key]['loading'] != False:
|
if mod_menu and cls.all_package_list[key]['loading'] != False:
|
||||||
cls.plugin_menus[key]= {'menu':mod_menu, 'match':False}
|
cls.plugin_menus[key]= {'menu':mod_menu, 'match':False}
|
||||||
if entity['version'] == '4':
|
setting_menu = getattr(entity['P'], 'setting_menu')
|
||||||
setting_menu = getattr(entity['P'], 'setting_menu')
|
if setting_menu != None and cls.all_package_list[key]['loading'] != False:
|
||||||
if setting_menu != None and cls.all_package_list[key]['loading'] != False:
|
setting_menu['plugin'] = entity['P'].package_name
|
||||||
F.logger.info(f"메뉴 포함 : {key}")
|
F.logger.info(f"확장 설정 : {key}")
|
||||||
cls.setting_menus.append(setting_menu)
|
cls.setting_menus.append(setting_menu)
|
||||||
|
|
||||||
|
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
F.logger.debug('no menu')
|
F.logger.debug('no menu')
|
||||||
F.logger.debug('### plugin_load threading all start.. : %s ', len(cls.plugin_list))
|
F.logger.debug('### plugin_load threading all start.. : %s ', len(cls.plugin_list))
|
||||||
# 모든 모듈을 로드한 이후에 app 등록, table 생성, start
|
# 모든 모듈을 로드한 이후에 app 등록, table 생성, start
|
||||||
|
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
F.logger.error('Exception:%s', exception)
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
F.logger.error(traceback.format_exc())
|
F.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
@@ -243,17 +225,9 @@ class PluginManager:
|
|||||||
def plugin_unload(cls):
|
def plugin_unload(cls):
|
||||||
for key, entity in cls.plugin_list.items():
|
for key, entity in cls.plugin_list.items():
|
||||||
try:
|
try:
|
||||||
if entity['version'] == '3':
|
mod_plugin_unload = getattr(entity['P'], 'plugin_unload')
|
||||||
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')
|
|
||||||
if mod_plugin_unload:
|
if mod_plugin_unload:
|
||||||
mod_plugin_unload()
|
mod_plugin_unload()
|
||||||
#mod.plugin_unload()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
F.logger.error('module:%s', key)
|
F.logger.error('module:%s', key)
|
||||||
F.logger.error(f'Exception:{str(e)}')
|
F.logger.error(f'Exception:{str(e)}')
|
||||||
@@ -267,6 +241,7 @@ class PluginManager:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def plugin_install(cls, plugin_git, zip_url=None, zip_filename=None):
|
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
|
is_git = True if plugin_git != None and plugin_git != '' else False
|
||||||
ret = {}
|
ret = {}
|
||||||
try:
|
try:
|
||||||
@@ -381,7 +356,7 @@ class PluginManager:
|
|||||||
tmps = os.listdir(plugins_path)
|
tmps = os.listdir(plugins_path)
|
||||||
for t in tmps:
|
for t in tmps:
|
||||||
plugin_path = os.path.join(plugins_path, t)
|
plugin_path = os.path.join(plugins_path, t)
|
||||||
if t.startswith('_'):
|
if t.startswith('_') or t.startswith('.'):
|
||||||
continue
|
continue
|
||||||
if os.path.exists(os.path.join(plugin_path, '.git')):
|
if os.path.exists(os.path.join(plugin_path, '.git')):
|
||||||
command = ['git', '-C', plugin_path, 'reset', '--hard', 'HEAD']
|
command = ['git', '-C', plugin_path, 'reset', '--hard', 'HEAD']
|
||||||
@@ -392,14 +367,15 @@ class PluginManager:
|
|||||||
F.logger.debug(ret)
|
F.logger.debug(ret)
|
||||||
else:
|
else:
|
||||||
F.logger.debug(f"{plugin_path} not git repo")
|
F.logger.debug(f"{plugin_path} not git repo")
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
F.logger.error('Exception:%s', exception)
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
F.logger.error(traceback.format_exc())
|
F.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_plugin_instance(cls, package_name):
|
def get_plugin_instance(cls, package_name):
|
||||||
try:
|
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:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import traceback
|
|||||||
from flask import (jsonify, redirect, render_template, request,
|
from flask import (jsonify, redirect, render_template, request,
|
||||||
send_from_directory)
|
send_from_directory)
|
||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
|
|
||||||
from framework import F
|
from framework import F
|
||||||
|
|
||||||
|
|
||||||
@@ -86,27 +85,31 @@ def open_file(path):
|
|||||||
@F.app.route("/file/<path:path>")
|
@F.app.route("/file/<path:path>")
|
||||||
@F.check_api
|
@F.check_api
|
||||||
def file2(path):
|
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)
|
return send_from_directory('/', path, as_attachment=True)
|
||||||
|
|
||||||
|
|
||||||
@F.app.route("/upload", methods=['GET', 'POST'])
|
@F.app.route("/upload", methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
def upload():
|
def upload():
|
||||||
try:
|
try:
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
f = request.files['file']
|
f = request.files['file']
|
||||||
from werkzeug import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
upload_path = F.SystemModelSetting.get('path_upload')
|
upload_path = F.SystemModelSetting.get('path_upload')
|
||||||
os.makedirs(upload_path, exist_ok=True)
|
os.makedirs(upload_path, exist_ok=True)
|
||||||
f.save(os.path.join(upload_path, secure_filename(f.filename)))
|
f.save(os.path.join(upload_path, secure_filename(f.filename)))
|
||||||
return jsonify('success')
|
return jsonify('success')
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
F.logger.error('Exception:%s', exception)
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
F.logger.error(traceback.format_exc())
|
F.logger.error(traceback.format_exc())
|
||||||
return jsonify('fail')
|
return jsonify('fail')
|
||||||
|
|
||||||
|
|
||||||
@F.app.route("/videojs", methods=['GET', 'POST'])
|
@F.app.route("/videojs", methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
def videojs():
|
def videojs():
|
||||||
data = {}
|
data = {}
|
||||||
data['play_title'] = request.form['play_title']
|
data['play_title'] = request.form['play_title']
|
||||||
@@ -116,9 +119,33 @@ def videojs():
|
|||||||
data['play_subtitle_src'] = request.form['play_subtitle_src']
|
data['play_subtitle_src'] = request.form['play_subtitle_src']
|
||||||
return render_template('videojs.html', data=data)
|
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'])
|
@F.app.route("/headers", methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
def headers():
|
def headers():
|
||||||
from support import d
|
from support import d
|
||||||
F.logger.info(d(request.headers))
|
F.logger.info(d(request.headers))
|
||||||
@@ -127,6 +154,7 @@ def headers():
|
|||||||
|
|
||||||
# 3.10에서 이거 필수
|
# 3.10에서 이거 필수
|
||||||
@F.socketio.on('connect', namespace=f'/framework')
|
@F.socketio.on('connect', namespace=f'/framework')
|
||||||
|
@login_required
|
||||||
def connect():
|
def connect():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ from framework import F
|
|||||||
|
|
||||||
|
|
||||||
def get_menu(full_query):
|
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)
|
match = re.compile(r'\/(?P<menu>.*?)\/manual\/(?P<sub2>.*?)($|\?)').match(full_query)
|
||||||
if match:
|
if match:
|
||||||
return match.group('menu'), 'manual', match.group('sub2')
|
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_menu=get_menu)
|
||||||
app.jinja_env.globals.update(get_theme=get_theme)
|
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_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(get_web_title=get_web_title)
|
||||||
app.jinja_env.globals.update(dropzone=F.dropzone)
|
app.jinja_env.globals.update(dropzone=F.dropzone)
|
||||||
|
|
||||||
app.jinja_env.filters['get_menu'] = get_menu
|
app.jinja_env.filters['get_menu'] = get_menu
|
||||||
app.jinja_env.filters['get_theme'] = get_theme
|
app.jinja_env.filters['get_theme'] = get_theme
|
||||||
app.jinja_env.filters['get_menu_map'] = MenuManager.get_menu_map
|
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.filters['get_web_title'] = get_web_title
|
||||||
|
|
||||||
app.jinja_env.auto_reload = True
|
app.jinja_env.auto_reload = True
|
||||||
|
|||||||
@@ -4,17 +4,18 @@ import time
|
|||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
from support import SingletonClass
|
|
||||||
|
|
||||||
from framework import F
|
from framework import F
|
||||||
|
from support import SingletonClass
|
||||||
|
|
||||||
namespace = 'log'
|
namespace = 'log'
|
||||||
|
|
||||||
@F.socketio.on('connect', namespace='/%s' % namespace)
|
@F.socketio.on('connect', namespace='/%s' % namespace)
|
||||||
|
@F.login_required
|
||||||
def socket_connect():
|
def socket_connect():
|
||||||
F.logger.debug('log connect')
|
F.logger.debug('log connect')
|
||||||
|
|
||||||
@F.socketio.on('start', namespace='/%s' % namespace)
|
@F.socketio.on('start', namespace='/%s' % namespace)
|
||||||
|
@F.login_required
|
||||||
def socket_file(data):
|
def socket_file(data):
|
||||||
try:
|
try:
|
||||||
package = filename = None
|
package = filename = None
|
||||||
@@ -24,8 +25,8 @@ def socket_file(data):
|
|||||||
filename = data['filename']
|
filename = data['filename']
|
||||||
LogViewer.instance().start(package, filename, request.sid)
|
LogViewer.instance().start(package, filename, request.sid)
|
||||||
F.logger.debug('start package:%s filename:%s sid:%s', package, filename, request.sid)
|
F.logger.debug('start package:%s filename:%s sid:%s', package, filename, request.sid)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
F.logger.error('Exception:%s', exception)
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
F.logger.error(traceback.format_exc())
|
F.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
@F.socketio.on('disconnect', namespace='/%s' % namespace)
|
@F.socketio.on('disconnect', namespace='/%s' % namespace)
|
||||||
@@ -33,8 +34,8 @@ def disconnect():
|
|||||||
try:
|
try:
|
||||||
LogViewer.instance().disconnect(request.sid)
|
LogViewer.instance().disconnect(request.sid)
|
||||||
F.logger.debug('disconnect sid:%s', request.sid)
|
F.logger.debug('disconnect sid:%s', request.sid)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
F.logger.error('Exception:%s', exception)
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
F.logger.error(traceback.format_exc())
|
F.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
@@ -62,18 +63,17 @@ class WatchThread(threading.Thread):
|
|||||||
key = 'filename'
|
key = 'filename'
|
||||||
value = self.filename
|
value = self.filename
|
||||||
if os.path.exists(logfile):
|
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)
|
f.seek(0, os.SEEK_END)
|
||||||
while not self.stop_flag:
|
while not self.stop_flag:
|
||||||
line = f.readline()
|
line = f.readline()
|
||||||
if not line:
|
if not line:
|
||||||
time.sleep(0.1) # Sleep briefly
|
time.sleep(0.1) # Sleep briefly
|
||||||
continue
|
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)
|
F.logger.debug('WatchThread.. End %s', value)
|
||||||
else:
|
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):
|
class LogViewer(SingletonClass):
|
||||||
|
|||||||
1
lib/framework/logger.py
Normal file
1
lib/framework/logger.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from support import get_logger
|
||||||
@@ -49,8 +49,8 @@ class Scheduler(object):
|
|||||||
if flag_exit:
|
if flag_exit:
|
||||||
self.remove_job("scheduler_check")
|
self.remove_job("scheduler_check")
|
||||||
#time.sleep(30)
|
#time.sleep(30)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
self.logger.error('Exception:%s', exception)
|
self.logger.error(f"Exception:{str(e)}")
|
||||||
self.logger.error(traceback.format_exc())
|
self.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
@@ -233,21 +233,21 @@ class Job(object):
|
|||||||
if self.args is None:
|
if self.args is None:
|
||||||
self.thread = threading.Thread(target=self.target_function, args=())
|
self.thread = threading.Thread(target=self.target_function, args=())
|
||||||
else:
|
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.daemon = True
|
||||||
self.thread.start()
|
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()
|
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.end_time = datetime.now(timezone('Asia/Seoul'))
|
||||||
self.running_timedelta = self.end_time - self.start_time
|
self.running_timedelta = self.end_time - self.start_time
|
||||||
self.status = 'success'
|
self.status = 'success'
|
||||||
if not F.scheduler.is_include(self.job_id):
|
if not F.scheduler.is_include(self.job_id):
|
||||||
F.scheduler.remove_job_instance(self.job_id)
|
F.scheduler.remove_job_instance(self.job_id)
|
||||||
self.count += 1
|
self.count += 1
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
self.status = 'exception'
|
self.status = 'exception'
|
||||||
F.logger.error('Exception:%s', exception)
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
F.logger.error(traceback.format_exc())
|
F.logger.error(traceback.format_exc())
|
||||||
finally:
|
finally:
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
|
|||||||
BIN
lib/framework/static/.DS_Store
vendored
Normal file
BIN
lib/framework/static/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -106,3 +106,30 @@ background-color: #ffff0080 !important;
|
|||||||
.dropdown-menu {
|
.dropdown-menu {
|
||||||
margin:-2px;
|
margin:-2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal { overflow: scroll !important; }
|
||||||
|
|
||||||
|
/* Mobile Navigation Tightening */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
#menu_module_div {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
#menu_module_div .nav-pills {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
#menu_page_div .nav-pills {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
box-shadow: 0 .125rem .25rem rgba(0,0,0,.075) !important;
|
||||||
|
}
|
||||||
|
#main_container {
|
||||||
|
padding-top: 0 !important;
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
#main_container > .d-inline-block {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
160
lib/framework/static/css/showdown.css
Normal file
160
lib/framework/static/css/showdown.css
Normal 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;
|
||||||
|
}
|
||||||
9
lib/framework/static/img/loader.svg
Normal file
9
lib/framework/static/img/loader.svg
Normal 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 |
3845
lib/framework/static/js/chartjs-utils.js
Normal file
3845
lib/framework/static/js/chartjs-utils.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ if (tmp.length == 2) {
|
|||||||
var PACKAGE_NAME = tmp[1];
|
var PACKAGE_NAME = tmp[1];
|
||||||
var MODULE_NAME = tmp[2];
|
var MODULE_NAME = tmp[2];
|
||||||
var PAGE_NAME = "";
|
var PAGE_NAME = "";
|
||||||
} else if (tmp.length == 4){
|
} else if (tmp.length > 3){
|
||||||
var PACKAGE_NAME = tmp[1];
|
var PACKAGE_NAME = tmp[1];
|
||||||
var MODULE_NAME = tmp[2];
|
var MODULE_NAME = tmp[2];
|
||||||
var PAGE_NAME = tmp[3];
|
var PAGE_NAME = tmp[3];
|
||||||
@@ -23,8 +23,6 @@ $(window).on("load resize", function (event) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$('#command_modal').on('show.bs.modal', 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);
|
data = JSON.stringify(data, null, 2);
|
||||||
}
|
}
|
||||||
document.getElementById("modal_body").innerHTML = '<pre style="white-space: pre-wrap;">' +data + '</pre>';
|
document.getElementById("modal_body").innerHTML = '<pre style="white-space: pre-wrap;">' +data + '</pre>';
|
||||||
|
|
||||||
$("#large_modal").modal();
|
$("#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;
|
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -15,11 +15,10 @@ $(document).ready(function(){
|
|||||||
|
|
||||||
var protocol = window.location.protocol;
|
var protocol = window.location.protocol;
|
||||||
var frameSocket = io.connect(protocol + "//" + document.domain + ":" + location.port + "/framework");
|
var frameSocket = io.connect(protocol + "//" + document.domain + ":" + location.port + "/framework");
|
||||||
console.log(frameSocket);
|
|
||||||
|
|
||||||
frameSocket.on('notify', function(data){
|
frameSocket.on('notify', function(data){
|
||||||
$.notify({
|
$.notify({
|
||||||
message : data['msg'],
|
message : '<strong>' + data['msg'] + '</strong>',
|
||||||
url: data['url'],
|
url: data['url'],
|
||||||
target: '_self'
|
target: '_self'
|
||||||
},{
|
},{
|
||||||
@@ -29,7 +28,7 @@ frameSocket.on('notify', function(data){
|
|||||||
});
|
});
|
||||||
|
|
||||||
frameSocket.on('modal', 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){
|
frameSocket.on('loading_hide', function(data){
|
||||||
@@ -37,14 +36,12 @@ frameSocket.on('loading_hide', function(data){
|
|||||||
});
|
});
|
||||||
|
|
||||||
frameSocket.on('refresh', function(data){
|
frameSocket.on('refresh', function(data){
|
||||||
console.log('data')
|
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
$('#command_modal').on('hide.bs.modal', function (e) {
|
$('#command_modal').on('hide.bs.modal', function (e) {
|
||||||
//e.preventDefault(); 있으면 동작 안함.
|
//e.preventDefault(); 있으면 동작 안함.
|
||||||
console.log("ff global command_modal hide.bs.modal CATCH")
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: `/global/ajax/command_modal_hide`,
|
url: `/global/ajax/command_modal_hide`,
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
@@ -74,13 +71,27 @@ $("body").on('click', '#globalLinkBtn', function(e) {
|
|||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("body").on('click', '#globalReloadBtn', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
// global_link_btn 모두 찾아 변경
|
// global_link_btn 모두 찾아 변경
|
||||||
|
|
||||||
$("body").on('click', '#globalSettingSaveBtn', function(e){
|
$("body").on('click', '#globalSettingSaveBtn', function(e){
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
globalSettingSave();
|
if (globalSettingSaveBefore()) {
|
||||||
|
globalSettingSave();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function globalSettingSaveBefore() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function globalSettingSaveAfter() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function globalSettingSave() {
|
function globalSettingSave() {
|
||||||
var formData = getFormdata('#setting');
|
var formData = getFormdata('#setting');
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@@ -94,6 +105,7 @@ function globalSettingSave() {
|
|||||||
$.notify('<strong>설정을 저장하였습니다.</strong>', {
|
$.notify('<strong>설정을 저장하였습니다.</strong>', {
|
||||||
type: 'success'
|
type: 'success'
|
||||||
});
|
});
|
||||||
|
globalSettingSaveAfter();
|
||||||
} else {
|
} else {
|
||||||
$.notify('<strong>설정 저장에 실패하였습니다.</strong>', {
|
$.notify('<strong>설정 저장에 실패하였습니다.</strong>', {
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
@@ -106,7 +118,10 @@ function globalSettingSave() {
|
|||||||
$("body").on('click', '#globalEditBtn', function(e) {
|
$("body").on('click', '#globalEditBtn', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
file = $(this).data('file');
|
file = $(this).data('file');
|
||||||
console.log(file);
|
if (file == null) {
|
||||||
|
var tag = $(this).data('tag');
|
||||||
|
file = $('#' + tag).val();
|
||||||
|
}
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/global/ajax/is_available_edit',
|
url: '/global/ajax/is_available_edit',
|
||||||
type: "POST",
|
type: "POST",
|
||||||
@@ -236,107 +251,188 @@ $("body").on('click', '#globalImmediatelyExecutePageBtn', function(e){
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$("body").on('click', '#globalDbDeleteDayBtn', function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
var tag_id = $(this).data('tag_id');
|
||||||
|
var day = $('#' + tag_id).val();
|
||||||
|
globalConfirmModal('DB 삭제', "최근 " + day + "일 이내 데이터를 제외하고 삭제 하시겠습니까?", function() {
|
||||||
|
globalDbDelete(day);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$("body").on('click', '#globalDbDeleteBtn', function(e){
|
$("body").on('click', '#globalDbDeleteBtn', function(e){
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
document.getElementById("confirm_title").innerHTML = "DB 삭제";
|
globalConfirmModal('DB 삭제', "전체 목록을 삭제 하시겠습니까?", function() {
|
||||||
document.getElementById("confirm_body").innerHTML = "전체 목록을 삭제 하시겠습니까?";
|
globalDbDelete(0);
|
||||||
$('#confirm_button').attr('onclick', "globalDbDelete();");
|
});
|
||||||
$("#confirm_modal").modal();
|
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function globalDbDelete() {
|
function globalDbDelete(day) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/'+PACKAGE_NAME+'/ajax/' + MODULE_NAME + '/reset_db',
|
url: '/'+PACKAGE_NAME+'/ajax/' + MODULE_NAME + '/db_delete',
|
||||||
type: "POST",
|
type: "POST",
|
||||||
cache: false,
|
cache: false,
|
||||||
data: {},
|
data: {day:day},
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
if (data) {
|
if (data == -1) {
|
||||||
$.notify('<strong>삭제하였습니다.</strong>', {
|
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$.notify('<strong>삭제에 실패하였습니다.</strong>',{
|
$.notify('<strong>삭제에 실패하였습니다.</strong>',{
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
$.notify('<strong>'+data+'개를 삭제하였습니다.</strong>', {
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
globalRequestSearch('1');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////
|
||||||
|
|
||||||
|
$("body").on('click', '#globalDbDeleteDayPageBtn', function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
var tag_id = $(this).data('tag_id');
|
||||||
|
var day = $('#' + tag_id).val();
|
||||||
|
globalConfirmModal('DB 삭제', day + "일 제외 목록을 삭제 하시겠습니까?", function() {
|
||||||
|
globalDbDeletePage(day);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
$("body").on('click', '#globalDbDeletePageBtn', function(e){
|
$("body").on('click', '#globalDbDeletePageBtn', function(e){
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
document.getElementById("confirm_title").innerHTML = "DB 삭제";
|
globalConfirmModal('DB 삭제', "최근 " + day + "일 이내 데이터를 제외하고 삭제 하시겠습니까?", function() {
|
||||||
document.getElementById("confirm_body").innerHTML = "전체 목록을 삭제 하시겠습니까?";
|
globalDbDeletePage(0);
|
||||||
$('#confirm_button').attr('onclick', "globalDbDeletePage();");
|
});
|
||||||
$("#confirm_modal").modal();
|
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function globalDbDeletePage() {
|
function globalDbDeletePage(day) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/'+PACKAGE_NAME+'/ajax/' + MODULE_NAME + '/' + PAGE_NAME + '/reset_db',
|
url: '/'+PACKAGE_NAME+'/ajax/' + MODULE_NAME + '/' + PAGE_NAME + '/reset_db',
|
||||||
type: "POST",
|
type: "POST",
|
||||||
cache: false,
|
cache: false,
|
||||||
data: {sub:sub},
|
data: {day:day},
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
if (data) {
|
if (data == -1) {
|
||||||
$.notify('<strong>삭제하였습니다.</strong>', {
|
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$.notify('<strong>삭제에 실패하였습니다.</strong>',{
|
$.notify('<strong>삭제에 실패하였습니다.</strong>',{
|
||||||
type: 'warning'
|
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 - 함수
|
// Global - 함수
|
||||||
///////////////////////////////////////
|
///////////////////////////////////////
|
||||||
|
|
||||||
function globalSendCommand(command, arg1, arg2, arg3, modal_title, callback) {
|
function globalSendCommand(command, arg1, arg2, arg3, callback) {
|
||||||
console.log("globalSendCommand [" + command + '] [' + arg1 + '] [' + arg2 + '] [' + arg3 + '] [' + modal_title + '] [' + callback + ']');
|
var url = '/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/command';
|
||||||
console.log('/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/command');
|
return globalSendCommandByUrl(url, command, arg1, arg2, arg3, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function globalSendCommandByUrl(url, command, arg1, arg2, arg3, callback) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/command',
|
url: url,
|
||||||
type: "POST",
|
type: "POST",
|
||||||
cache: false,
|
cache: false,
|
||||||
data:{command:command, arg1:arg1, arg2:arg2, arg3},
|
data:{command:command, arg1:arg1, arg2:arg2, arg3},
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (ret) {
|
success: function (ret) {
|
||||||
|
console.log(ret)
|
||||||
if (ret.msg != null) notify(ret.msg, ret.ret);
|
if (ret.msg != null) notify(ret.msg, ret.ret);
|
||||||
if (ret.modal != null) showModal(ret.modal, modal_title, false);
|
if (ret.modal != null) showModal(ret.modal, ret.title, false);
|
||||||
if (ret.json != null) showModal(ret.json, modal_title, true);
|
if (ret.json != null) showModal(ret.json, ret.title, true);
|
||||||
if (callback != null) callback(ret);
|
if (callback != null) callback(ret);
|
||||||
|
if (ret.reload) location.reload();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function globalSendCommandPage(command, arg1, arg2, arg3, modal_title, callback) {
|
function globalSendCommandPage(command, arg1, arg2, arg3, callback) {
|
||||||
console.log("globalSendCommandPage [" + command + '] [' + arg1 + '] [' + arg2 + '] [' + arg3 + '] [' + modal_title + '] [' + callback + ']');
|
var url = '/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/' + PAGE_NAME + '/command';
|
||||||
console.log('/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/command');
|
return globalSendCommandPageByUrl(url, command, arg1, arg2, arg3, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function globalSendCommandPageByUrl(url, command, arg1, arg2, arg3, callback) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/' + PAGE_NAME + '/command',
|
url: url,
|
||||||
type: "POST",
|
type: "POST",
|
||||||
cache: false,
|
cache: false,
|
||||||
data:{command:command, arg1:arg1, arg2:arg2, arg3},
|
data:{command:command, arg1:arg1, arg2:arg2, arg3},
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (ret) {
|
success: function (ret) {
|
||||||
if (ret.msg != null) notify(ret.msg, ret.ret);
|
if (ret.msg != null) notify(ret.msg, ret.ret);
|
||||||
if (ret.modal != null) m_modal(ret.modal, modal_title, false);
|
if (ret.modal != null) showModal(ret.modal, ret.title, false);
|
||||||
if (ret.json != null) m_modal(ret.json, modal_title, true);
|
if (ret.json != null) showModal(ret.json, ret.title, true);
|
||||||
if (callback != null) callback(ret);
|
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">»</button>'
|
str += '<button id="gloablSearchPageBtn" data-page="' + (data.last_page+1) + '" type="button" class="btn btn-secondary">»</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> \
|
str += '</div> \
|
||||||
</div> \
|
</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'
|
dataType: 'json'
|
||||||
}).done((datas) => {
|
}).done((datas) => {
|
||||||
console.log(datas)
|
|
||||||
if (datas.length == 0) {
|
if (datas.length == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -510,8 +625,6 @@ let listdir = (path = '/', only_dir = true) => {
|
|||||||
} else {
|
} else {
|
||||||
//new_path = (path !== path_spliter) ? path + path_spliter + $(evt.currentTarget).text() : path + $(evt.currentTarget).text();
|
//new_path = (path !== path_spliter) ? path + path_spliter + $(evt.currentTarget).text() : path + $(evt.currentTarget).text();
|
||||||
new_path = $(evt.currentTarget).data('value');
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
14
lib/framework/static/js/ff_global_plugin.js
Normal file
14
lib/framework/static/js/ff_global_plugin.js
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -10,12 +10,13 @@ function j_button_group(h) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// primary, secondary, success, danger, warning, info, light, dark, white
|
// 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';
|
var str = '<button id="'+id+'" name="'+id+'" class="btn btn-sm btn';
|
||||||
if (outline) {
|
if (outline) {
|
||||||
str += '-outline';
|
str += '-outline';
|
||||||
}
|
}
|
||||||
str += '-' + color+'';
|
str += '-' + color+'';
|
||||||
|
str += ' ' + _class;
|
||||||
if (small) {
|
if (small) {
|
||||||
str += ' py-0" style="font-size: 0.8em;"';
|
str += ' py-0" style="font-size: 0.8em;"';
|
||||||
} else {
|
} else {
|
||||||
@@ -35,9 +36,14 @@ function j_button_small(id, text, data={}, color='primary', outline=true) {
|
|||||||
|
|
||||||
|
|
||||||
function j_row_start(padding='10', align='center') {
|
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;
|
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') {
|
function j_col(w, h, align='left') {
|
||||||
var str = '<div class="col-sm-' + w + ' " style="text-align: '+align+'; word-break:break-all;">';
|
var str = '<div class="col-sm-' + w + ' " style="text-align: '+align+'; word-break:break-all;">';
|
||||||
str += h;
|
str += h;
|
||||||
@@ -45,6 +51,13 @@ function j_col(w, h, align='left') {
|
|||||||
return str;
|
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') {
|
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;">';
|
var str = '<div class="col-sm-' + w + ' " style="padding:0px; margin:0px; text-align: '+align+'; word-break:break-all;">';
|
||||||
str += h;
|
str += h;
|
||||||
@@ -87,57 +100,101 @@ function j_row_info(left, right, l=2, r=8) {
|
|||||||
function j_progress(id, width, label) {
|
function j_progress(id, width, label) {
|
||||||
var str = '';
|
var str = '';
|
||||||
str += '<div class="progress" style="height: 25px;">'
|
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+'" class="progress-bar bg-success" style="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+'_label" class="justify-content-center d-flex w-100 position-absolute" style="margin-top:2px">'+label+'</div>';
|
||||||
str += '</div>'
|
str += '</div>'
|
||||||
return str;
|
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 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();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function make_log(key, value, left=2, right=10) {
|
|
||||||
row = m_col(left, key, aligh='right');
|
|
||||||
row += m_col(right, value, aligh='left');
|
|
||||||
return row;
|
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에서 화면 생성
|
// javascript에서 화면 생성
|
||||||
function text_color(text, color='red') {
|
function text_color(text, color='red') {
|
||||||
return '<span style="color:'+color+'; font-weight:bold">' + text + '</span>';
|
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') {
|
function m_row_start_top(padding='10') {
|
||||||
return m_row_start(padding, 'top');
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
35
lib/framework/static/js/showdown-prettify.js
Normal file
35
lib/framework/static/js/showdown-prettify.js
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}());
|
||||||
3
lib/framework/static/js/showdown_2.1.0.js
Normal file
3
lib/framework/static/js/showdown_2.1.0.js
Normal file
File diff suppressed because one or more lines are too long
@@ -35,6 +35,12 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/gh/gitbrent/bootstrap4-toggle@3.4.0/js/bootstrap4-toggle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/gh/gitbrent/bootstrap4-toggle@3.4.0/js/bootstrap4-toggle.min.js"></script>
|
||||||
<!-- end 토글 -->
|
<!-- 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>
|
</head>
|
||||||
|
|
||||||
<body class="body ">
|
<body class="body ">
|
||||||
@@ -50,7 +56,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<div class="loading" id="loading">
|
<div class="loading" id="loading">
|
||||||
<img src="/static/img/loading.gif" />
|
<!-- <img src="/static/img/loading.gif" /> -->
|
||||||
|
<img src="/static/img/loader.svg" />
|
||||||
</div>
|
</div>
|
||||||
{{ modals() }}
|
{{ modals() }}
|
||||||
</body>
|
</body>
|
||||||
@@ -59,3 +66,5 @@
|
|||||||
<!-- 글로벌 버튼이 모두 나오고 처리-->
|
<!-- 글로벌 버튼이 모두 나오고 처리-->
|
||||||
<script src="{{ url_for('static', filename='js/sjva_global1.js') }}"></script>
|
<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_global1.js') }}"></script>
|
||||||
|
|
||||||
|
<script src="{{ url_for('static', filename='js/ff_global_plugin.js') }}"></script>
|
||||||
|
|||||||
@@ -1,81 +1,208 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div>
|
<style>
|
||||||
<nav>
|
/* Unified Log Page Design (matches gds_dviewer) */
|
||||||
{{ macros.m_tab_head_start() }}
|
.log-wrapper {
|
||||||
{{ macros.m_tab_head('old', '이전', true) }}
|
max-width: 1400px;
|
||||||
{{ macros.m_tab_head('new', '실시간', false) }}
|
margin: 0 auto;
|
||||||
{{ macros.m_tab_head_end() }}
|
padding: 20px;
|
||||||
</nav>
|
}
|
||||||
<div class="tab-content" id="nav-tabContent">
|
|
||||||
{{ macros.m_tab_content_start('이전', 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) }}
|
.log-card {
|
||||||
<div>
|
background: linear-gradient(145deg, rgba(20, 30, 48, 0.95), rgba(36, 59, 85, 0.9));
|
||||||
<textarea id="add" class="col-md-12" rows="30" charswidth="23" disabled style="background-color:#ffffff;visibility:visible"></textarea>
|
border: 1px solid rgba(100, 150, 180, 0.25);
|
||||||
</div>
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
<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>
|
.log-tabs {
|
||||||
|
border-bottom: 1px solid rgba(100, 150, 180, 0.2);
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
padding: 10px 10px 0 10px;
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-tab {
|
||||||
|
color: #94a3b8;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-tab:hover {
|
||||||
|
color: #e2e8f0;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-tab.active {
|
||||||
|
color: #7dd3fc;
|
||||||
|
background: rgba(20, 30, 48, 0.95);
|
||||||
|
border-bottom: 2px solid #7dd3fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-content.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-container {
|
||||||
|
height: calc(100vh - 200px);
|
||||||
|
min-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 16px;
|
||||||
|
font-family: 'SF Mono', 'Consolas', 'Monaco', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-line-error { color: #f87171; }
|
||||||
|
.log-line-warning { color: #fbbf24; }
|
||||||
|
.log-line-info { color: #5eead4; }
|
||||||
|
.log-line-debug { color: #94a3b8; }
|
||||||
|
|
||||||
|
.controls-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 12px 20px;
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
border-top: 1px solid rgba(100, 150, 180, 0.2);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-log {
|
||||||
|
background: linear-gradient(180deg, rgba(45, 55, 72, 0.95), rgba(35, 45, 60, 0.98));
|
||||||
|
border: 1px solid rgba(100, 150, 180, 0.25);
|
||||||
|
color: #7dd3fc;
|
||||||
|
padding: 6px 14px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-log:hover {
|
||||||
|
background: linear-gradient(180deg, rgba(55, 65, 82, 0.95), rgba(45, 55, 70, 0.98));
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-switch {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-switch label {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.log-wrapper {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.log-container {
|
||||||
|
height: calc(100vh - 180px);
|
||||||
|
min-height: 300px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="log-wrapper">
|
||||||
|
<div class="log-card">
|
||||||
|
<div class="log-tabs">
|
||||||
|
<button class="log-tab active" data-tab="old">이전</button>
|
||||||
|
<button class="log-tab" data-tab="new">실시간</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="log-content active" id="tab-old">
|
||||||
|
<div class="log-container" id="log-history"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="log-content" id="tab-new">
|
||||||
|
<div class="log-container" id="log-realtime"></div>
|
||||||
|
<div class="controls-bar">
|
||||||
|
<div class="log-switch">
|
||||||
|
<label for="auto_scroll">자동 스크롤</label>
|
||||||
|
<input id="auto_scroll" name="auto_scroll" type="checkbox" checked>
|
||||||
|
</div>
|
||||||
|
<button id="clear" class="btn-log">리셋</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function() {
|
function escapeHtml(text) {
|
||||||
setWide();
|
var div = document.createElement('div');
|
||||||
$('#loading').show();
|
div.appendChild(document.createTextNode(text));
|
||||||
ResizeTextAreaLog()
|
return div.innerHTML;
|
||||||
})
|
|
||||||
|
|
||||||
function ResizeTextAreaLog() {
|
|
||||||
ClientHeight = window.innerHeight
|
|
||||||
$("#log").height(ClientHeight-240);
|
|
||||||
$("#add").height(ClientHeight-260);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$(window).resize(function() {
|
function formatLogLine(line) {
|
||||||
ResizeTextAreaLog();
|
var className = '';
|
||||||
|
if (line.indexOf('ERROR') !== -1) className = 'log-line-error';
|
||||||
|
else if (line.indexOf('WARNING') !== -1) className = 'log-line-warning';
|
||||||
|
else if (line.indexOf('INFO') !== -1) className = 'log-line-info';
|
||||||
|
else if (line.indexOf('DEBUG') !== -1) className = 'log-line-debug';
|
||||||
|
return '<div class="' + className + '">' + escapeHtml(line) + '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab switching
|
||||||
|
document.querySelectorAll('.log-tab').forEach(function(tab) {
|
||||||
|
tab.addEventListener('click', function() {
|
||||||
|
document.querySelectorAll('.log-tab').forEach(function(t) { t.classList.remove('active'); });
|
||||||
|
document.querySelectorAll('.log-content').forEach(function(c) { c.classList.remove('active'); });
|
||||||
|
this.classList.add('active');
|
||||||
|
document.getElementById('tab-' + this.dataset.tab).classList.add('active');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
setWide();
|
||||||
|
$('#loading').show();
|
||||||
|
});
|
||||||
|
|
||||||
var protocol = window.location.protocol;
|
var protocol = window.location.protocol;
|
||||||
var socket = io.connect(protocol + "//" + document.domain + ":" + location.port + "/log");
|
var socket = io.connect(protocol + "//" + document.domain + ":" + location.port + "/log");
|
||||||
socket.emit("start", {'package':'{{package}}'} );
|
|
||||||
socket.on('on_start', function(data){
|
socket.emit("start", {'package':'{{package}}'});
|
||||||
document.getElementById("log").innerHTML += data.data;
|
|
||||||
document.getElementById("log").scrollTop = document.getElementById("log").scrollHeight;
|
socket.on('on_start', function(data) {
|
||||||
document.getElementById("log").style.visibility = 'visible';
|
var container = document.getElementById("log-history");
|
||||||
$('#loading').hide();
|
var lines = data.data.split('\n');
|
||||||
|
var html = '';
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
html += formatLogLine(lines[i]);
|
||||||
|
}
|
||||||
|
container.innerHTML = html || '<div style="text-align:center;color:#64748b;">로그가 비어 있습니다.</div>';
|
||||||
|
container.scrollTop = container.scrollHeight;
|
||||||
|
$('#loading').hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('add', function(data){
|
socket.on('add', function(data) {
|
||||||
if (data.package == "{{package}}") {
|
if (data.package == "{{package}}") {
|
||||||
var chk = $('#auto_scroll').is(":checked");
|
var chk = $('#auto_scroll').is(":checked");
|
||||||
document.getElementById("add").innerHTML += data.data;
|
var container = document.getElementById("log-realtime");
|
||||||
if (chk) document.getElementById("add").scrollTop = document.getElementById("add").scrollHeight;
|
container.innerHTML += formatLogLine(data.data);
|
||||||
}
|
if (chk) container.scrollTop = container.scrollHeight;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#clear").click(function(e) {
|
$("#clear").click(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
document.getElementById("add").innerHTML = '';
|
document.getElementById("log-realtime").innerHTML = '';
|
||||||
});
|
});
|
||||||
|
</script>
|
||||||
$("#auto_scroll").click(function(){
|
|
||||||
var chk = $(this).is(":checked");//.attr('checked');
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -25,11 +25,11 @@
|
|||||||
|
|
||||||
<!-- 탭 -->
|
<!-- 탭 -->
|
||||||
{% macro m_tab_head_start() %}
|
{% 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 %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro m_tab_head_end() %}
|
{% macro m_tab_head_end() %}
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro m_tab_head(name, title, active) %}
|
{% 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>
|
<a class="nav-item nav-link" id="tab_{{name}}" data-toggle="tab" href="#{{name}}" role="tab">{{title}}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
<!----------------------------------------------------------------->
|
|
||||||
|
|
||||||
|
|
||||||
<!-- SETTING -->
|
|
||||||
<!-- SETTING -->
|
|
||||||
<!-- SETTING -->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!------------------------------------------------------------------>
|
||||||
|
<!-- 설정 -->
|
||||||
|
|
||||||
<!-- SETTING 기본 틀-->
|
<!-- SETTING 기본 틀-->
|
||||||
{% macro setting_top(left='', padding='10') %}
|
{% macro setting_top(left='', padding='10') %}
|
||||||
@@ -57,6 +70,16 @@
|
|||||||
<div class='col-sm-9'>
|
<div class='col-sm-9'>
|
||||||
{% endmacro %}
|
{% 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') %}
|
{% macro setting_bottom(desc=None, padding_top='5') %}
|
||||||
{% if desc is not none %}
|
{% if desc is not none %}
|
||||||
<div style="padding-left:20px; padding-top:{{padding_top}}px;">
|
<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) }}
|
{{ setting_top(left) }}
|
||||||
<div class="input-group col-sm-3">
|
<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>
|
<input id="globalSchedulerSwitchBtn" name="globalSchedulerSwitchBtn" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
|
||||||
{% else %}
|
{% else %}
|
||||||
<input id="globalSchedulerSwitchBtn" name="globalSchedulerSwitchBtn" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
|
<input id="globalSchedulerSwitchBtn" name="globalSchedulerSwitchBtn" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
|
||||||
{% endif %}
|
{% 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>
|
<span style="padding-left:10px; padding-top: 8px; font-weight: bold;">실행중</span>
|
||||||
{% else %}
|
{% 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>
|
<span style="padding-left:10px; padding-top: 8px; ">대기중</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -268,13 +312,116 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setting_gole
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- NOT SETTING -->
|
<!-- SELECT Dummy
|
||||||
<!-- NOT SETTING -->
|
option을 script로 넣을 때 사용
|
||||||
<!-- NOT SETTING -->
|
예: 시스템 - 전체로그
|
||||||
|
-->
|
||||||
|
{% 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) %}
|
{% macro m_button_group(buttons) %}
|
||||||
<div class="btn-group btn-group-sm flex-wrap mr-2" role="group">
|
<div class="btn-group btn-group-sm flex-wrap mr-2" role="group">
|
||||||
@@ -304,6 +451,14 @@
|
|||||||
{{ setting_bottom(desc, padding_top='-5') }}
|
{{ setting_bottom(desc, padding_top='-5') }}
|
||||||
{% endmacro %}
|
{% 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) %}
|
{% macro info_text_go(id, left, value='', desc=None, padding=10) %}
|
||||||
{{ setting_top(left, padding) }}
|
{{ setting_top(left, padding) }}
|
||||||
<div style="padding-left:20px; padding-top:-5px;">
|
<div style="padding-left:20px; padding-top:-5px;">
|
||||||
@@ -354,219 +509,208 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- SELECT Dummy
|
|
||||||
option을 script로 넣을 때 사용
|
|
||||||
예: 시스템 - 전체로그
|
{% macro m_modal_start(id, title, size) %}
|
||||||
-->
|
<!-- Modal -->
|
||||||
{% macro setting_select_empty(id, title, col='9', desc=None, value=None) %}
|
<div class="modal fade" id="{{id}}" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
|
||||||
{{ setting_top(title) }}
|
<div class="modal-dialog {{size}}">
|
||||||
<div class="input-group col-sm-{{col}}">
|
<div class="modal-content">
|
||||||
<div id="{{id}}_div" name="{{id}}_div"></div>
|
|
||||||
</div>
|
<div class="modal-header">
|
||||||
{{ setting_bottom(desc) }}
|
<h4 class="modal-title" id="{{id}}_title">{{title}}</h4>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="{{id}}_modal_body" style="word-break:break-all;">
|
||||||
{% endmacro %}
|
{% 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" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- 삭제해야함 --------------------------------------------------------->
|
|
||||||
<!--
|
|
||||||
{% 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>
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
{{ setting_bottom(desc) }}
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Modal end -->
|
||||||
{% endmacro %}
|
{% 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 m_modal_end_with_button(buttons) %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% 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>
|
</div>
|
||||||
{{ setting_bottom(desc) }}
|
<div class="modal-footer">
|
||||||
{% endmacro %}
|
<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] %}
|
||||||
{% macro setting_checkbox(id, left, value, desc='') %}
|
data-{{d[0]}}="{{d[1]}}""
|
||||||
{{ setting_top(left) }}
|
{% endfor %}
|
||||||
<div class="input-group col-sm-3">
|
{% endif %}
|
||||||
{% if value == 'True' or value == 'true' or value == 'On' %}
|
>{{b[1]}}</button>
|
||||||
<input id="{{ id }}" name="{{ id }}" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
|
{% endfor %}
|
||||||
{% else %}
|
<button type="button" class="btn btn-sm btn-warning" data-dismiss="modal">닫기 (취소)</button>
|
||||||
<input id="{{ id }}" name="{{ id }}" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
|
</div>
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{{ setting_bottom(desc) }}
|
<div class="loading" id="modal_loading">
|
||||||
|
<img src="/static/img/loading.gif" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Modal end -->
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% 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='') %}
|
{% macro setting_default_checkbox(id, left, label, value, desc='') %}
|
||||||
@@ -584,31 +728,6 @@ option을 script로 넣을 때 사용
|
|||||||
{{ setting_bottom(desc) }}
|
{{ setting_bottom(desc) }}
|
||||||
{% endmacro %}
|
{% 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 -->
|
<!-- select -->
|
||||||
{% macro setting_select(id, title, options, col='9', desc=None, value=None) %}
|
{% macro setting_select(id, title, options, col='9', desc=None, value=None) %}
|
||||||
{{ setting_top(title) }}
|
{{ setting_top(title) }}
|
||||||
@@ -655,21 +794,6 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
|
|||||||
{{ setting_bottom(desc) }}
|
{{ setting_bottom(desc) }}
|
||||||
{% endmacro %}
|
{% 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 + 버튼 -->
|
<!-- select + 버튼 -->
|
||||||
@@ -703,15 +827,6 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!--progress-bar-striped progress-bar-animated-->
|
<!--progress-bar-striped progress-bar-animated-->
|
||||||
{% macro setting_progress(id, left='', desc='') %}
|
{% macro setting_progress(id, left='', desc='') %}
|
||||||
{{ setting_top(left) }}
|
{{ setting_top(left) }}
|
||||||
@@ -725,66 +840,6 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
|
|||||||
{% endmacro %}
|
{% 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 %}
|
{% 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) %}
|
{% macro m_button(id, text) %}
|
||||||
<button id="{{id}}" name="{{id}}" class="btn btn-sm btn-outline-primary">{{text}}</button>
|
<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">×</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">×</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') %}
|
{% macro row_start(padding='10') %}
|
||||||
<div class='row' style="padding-top: {{padding}}px; padding-bottom:{{padding}}px; align-items: center;">
|
<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 %}
|
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<div class="modal-body" id="modal_body" style="word-break:break-all;">
|
<div class="modal-body" id="modal_body" style="word-break:break-all;">
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<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>-->
|
<!--<button type="button" class="btn btn-primary">Save changes</button>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" id='select_local_file_modal_confirm_btn' class="btn btn-success" data-dismiss="modal">선택
|
<button type="button" id='select_local_file_modal_confirm_btn' class="btn btn-success" data-dismiss="modal">선택
|
||||||
</button>
|
</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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
{% if 'uri' in category and category['uri'].startswith('http') %}
|
{% 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>
|
<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 %}
|
{% 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 %}
|
{% else %}
|
||||||
<!--{{ category }}-->
|
<!--{{ category }}-->
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
@@ -134,10 +134,11 @@
|
|||||||
{% if current_menu[0] == plugin['uri'] and 'list' in plugin %}
|
{% if current_menu[0] == plugin['uri'] and 'list' in plugin %}
|
||||||
{% for module in plugin['list'] %}
|
{% for module in plugin['list'] %}
|
||||||
{% if module['uri'] == current_menu[1] and 'list' in module%}
|
{% if module['uri'] == current_menu[1] and 'list' in module%}
|
||||||
<!--{{ module }}-->
|
<!-- {{ module }} -->
|
||||||
<ul class="nav nav-pills bg-light shadow text-dark">
|
<ul class="nav nav-pills bg-light shadow text-dark">
|
||||||
{% for page in module['list'] %}
|
{% 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>
|
<li class="nav-item"><a class="nav-link active" href="/{{ current_menu[0] }}/{{ current_menu[1] }}/{{ page['uri'] }}">{{page['name']}}</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="nav-item"><a class="nav-link" href="/{{ current_menu[0] }}/{{ current_menu[1] }}/{{ page['uri'] }}">{{page['name']}}</a></li>
|
<li class="nav-item"><a class="nav-link" href="/{{ current_menu[0] }}/{{ current_menu[1] }}/{{ page['uri'] }}">{{page['name']}}</a></li>
|
||||||
|
|||||||
@@ -1,30 +1,32 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% filter markdown %}
|
<script src="https://cdn.jsdelivr.net/gh/google/code-prettify@master/loader/run_prettify.js?autorun=true&lang=css&lang=python&skin=sunburst"></script>
|
||||||
{{ data }}
|
|
||||||
{% endfilter %}
|
|
||||||
|
|
||||||
|
|
||||||
<style type="text/css">
|
<script src="{{ url_for('static', filename='js/showdown_2.1.0.js') }}"></script>
|
||||||
img{
|
<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">
|
<script type="text/javascript">
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
//$('#main_container').attr('class', 'container-fluid');
|
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>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
30
lib/framework/templates/manual_old.html
Normal file
30
lib/framework/templates/manual_old.html
Normal 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 %}
|
||||||
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
|
||||||
|
<html>
|
||||||
<title>{{data['play_title']}}</title>
|
<title>{{data['play_title']}}</title>
|
||||||
<script src="https://vjs.zencdn.net/7.11.4/video.min.js"></script>
|
<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" />
|
<link href="https://vjs.zencdn.net/7.11.4/video-js.css" rel="stylesheet" />
|
||||||
@@ -63,3 +65,4 @@ player.ready(function(){
|
|||||||
|
|
||||||
player.play();
|
player.play();
|
||||||
</script>
|
</script>
|
||||||
|
</html>
|
||||||
|
|||||||
90
lib/framework/templates/videojs_discord.html
Normal file
90
lib/framework/templates/videojs_discord.html
Normal 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(/&/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>
|
||||||
@@ -34,8 +34,8 @@ class Util(object):
|
|||||||
paging['count'] = count
|
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
|
return paging
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
F.logger.debug('Exception:%s', exception)
|
F.logger.debug(f"Exception:{str(e)}")
|
||||||
F.logger.debug(traceback.format_exc())
|
F.logger.debug(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
@@ -60,8 +60,8 @@ class Util(object):
|
|||||||
ret['dirname'] = max_filename.replace('/%s' % ret['filename'], '')
|
ret['dirname'] = max_filename.replace('/%s' % ret['filename'], '')
|
||||||
ret['max_size'] = max_size
|
ret['max_size'] = max_size
|
||||||
return ret
|
return ret
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
F.logger.error('Exception:%s', exception)
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
F.logger.error(traceback.format_exc())
|
F.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
@@ -81,8 +81,8 @@ class Util(object):
|
|||||||
import shutil
|
import shutil
|
||||||
shutil.rmtree(zip_path)
|
shutil.rmtree(zip_path)
|
||||||
return True
|
return True
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
F.logger.error('Exception:%s', exception)
|
F.logger.error(f"Exception:{str(e)}")
|
||||||
F.logger.error(traceback.format_exc())
|
F.logger.error(traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -92,12 +92,12 @@ class Util(object):
|
|||||||
def make_apikey(url):
|
def make_apikey(url):
|
||||||
from framework import SystemModelSetting
|
from framework import SystemModelSetting
|
||||||
url = url.format(ddns=SystemModelSetting.get('ddns'))
|
url = url.format(ddns=SystemModelSetting.get('ddns'))
|
||||||
if SystemModelSetting.get_bool('auth_use_apikey'):
|
if SystemModelSetting.get_bool('use_apikey'):
|
||||||
if url.find('?') == -1:
|
if url.find('?') == -1:
|
||||||
url += '?'
|
url += '?'
|
||||||
else:
|
else:
|
||||||
url += '&'
|
url += '&'
|
||||||
url += 'apikey=%s' % SystemModelSetting.get('auth_apikey')
|
url += 'apikey=%s' % SystemModelSetting.get('apikey')
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
VERSION="4.0.47"
|
VERSION="4.1.41"
|
||||||
|
|||||||
@@ -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 datetime import datetime, timedelta
|
||||||
from flask import Blueprint, render_template, jsonify, redirect, request
|
from flask import Blueprint, render_template, jsonify, redirect, request
|
||||||
from sqlalchemy import desc, or_
|
from sqlalchemy import desc, or_
|
||||||
|
|||||||
@@ -94,8 +94,8 @@ class FfmpegQueue(object):
|
|||||||
self.download_thread = threading.Thread(target=self.download_thread_function, args=())
|
self.download_thread = threading.Thread(target=self.download_thread_function, args=())
|
||||||
self.download_thread.daemon = True
|
self.download_thread.daemon = True
|
||||||
self.download_thread.start()
|
self.download_thread.start()
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
self.P.logger.error('Exception:%s', exception)
|
self.P.logger.error(f"Exception:{str(e)}")
|
||||||
self.P.logger.error(traceback.format_exc())
|
self.P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
@@ -107,8 +107,8 @@ class FfmpegQueue(object):
|
|||||||
if self.current_ffmpeg_count < self.max_ffmpeg_count:
|
if self.current_ffmpeg_count < self.max_ffmpeg_count:
|
||||||
break
|
break
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
self.P.logger.error('Exception:%s', exception)
|
self.P.logger.error(f"Exception:{str(e)}")
|
||||||
self.P.logger.error(traceback.format_exc())
|
self.P.logger.error(traceback.format_exc())
|
||||||
self.P.logger.error('current_ffmpeg_count : %s', self.current_ffmpeg_count)
|
self.P.logger.error('current_ffmpeg_count : %s', self.current_ffmpeg_count)
|
||||||
self.P.logger.error('max_ffmpeg_count : %s', self.max_ffmpeg_count)
|
self.P.logger.error('max_ffmpeg_count : %s', self.max_ffmpeg_count)
|
||||||
@@ -153,8 +153,8 @@ class FfmpegQueue(object):
|
|||||||
f.start()
|
f.start()
|
||||||
self.current_ffmpeg_count += 1
|
self.current_ffmpeg_count += 1
|
||||||
self.download_queue.task_done()
|
self.download_queue.task_done()
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
self.P.logger.error('Exception:%s', exception)
|
self.P.logger.error(f"Exception:{str(e)}")
|
||||||
self.P.logger.error(traceback.format_exc())
|
self.P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
def ffmpeg_listener(self, **arg):
|
def ffmpeg_listener(self, **arg):
|
||||||
@@ -203,8 +203,8 @@ class FfmpegQueue(object):
|
|||||||
self.entity_list.append(entity)
|
self.entity_list.append(entity)
|
||||||
self.download_queue.put(entity)
|
self.download_queue.put(entity)
|
||||||
return True
|
return True
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
self.P.logger.error('Exception:%s', exception)
|
self.P.logger.error(f"Exception:{str(e)}")
|
||||||
self.P.logger.error(traceback.format_exc())
|
self.P.logger.error(traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -270,8 +270,8 @@ class FfmpegQueue(object):
|
|||||||
self.entity_list = new_list
|
self.entity_list = new_list
|
||||||
ret['ret'] = 'refresh'
|
ret['ret'] = 'refresh'
|
||||||
return ret
|
return ret
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
self.P.logger.error('Exception:%s', exception)
|
self.P.logger.error(f"Exception:{str(e)}")
|
||||||
self.P.logger.error(traceback.format_exc())
|
self.P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ from support import SupportYaml
|
|||||||
from . import (Logic, default_route, default_route_single_module,
|
from . import (Logic, default_route, default_route_single_module,
|
||||||
get_model_setting)
|
get_model_setting)
|
||||||
|
|
||||||
|
from loguru import logger as logger1
|
||||||
|
|
||||||
|
|
||||||
class PluginBase(object):
|
class PluginBase(object):
|
||||||
package_name = None
|
package_name = None
|
||||||
@@ -23,6 +25,7 @@ class PluginBase(object):
|
|||||||
|
|
||||||
def __init__(self, setting):
|
def __init__(self, setting):
|
||||||
try:
|
try:
|
||||||
|
logger1.debug(f"[DEBUG] PluginBase init for {setting.get('filepath')}")
|
||||||
is_system = ('system' == os.path.basename(os.path.dirname(setting['filepath'])))
|
is_system = ('system' == os.path.basename(os.path.dirname(setting['filepath'])))
|
||||||
self.status = ""
|
self.status = ""
|
||||||
self.setting = setting
|
self.setting = setting
|
||||||
@@ -39,6 +42,7 @@ class PluginBase(object):
|
|||||||
self.logger = F.get_logger(self.package_name)
|
self.logger = F.get_logger(self.package_name)
|
||||||
self.blueprint = Blueprint(self.package_name, self.package_name, url_prefix=f'/{self.package_name}', template_folder=os.path.join(os.path.dirname(setting['filepath']), 'templates'), static_folder=os.path.join(os.path.dirname(setting['filepath']), 'static'))
|
self.blueprint = Blueprint(self.package_name, self.package_name, url_prefix=f'/{self.package_name}', template_folder=os.path.join(os.path.dirname(setting['filepath']), 'templates'), static_folder=os.path.join(os.path.dirname(setting['filepath']), 'static'))
|
||||||
self.menu = setting['menu']
|
self.menu = setting['menu']
|
||||||
|
logger1.debug(f"[DEBUG] Menu set for {self.package_name}: {self.menu}")
|
||||||
self.setting_menu = setting.get('setting_menu', None)
|
self.setting_menu = setting.get('setting_menu', None)
|
||||||
|
|
||||||
self.ModelSetting = None
|
self.ModelSetting = None
|
||||||
@@ -52,6 +56,7 @@ class PluginBase(object):
|
|||||||
self.home_module = setting.get('home_module')
|
self.home_module = setting.get('home_module')
|
||||||
self.status = "init_success"
|
self.status = "init_success"
|
||||||
self.config = {}
|
self.config = {}
|
||||||
|
self.recent_menu_plugin_except_list = setting.get('recent_menu_plugin_except_list', [])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f'Exception:{str(e)}')
|
self.logger.error(f'Exception:{str(e)}')
|
||||||
self.logger.error(traceback.format_exc())
|
self.logger.error(traceback.format_exc())
|
||||||
@@ -59,10 +64,16 @@ class PluginBase(object):
|
|||||||
|
|
||||||
def set_module_list(self, mod_list):
|
def set_module_list(self, mod_list):
|
||||||
try:
|
try:
|
||||||
|
# self.module_list = []
|
||||||
for mod in mod_list:
|
for mod in mod_list:
|
||||||
|
logger1.debug(mod)
|
||||||
mod_ins = mod(self)
|
mod_ins = mod(self)
|
||||||
|
# self.logger.debug(mod_ins)
|
||||||
|
logger1.debug(mod_ins)
|
||||||
self.module_list.append(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:
|
except Exception as e:
|
||||||
F.logger.error(f'[{self.package_name}] Exception:{str(e)}')
|
F.logger.error(f'[{self.package_name}] Exception:{str(e)}')
|
||||||
F.logger.error(traceback.format_exc())
|
F.logger.error(traceback.format_exc())
|
||||||
@@ -77,16 +88,31 @@ class PluginBase(object):
|
|||||||
|
|
||||||
|
|
||||||
def plugin_load(self):
|
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):
|
def plugin_unload(self):
|
||||||
self.logic.plugin_unload()
|
if self.logic:
|
||||||
|
self.logic.plugin_unload()
|
||||||
|
|
||||||
def get_first_manual_path(self):
|
def get_first_manual_path(self):
|
||||||
for __ in self.menu['list']:
|
for __ in self.menu['list']:
|
||||||
if __['uri'] == 'manual' and len(__['list']) > 0:
|
if __['uri'] == 'manual' and len(__['list']) > 0:
|
||||||
return __['list'][0]['uri']
|
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):
|
def create_plugin_instance(config):
|
||||||
ins = PluginBase(config)
|
ins = PluginBase(config)
|
||||||
|
|||||||
@@ -32,14 +32,39 @@ class Logic(object):
|
|||||||
if self.P.ModelSetting is not None:
|
if self.P.ModelSetting is not None:
|
||||||
for module in self.P.module_list:
|
for module in self.P.module_list:
|
||||||
key = f'{module.name}_auto_start'
|
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)
|
self.scheduler_start(module.name)
|
||||||
|
|
||||||
if module.page_list is not None:
|
if module.page_list is not None:
|
||||||
for page_instance in module.page_list:
|
for page_instance in module.page_list:
|
||||||
key = f'{module.name}_{page_instance.name}_auto_start'
|
key = f'{module.name}_{page_instance.name}_auto_start'
|
||||||
if self.P.ModelSetting.has_key(key) and self.P.ModelSetting.get_bool(key):
|
if self.P.ModelSetting.has_key(key) and self.P.ModelSetting.get_bool(key):
|
||||||
self.scheduler_start_sub(module.name, page_instance.name)
|
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):
|
def db_init(self):
|
||||||
try:
|
try:
|
||||||
@@ -88,7 +113,7 @@ class Logic(object):
|
|||||||
try:
|
try:
|
||||||
job_id = '%s_%s' % (self.P.package_name, module_name)
|
job_id = '%s_%s' % (self.P.package_name, module_name)
|
||||||
module = self.get_module(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)
|
F.scheduler.add_job_instance(job)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.P.logger.error(f'Exception:{str(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(f'Exception:{str(e)}')
|
||||||
self.P.logger.error(traceback.format_exc())
|
self.P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
def reset_db(self, module_name):
|
|
||||||
|
def db_delete(self, module_name, page_name, day):
|
||||||
try:
|
try:
|
||||||
module = self.get_module(module_name)
|
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:
|
except Exception as e:
|
||||||
self.P.logger.error(f'Exception:{str(e)}')
|
self.P.logger.error(f'Exception:{str(e)}')
|
||||||
self.P.logger.error(traceback.format_exc())
|
self.P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def one_execute(self, module_name):
|
def one_execute(self, module_name):
|
||||||
self.P.logger.debug('one_execute :%s', module_name)
|
self.P.logger.debug('one_execute :%s', module_name)
|
||||||
try:
|
try:
|
||||||
@@ -166,6 +201,7 @@ class Logic(object):
|
|||||||
self.P.logger.error(f'Exception:{str(e)}')
|
self.P.logger.error(f'Exception:{str(e)}')
|
||||||
self.P.logger.error(traceback.format_exc())
|
self.P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
"""
|
||||||
def process_telegram_data(self, data, target=None):
|
def process_telegram_data(self, data, target=None):
|
||||||
try:
|
try:
|
||||||
for module in self.P.module_list:
|
for module in self.P.module_list:
|
||||||
@@ -174,7 +210,7 @@ class Logic(object):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.P.logger.error(f'Exception:{str(e)}')
|
self.P.logger.error(f'Exception:{str(e)}')
|
||||||
self.P.logger.error(traceback.format_exc())
|
self.P.logger.error(traceback.format_exc())
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -303,12 +339,23 @@ class Logic(object):
|
|||||||
|
|
||||||
|
|
||||||
def arg_to_dict(self, arg):
|
def arg_to_dict(self, arg):
|
||||||
|
"""
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
tmp = urllib.parse.unquote(arg)
|
tmp = urllib.parse.unquote(arg)
|
||||||
tmps = tmp.split('&')
|
tmps = tmp.split('&')
|
||||||
ret = {}
|
ret = {}
|
||||||
for tmp in tmps:
|
for tmp in tmps:
|
||||||
_ = tmp.split('=')
|
_ = tmp.split('=', 1)
|
||||||
ret[_[0]] = _[1]
|
ret[_[0]] = _[1]
|
||||||
return ret
|
return ret
|
||||||
|
"""
|
||||||
|
import html
|
||||||
|
import urllib.parse
|
||||||
|
char = '||!||'
|
||||||
|
arg = arg.replace('&', 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
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ class PluginModuleBase(object):
|
|||||||
|
|
||||||
def get_page(self, page_name):
|
def get_page(self, page_name):
|
||||||
try:
|
try:
|
||||||
|
if self.page_list == None:
|
||||||
|
return
|
||||||
for page in self.page_list:
|
for page in self.page_list:
|
||||||
if page_name == page.name:
|
if page_name == page.name:
|
||||||
return page
|
return page
|
||||||
@@ -48,10 +50,22 @@ class PluginModuleBase(object):
|
|||||||
|
|
||||||
|
|
||||||
def process_menu(self, page, req):
|
def process_menu(self, page, req):
|
||||||
if self.page_list is not None:
|
from framework import F
|
||||||
page_ins = self.get_page(page)
|
|
||||||
if page_ins != None:
|
try:
|
||||||
return page_ins.process_menu(req)
|
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}")
|
return render_template('sample.html', title=f"PluginModuleBase-process_menu{self.P.package_name}/{self.name}/{page}")
|
||||||
|
|
||||||
def process_ajax(self, sub, req):
|
def process_ajax(self, sub, req):
|
||||||
@@ -69,12 +83,18 @@ class PluginModuleBase(object):
|
|||||||
def scheduler_function(self):
|
def scheduler_function(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def reset_db(self):
|
def db_delete(self, day):
|
||||||
pass
|
if self.web_list_model != None:
|
||||||
|
return self.web_list_model.delete_all(day)
|
||||||
|
|
||||||
|
|
||||||
def plugin_load(self):
|
def plugin_load(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def plugin_load_celery(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def plugin_unload(self):
|
def plugin_unload(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -115,12 +135,37 @@ class PluginModuleBase(object):
|
|||||||
pass
|
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()
|
arg = self.P.ModelSetting.to_dict()
|
||||||
return render_template(f'{self.P.package_name}_{self.parent.name}_{self.name}.html', arg=arg)
|
return render_template(f'{self.P.package_name}_{self.parent.name}_{self.name}.html', arg=arg)
|
||||||
except Exception as e:
|
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}")
|
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):
|
def plugin_load(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def plugin_load_celery(self):
|
||||||
|
pass
|
||||||
|
|
||||||
# logic
|
# logic
|
||||||
def plugin_unload(self):
|
def plugin_unload(self):
|
||||||
pass
|
pass
|
||||||
@@ -207,3 +256,41 @@ class PluginPageBase(object):
|
|||||||
pass
|
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
|
||||||
|
"""
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import sqlite3
|
||||||
import traceback
|
import traceback
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
@@ -45,7 +46,7 @@ class ModelBase(F.db.Model):
|
|||||||
paging['next_page'] = False
|
paging['next_page'] = False
|
||||||
paging['current_page'] = current_page
|
paging['current_page'] = current_page
|
||||||
paging['count'] = count
|
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
|
return paging
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
cls.P.logger.error(f'Exception:{str(e)}')
|
cls.P.logger.error(f'Exception:{str(e)}')
|
||||||
@@ -89,24 +90,30 @@ class ModelBase(F.db.Model):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete_all(cls, days=None):
|
def delete_all(cls, day=None):
|
||||||
|
count = -1
|
||||||
try:
|
try:
|
||||||
with F.app.app_context():
|
with F.app.app_context():
|
||||||
if days == None:
|
if day == None or day in [0, '0']:
|
||||||
F.db.session.query(cls).delete()
|
count = F.db.session.query(cls).delete()
|
||||||
F.db.session.commit()
|
|
||||||
else:
|
else:
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
ago = now - timedelta(days=int(days))
|
ago = now - timedelta(days=int(day))
|
||||||
#ago.hour = 0
|
#ago.hour = 0
|
||||||
#ago.minute = 0
|
#ago.minute = 0
|
||||||
count = F.db.session.query(cls).filter(cls.created_time > ago).delete()
|
count = F.db.session.query(cls).filter(cls.created_time < ago).delete()
|
||||||
cls.P.logger.info(f"delete_all {days=} {count=}")
|
cls.P.logger.info(f"delete_all {day=} {count=}")
|
||||||
return True
|
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:
|
except Exception as e:
|
||||||
cls.P.logger.error(f'Exception:{str(e)}')
|
cls.P.logger.error(f'Exception:{str(e)}')
|
||||||
cls.P.logger.error(traceback.format_exc())
|
cls.P.logger.error(traceback.format_exc())
|
||||||
return False
|
return count
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -135,12 +142,12 @@ class ModelBase(F.db.Model):
|
|||||||
if cls.P.ModelSetting is not None and cls.__tablename__ is not None:
|
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}')
|
cls.P.ModelSetting.set(f'{cls.__tablename__}_last_list_option', f'{order}|{page}|{search}|{option1}|{option2}')
|
||||||
except Exception as e:
|
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(traceback.format_exc())
|
||||||
F.logger.error(f'{cls.__tablename__}_last_list_option ERROR!' )
|
F.logger.error(f'{cls.__tablename__}_last_list_option ERROR!' )
|
||||||
return ret
|
return ret
|
||||||
except Exception as e:
|
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())
|
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'):
|
def make_query(cls, req, order='desc', search='', option1='all', option2='all'):
|
||||||
with F.app.app_context():
|
with F.app.app_context():
|
||||||
query = F.db.session.query(cls)
|
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
|
return query
|
||||||
|
|
||||||
|
|
||||||
@@ -171,3 +182,16 @@ class ModelBase(F.db.Model):
|
|||||||
query = query.filter(field.like('%'+search+'%'))
|
query = query.filter(field.like('%'+search+'%'))
|
||||||
#query = query1.union(query2)
|
#query = query1.union(query2)
|
||||||
return query
|
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
|
||||||
@@ -107,6 +107,8 @@ def get_model_setting(package_name, logger, table_name=None):
|
|||||||
if ModelSetting.get(key) != value:
|
if ModelSetting.get(key) != value:
|
||||||
change_list.append(key)
|
change_list.append(key)
|
||||||
entity = F.db.session.query(ModelSetting).filter_by(key=key).with_for_update().first()
|
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
|
entity.value = value
|
||||||
F.db.session.commit()
|
F.db.session.commit()
|
||||||
return True, change_list
|
return True, change_list
|
||||||
@@ -117,7 +119,8 @@ def get_model_setting(package_name, logger, table_name=None):
|
|||||||
return False, []
|
return False, []
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_list(key, delimeter='\n', comment=' #'):
|
def get_list(key, delimeter='\n', comment='#'):
|
||||||
|
value = None
|
||||||
try:
|
try:
|
||||||
value = ModelSetting.get(key).replace('\n', delimeter)
|
value = ModelSetting.get(key).replace('\n', delimeter)
|
||||||
if comment is None:
|
if comment is None:
|
||||||
|
|||||||
@@ -27,7 +27,12 @@ def default_route(P):
|
|||||||
def first_menu(sub):
|
def first_menu(sub):
|
||||||
try:
|
try:
|
||||||
if P.ModelSetting is not None and (P.package_name == 'system' and sub != 'home'):
|
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:
|
for module in P.module_list:
|
||||||
if sub == module.name:
|
if sub == module.name:
|
||||||
first_menu = module.get_first_menu()
|
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 redirect(f"/{P.package_name}/manual/{P.get_first_manual_path()}")
|
||||||
|
|
||||||
return render_template('sample.html', title='%s - %s' % (P.package_name, sub))
|
return render_template('sample.html', title='%s - %s' % (P.package_name, sub))
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
P.logger.error('Exception:%s', exception)
|
P.logger.error(f"Exception:{str(e)}")
|
||||||
P.logger.error(traceback.format_exc())
|
P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
@P.blueprint.route('/manual/<path:path>', methods=['GET', 'POST'])
|
@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('/'))
|
filepath = os.path.join(plugin_root, *path.split('/'))
|
||||||
from support import SupportFile
|
from support import SupportFile
|
||||||
data = SupportFile.read_file(filepath)
|
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)
|
return render_template('manual.html', data=data)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
P.logger.error('Exception:%s', exception)
|
P.logger.error(f"Exception:{str(e)}")
|
||||||
P.logger.error(traceback.format_exc())
|
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'])
|
@P.blueprint.route('/<sub>/<sub2>', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def second_menu(sub, sub2):
|
def second_menu(sub, sub2):
|
||||||
|
if sub2 == 'null':
|
||||||
|
return
|
||||||
if P.ModelSetting is not None:
|
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:
|
try:
|
||||||
for module in P.module_list:
|
for module in P.module_list:
|
||||||
if sub == module.name:
|
if sub == module.name:
|
||||||
@@ -74,8 +106,8 @@ def default_route(P):
|
|||||||
if sub == 'log':
|
if sub == 'log':
|
||||||
return render_template('log.html', package=P.package_name)
|
return render_template('log.html', package=P.package_name)
|
||||||
return render_template('sample.html', title='%s - %s' % (P.package_name, sub))
|
return render_template('sample.html', title='%s - %s' % (P.package_name, sub))
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
P.logger.error('Exception:%s', exception)
|
P.logger.error(f"Exception:{str(e)}")
|
||||||
P.logger.error(traceback.format_exc())
|
P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
#########################################################
|
#########################################################
|
||||||
@@ -84,7 +116,7 @@ def default_route(P):
|
|||||||
@P.blueprint.route('/ajax/<sub>', methods=['GET', 'POST'])
|
@P.blueprint.route('/ajax/<sub>', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def ajax(sub):
|
def ajax(sub):
|
||||||
P.logger.debug('AJAX %s %s', P.package_name, sub)
|
#P.logger.debug('AJAX %s %s', P.package_name, sub)
|
||||||
try:
|
try:
|
||||||
# global
|
# global
|
||||||
if sub == 'setting_save':
|
if sub == 'setting_save':
|
||||||
@@ -93,24 +125,26 @@ def default_route(P):
|
|||||||
module.setting_save_after(change_list)
|
module.setting_save_after(change_list)
|
||||||
return jsonify(ret)
|
return jsonify(ret)
|
||||||
|
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
P.logger.error('Exception:%s', exception)
|
P.logger.error(f"Exception:{str(e)}")
|
||||||
P.logger.error(traceback.format_exc())
|
P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
@P.blueprint.route('/ajax/<module_name>/<cmd>', methods=['GET', 'POST'])
|
@P.blueprint.route('/ajax/<module_name>/<cmd>', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def second_ajax(module_name, cmd):
|
def second_ajax(module_name, cmd):
|
||||||
|
# P.logger.debug(f"[CORE-DEBUG] second_ajax: package={P.package_name}, module={module_name}, cmd={cmd}")
|
||||||
try:
|
try:
|
||||||
for module in P.module_list:
|
for module in P.module_list:
|
||||||
if cmd == 'scheduler':
|
if cmd == 'scheduler':
|
||||||
go = request.form['scheduler']
|
go = request.form['scheduler']
|
||||||
if go == 'true':
|
if go == 'true':
|
||||||
P.logic.scheduler_start(module_name)
|
P.logic.scheduler_start(module_name)
|
||||||
else:
|
else:
|
||||||
P.logic.scheduler_stop(module_name)
|
P.logic.scheduler_stop(module_name)
|
||||||
return jsonify(go)
|
return jsonify(go)
|
||||||
elif cmd == 'reset_db':
|
elif cmd == 'db_delete':
|
||||||
ret = P.logic.reset_db(module_name)
|
day = request.form['day']
|
||||||
|
ret = P.logic.db_delete(module_name, None, day)
|
||||||
return jsonify(ret)
|
return jsonify(ret)
|
||||||
elif cmd == 'one_execute':
|
elif cmd == 'one_execute':
|
||||||
ret = P.logic.one_execute(module_name)
|
ret = P.logic.one_execute(module_name)
|
||||||
@@ -118,18 +152,25 @@ def default_route(P):
|
|||||||
elif cmd == 'immediately_execute':
|
elif cmd == 'immediately_execute':
|
||||||
ret = P.logic.immediately_execute(module_name)
|
ret = P.logic.immediately_execute(module_name)
|
||||||
return jsonify(ret)
|
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 module_name == module.name:
|
||||||
if cmd == 'command':
|
if cmd == 'command':
|
||||||
return module.process_command(request.form['command'], request.form.get('arg1'), request.form.get('arg2'), request.form.get('arg3'), request)
|
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:
|
else:
|
||||||
return module.process_ajax(cmd, request)
|
return module.process_ajax(cmd, request)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
P.logger.error('Exception:%s', exception)
|
P.logger.error(f"Exception:{str(e)}")
|
||||||
P.logger.error(traceback.format_exc())
|
P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
@P.blueprint.route('/ajax/<module_name>/<page_name>/<command>', methods=['GET', 'POST'])
|
@P.blueprint.route('/ajax/<module_name>/<page_name>/<command>', methods=['GET', 'POST'])
|
||||||
@@ -148,10 +189,10 @@ def default_route(P):
|
|||||||
else:
|
else:
|
||||||
P.logic.scheduler_stop_sub(module_name, page_name)
|
P.logic.scheduler_stop_sub(module_name, page_name)
|
||||||
return jsonify(go)
|
return jsonify(go)
|
||||||
#elif command == 'reset_db':
|
elif command == 'db_delete':
|
||||||
# sub = request.form['sub']
|
day = request.form['day']
|
||||||
# ret = P.logic.reset_db(sub)
|
ret = P.logic.db_delete(module_name, page_name, day)
|
||||||
# return jsonify(ret)
|
return jsonify(ret)
|
||||||
elif command == 'one_execute':
|
elif command == 'one_execute':
|
||||||
ret = P.logic.one_execute_sub(module_name, page_name)
|
ret = P.logic.one_execute_sub(module_name, page_name)
|
||||||
return jsonify(ret)
|
return jsonify(ret)
|
||||||
@@ -160,25 +201,30 @@ def default_route(P):
|
|||||||
return jsonify(ret)
|
return jsonify(ret)
|
||||||
elif command == 'command':
|
elif command == 'command':
|
||||||
return ins_page.process_command(request.form['command'], request.form.get('arg1'), request.form.get('arg2'), request.form.get('arg3'), request)
|
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:
|
else:
|
||||||
return ins_page.process_ajax(command, request)
|
return ins_page.process_ajax(command, request)
|
||||||
P.logger.error(f"not process ajax : {P.package_name} {module_name} {page_name} {command}")
|
P.logger.error(f"not process ajax : {P.package_name} {module_name} {page_name} {command}")
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
P.logger.error('Exception:%s', exception)
|
P.logger.error(f"Exception:{str(e)}")
|
||||||
P.logger.error(traceback.format_exc())
|
P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
#########################################################
|
#########################################################
|
||||||
# API - 외부
|
# API - 외부
|
||||||
#########################################################
|
#########################################################
|
||||||
# 단일 모듈인 경우 모듈이름을 붙이기 불편하여 추가.
|
# 단일 모듈인 경우 모듈이름을 붙이기 불편하여 추가.
|
||||||
@P.blueprint.route('/api/<sub2>', methods=['GET', 'POST'])
|
@P.blueprint.route('/api/<sub>', methods=['GET', 'POST'])
|
||||||
@F.check_api
|
@F.check_api
|
||||||
def api_first(sub2):
|
def api_first(sub):
|
||||||
try:
|
try:
|
||||||
for module in P.module_list:
|
return P.module_list[0].process_api(sub, request)
|
||||||
return module.process_api(sub2, request)
|
except Exception as e:
|
||||||
except Exception as exception:
|
P.logger.error(f"Exception:{str(e)}")
|
||||||
P.logger.error('Exception:%s', exception)
|
|
||||||
P.logger.error(traceback.format_exc())
|
P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
@P.blueprint.route('/api/<sub>/<sub2>', methods=['GET', 'POST'])
|
@P.blueprint.route('/api/<sub>/<sub2>', methods=['GET', 'POST'])
|
||||||
@@ -188,8 +234,16 @@ def default_route(P):
|
|||||||
for module in P.module_list:
|
for module in P.module_list:
|
||||||
if sub == module.name:
|
if sub == module.name:
|
||||||
return module.process_api(sub2, request)
|
return module.process_api(sub2, request)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
P.logger.error('Exception:%s', exception)
|
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.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
@P.blueprint.route('/normal/<sub>/<sub2>', methods=['GET', 'POST'])
|
@P.blueprint.route('/normal/<sub>/<sub2>', methods=['GET', 'POST'])
|
||||||
@@ -198,13 +252,13 @@ def default_route(P):
|
|||||||
for module in P.module_list:
|
for module in P.module_list:
|
||||||
if sub == module.name:
|
if sub == module.name:
|
||||||
return module.process_normal(sub2, request)
|
return module.process_normal(sub2, request)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
P.logger.error('Exception:%s', exception)
|
P.logger.error(f"Exception:{str(e)}")
|
||||||
P.logger.error(traceback.format_exc())
|
P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# default_route 끝
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -266,18 +320,14 @@ def default_route_single_module(P):
|
|||||||
else:
|
else:
|
||||||
P.logic.scheduler_stop(sub)
|
P.logic.scheduler_stop(sub)
|
||||||
return jsonify(go)
|
return jsonify(go)
|
||||||
elif sub == 'reset_db':
|
|
||||||
sub = request.form['sub']
|
|
||||||
ret = P.logic.reset_db(sub)
|
|
||||||
return jsonify(ret)
|
|
||||||
elif sub == 'one_execute':
|
elif sub == 'one_execute':
|
||||||
sub = request.form['sub']
|
sub = request.form['sub']
|
||||||
ret = P.logic.one_execute(sub)
|
ret = P.logic.one_execute(sub)
|
||||||
return jsonify(ret)
|
return jsonify(ret)
|
||||||
else:
|
else:
|
||||||
return P.module_list[0].process_ajax(sub, request)
|
return P.module_list[0].process_ajax(sub, request)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
P.logger.error('Exception:%s', exception)
|
P.logger.error(f"Exception:{str(e)}")
|
||||||
P.logger.error(traceback.format_exc())
|
P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
@P.blueprint.route('/api/<sub>', methods=['GET', 'POST'])
|
@P.blueprint.route('/api/<sub>', methods=['GET', 'POST'])
|
||||||
@@ -285,16 +335,16 @@ def default_route_single_module(P):
|
|||||||
def api(sub):
|
def api(sub):
|
||||||
try:
|
try:
|
||||||
return P.module_list[0].process_api(sub, request)
|
return P.module_list[0].process_api(sub, request)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
P.logger.error('Exception:%s', exception)
|
P.logger.error(f"Exception:{str(e)}")
|
||||||
P.logger.error(traceback.format_exc())
|
P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
@P.blueprint.route('/normal/<sub>', methods=['GET', 'POST'])
|
@P.blueprint.route('/normal/<sub>', methods=['GET', 'POST'])
|
||||||
def normal(sub):
|
def normal(sub):
|
||||||
try:
|
try:
|
||||||
return P.module_list[0].process_normal(sub, request)
|
return P.module_list[0].process_normal(sub, request)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
P.logger.error('Exception:%s', exception)
|
P.logger.error(f"Exception:{str(e)}")
|
||||||
P.logger.error(traceback.format_exc())
|
P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
@@ -330,14 +380,15 @@ def default_route_socketio_module(module, attach=''):
|
|||||||
module.socketio_list = []
|
module.socketio_list = []
|
||||||
|
|
||||||
@F.socketio.on('connect', namespace=f'/{P.package_name}/{module.name}{attach}')
|
@F.socketio.on('connect', namespace=f'/{P.package_name}/{module.name}{attach}')
|
||||||
|
@F.login_required
|
||||||
def connect():
|
def connect():
|
||||||
try:
|
try:
|
||||||
P.logger.debug(f'socket_connect : {P.package_name} - {module.name}{attach}')
|
P.logger.debug(f'socket_connect : {P.package_name} - {module.name}{attach}')
|
||||||
module.socketio_list.append(request.sid)
|
module.socketio_list.append(request.sid)
|
||||||
socketio_callback('start', '')
|
socketio_callback('start', '')
|
||||||
module.socketio_connect()
|
module.socketio_connect()
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
P.logger.error('Exception:%s', exception)
|
P.logger.error(f"Exception:{str(e)}")
|
||||||
P.logger.error(traceback.format_exc())
|
P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
@@ -347,8 +398,8 @@ def default_route_socketio_module(module, attach=''):
|
|||||||
P.logger.debug(f'socket_disconnect : {P.package_name} - {module.name}{attach}')
|
P.logger.debug(f'socket_disconnect : {P.package_name} - {module.name}{attach}')
|
||||||
module.socketio_list.remove(request.sid)
|
module.socketio_list.remove(request.sid)
|
||||||
module.socketio_disconnect()
|
module.socketio_disconnect()
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
P.logger.error('Exception:%s', exception)
|
P.logger.error(f"Exception:{str(e)}")
|
||||||
P.logger.error(traceback.format_exc())
|
P.logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
@@ -357,7 +408,7 @@ def default_route_socketio_module(module, attach=''):
|
|||||||
if encoding:
|
if encoding:
|
||||||
data = json.dumps(data, cls=AlchemyEncoder)
|
data = json.dumps(data, cls=AlchemyEncoder)
|
||||||
data = json.loads(data)
|
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
|
module.socketio_callback = socketio_callback
|
||||||
|
|
||||||
@@ -392,9 +443,10 @@ def default_route_socketio_page(page):
|
|||||||
page.socketio_list = []
|
page.socketio_list = []
|
||||||
|
|
||||||
@F.socketio.on('connect', namespace=f'/{P.package_name}/{module.name}/{page.name}')
|
@F.socketio.on('connect', namespace=f'/{P.package_name}/{module.name}/{page.name}')
|
||||||
|
@F.login_required
|
||||||
def page_socketio_connect():
|
def page_socketio_connect():
|
||||||
try:
|
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_list.append(request.sid)
|
||||||
page_socketio_socketio_callback('start', '')
|
page_socketio_socketio_callback('start', '')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -405,7 +457,7 @@ def default_route_socketio_page(page):
|
|||||||
@F.socketio.on('disconnect', namespace=f'/{P.package_name}/{module.name}/{page.name}')
|
@F.socketio.on('disconnect', namespace=f'/{P.package_name}/{module.name}/{page.name}')
|
||||||
def page_socketio_disconnect():
|
def page_socketio_disconnect():
|
||||||
try:
|
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)
|
page.socketio_list.remove(request.sid)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
P.logger.error(f'Exception:{str(e)}')
|
P.logger.error(f'Exception:{str(e)}')
|
||||||
@@ -417,6 +469,6 @@ def default_route_socketio_page(page):
|
|||||||
if encoding:
|
if encoding:
|
||||||
data = json.dumps(data, cls=AlchemyEncoder)
|
data = json.dumps(data, cls=AlchemyEncoder)
|
||||||
data = json.loads(data)
|
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
|
page.socketio_callback = page_socketio_socketio_callback
|
||||||
|
|||||||
BIN
lib/support/.DS_Store
vendored
Normal file
BIN
lib/support/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -10,15 +10,19 @@ def d(data):
|
|||||||
|
|
||||||
from .logger import get_logger
|
from .logger import get_logger
|
||||||
|
|
||||||
logger = get_logger()
|
#logger = get_logger()
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger('support')
|
||||||
|
|
||||||
from .base.aes import SupportAES
|
from .base.aes import SupportAES
|
||||||
from .base.discord import SupportDiscord
|
from .base.discord import SupportDiscord
|
||||||
from .base.file import SupportFile
|
from .base.file import SupportFile
|
||||||
|
from .base.os_command import SupportOSCommand
|
||||||
from .base.string import SupportString
|
from .base.string import SupportString
|
||||||
from .base.sub_process import SupportSubprocess
|
from .base.sub_process import SupportSubprocess
|
||||||
from .base.support_sc import SupportSC
|
from .base.support_sc import SupportSC
|
||||||
from .base.telegram import SupportTelegram
|
from .base.telegram import SupportTelegram
|
||||||
|
from .base.slack import SupportSlack
|
||||||
from .base.util import (AlchemyEncoder, SingletonClass, SupportUtil,
|
from .base.util import (AlchemyEncoder, SingletonClass, SupportUtil,
|
||||||
default_headers, pt)
|
default_headers, pt)
|
||||||
from .base.yaml import SupportYaml
|
from .base.yaml import SupportYaml
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ class SupportAES(object):
|
|||||||
def encrypt(cls, raw, mykey=None):
|
def encrypt(cls, raw, mykey=None):
|
||||||
try:
|
try:
|
||||||
Random.atfork()
|
Random.atfork()
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
raw = pad(raw)
|
raw = pad(raw)
|
||||||
@@ -31,8 +31,8 @@ class SupportAES(object):
|
|||||||
cipher = AES.new(key if mykey is None else mykey, AES.MODE_CBC, iv )
|
cipher = AES.new(key if mykey is None else mykey, AES.MODE_CBC, iv )
|
||||||
try:
|
try:
|
||||||
tmp = cipher.encrypt( raw )
|
tmp = cipher.encrypt( raw )
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
tmp = cipher.encrypt( raw.encode() )
|
tmp = cipher.encrypt( raw.encode() )
|
||||||
ret = base64.b64encode( iv + tmp )
|
ret = base64.b64encode( iv + tmp )
|
||||||
@@ -64,8 +64,8 @@ class SupportAES(object):
|
|||||||
def encrypt_(cls, raw, mykey=None, iv=None):
|
def encrypt_(cls, raw, mykey=None, iv=None):
|
||||||
try:
|
try:
|
||||||
Random.atfork()
|
Random.atfork()
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
raw = pad(raw)
|
raw = pad(raw)
|
||||||
if type(raw) == type(''):
|
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 )
|
cipher = AES.new(key if mykey is None else mykey, AES.MODE_CBC, iv )
|
||||||
try:
|
try:
|
||||||
tmp = cipher.encrypt( raw )
|
tmp = cipher.encrypt( raw )
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
tmp = cipher.encrypt( raw.encode() )
|
tmp = cipher.encrypt( raw.encode() )
|
||||||
ret = base64.b64encode( tmp )
|
ret = base64.b64encode( tmp )
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import io
|
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
@@ -11,31 +10,36 @@ try:
|
|||||||
except:
|
except:
|
||||||
os.system('pip3 install discord-webhook')
|
os.system('pip3 install discord-webhook')
|
||||||
|
|
||||||
|
# 2023-10-13 by flaskfarm
|
||||||
|
# 웹훅 URL이 git에 노출되면 중단.
|
||||||
|
# base64로 인코딩.
|
||||||
|
import base64
|
||||||
|
|
||||||
from discord_webhook import DiscordEmbed, DiscordWebhook
|
from discord_webhook import DiscordEmbed, DiscordWebhook
|
||||||
|
|
||||||
from . import logger
|
from . import logger
|
||||||
|
|
||||||
webhook_list = [
|
webhook_list = [
|
||||||
#'https://discord.com/api/webhooks/933908493612744705/DGPWBQN8LiMnt2cnCSNVy6rCc5Gi_vj98QpJ3ZEeihohzsfOsCWvcixJU1A2fQuepGFq', # 1
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MTkwMTk2NzE1NTMwNS9uY01aaWZVVDY3ZTRISXdPeG8xM0dLdFBTNFBnVjZZSDBZaU1SQ2FMQkNfMU0yMHo3WmNFRjExM2xnY0NpRTFFdnhEZQ==').decode('utf-8'), # 1
|
||||||
#'https://discord.com/api/webhooks/932754078839234731/R2iFzQ7P8IKV-MGWp820ToWX07s5q8X-st-QsUJs7j3JInUj6ZlI4uDYKeR_cwIi98mf', # 2
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MjY1NjQwOTA3MTY0Ni9zUjlHZFJMbERrQV9Cc243UkdvQXQ3TmVSMU9SVFRxczVqUEc5UU9PYTJCbjllTTI1YnctV0FXZ2pYT1pYa183U0V4Wg==').decode('utf-8'), # 2
|
||||||
#'https://discord.com/api/webhooks/932754171835351131/50RLrYa_B69ybk4BWoLruNqU7YlZ3pl3gpPr9bwuankWyTIGtRGbgf0CJ9ExJWJmvXwo', # 3
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MjkyNDExOTA0NDE4OC9wX3ZMN211eElKUmFWOXRDVG56S3c4LVJjY0R5V1JaSjdER2dYc1YwaXlLVGFjZEM4MVBiYmctWHFzY0NDTk5jdXpWeQ==').decode('utf-8'), # 3
|
||||||
'https://discord.com/api/webhooks/794661043863027752/A9O-vZSHIgfQ3KX7wO5_e2xisqpLw5TJxg2Qs1stBHxyd5PK-Zx0IJbAQXmyDN1ixZ-n', # 4
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MzE2MjE1ODI0MzkwMS9KdDMwZjlTTTR6dWNfVmYwSVlmbzdZNTluOFI5T2RQazNXdTFtNG93MHZxZFJERXlneVZvb25Rdm1QbVRya1lOVTRyag==').decode('utf-8'), # 4
|
||||||
'https://discord.com/api/webhooks/810373348776476683/h_uJLBBlHzD0w_CG0nUajFO-XEh3fvy-vQofQt1_8TMD7zHiR7a28t3jF-xBCP6EVlow', # 5
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MzMyNzY1MzE1ODk4Mi82Nk0zZVFyRGpSZG1UTzExaXZlMGhlTFFpNGwtckZUN1lRYTJ3elpmMjNBOGZPYm1CYjJSRmhxR2dNSHNlNUdHSFNLTA==').decode('utf-8'), # 5
|
||||||
'https://discord.com/api/webhooks/810373405508501534/wovhf-1pqcxW5h9xy7iwkYaf8KMDjHU49cMWuLKtBWjAnj-tzS1_j8RJ7tsMyViDbZCE', # 6
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MzU1ODAzNzg5MzIxNC84aWFNLTJIdXJFOW1XM0RqY293dm9tUVhUeUxLOElrbWR5SnhsY1BFRzJ4MjBqOTNIN0FWNnY0dVJIak5XeGprcjg4Tw==').decode('utf-8'), # 6
|
||||||
'https://discord.com/api/webhooks/796558388326039552/k2VV356S1gKQa9ht-JuAs5Dqw5eVkxgZsLUzFoxmFG5lW6jqKl7zCBbbKVhs3pcLOetm', # 7
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MzczMTQzNDYxMDc0OS9xRktGX0hSWDRYVHFYMVFPRzM5YWNJVkp6dmdRZXBzZjM2TEFEUlpNOWtiZ0pNUHVfd091OXZ4bXdZVVBRMUpkUjhhRg==').decode('utf-8'), # 7
|
||||||
'https://discord.com/api/webhooks/810373566452858920/Qf2V8BoLOy2kQzlZGHy5HZ1nTj7lK72ol_UFrR3_eHKEOK5fyR_fQ8Yw8YzVh9EQG54o', # 8
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1Mzg1NTI0NjI3NDYwMS9vWGVxYVdhWENNZktkM19iZktEVjB0Ti1XQzUyLUxpVjU0VjQxWE1jNWd3XzJmQnpnekp4MzJNYS1wOWlvQkFpd1I3Mw==').decode('utf-8'), # 8
|
||||||
'https://discord.com/api/webhooks/810373654411739157/SGgdO49OCkTNIlc_BSMSy7IXQwwXVonG3DsVfvBVE6luTCwvgCqEBpEk30WBeMMieCyI', # 9
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1Mzk5MDgyNzE1MTQ2MS85a0xXTXZCY1FaNzZRcnRpZmVJck9DOXo5SXl1WGl4YnRmbldocHVjSlFRVUJqcGxSd0tIdzdDc0h3THJhQkRQM1h5ag==').decode('utf-8'), # 9
|
||||||
'https://discord.com/api/webhooks/810373722341900288/FwcRJ4YxYjpyHpnRwF5f2an0ltEm8JPqcWeZqQi3Qz4QnhEY-kR2sjF9fo_n6stMGnf_', # 10
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDExMjQ1NzYzNzg5OC9ZVC1qblZTeWFxcjAxMjFtWUtVZFU1SjJaVFZHS0NOM2djUDI2RXEwWm5hR3RWeFllM3NZa0kyUG81RWhPd211WDd6aw==').decode('utf-8'), # 10
|
||||||
'https://discord.com/api/webhooks/931779811691626536/vvwCm1YQvE5tW4QJ4SNKRmXhQQrmOQxbjsgRjbTMMXOSiclB66qipiZaax5giAqqu2IB', # 11
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDI1Mzg1MTk1NTMxMS9RVUt1cU5uWFFiaWkwU01FMWxkU0lEakxhZXh5RDRUZEZuLWdXejFuSXRlYy1mSFVCU3dxUDd3WHNBbDB1dXd2VVJTRw==').decode('utf-8'), # 11
|
||||||
'https://discord.com/api/webhooks/931779905631420416/VKlDwfxWQPJfIaj94-ww_hM1MNEayRKoMq0adMffCC4WQS60yoAub_nqPbpnfFRR3VU5', # 12
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDM3NDMyNDgxMzkyNS9VR1Jsc3liY2dPQ3hoMVQ1Z0J0QVc2RGQyZ0dPaGVOXzcydy15QTBvZzU5aU1BcnB3WWxVRzhka0ZXTUxSVUZpaHFScw==').decode('utf-8'), # 12
|
||||||
'https://discord.com/api/webhooks/931779947914231840/22amQuHSOI7wPijSt3U01mXwd5hTo_WHfVkeaowDQMawCo5tXVfeEMd6wAWf1n7CseiG', # 13
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDUxNjE5NzI3Nzc2Ny9iOEFIN1FtY2JPSl9XcUVHZmtMOVNPbXBJMWluVThvcDF4amQwWGFjRXFFZW82ZURzbS0yYkpZYllmQ1RYclMxbHhUdQ==').decode('utf-8'), # 13
|
||||||
'https://discord.com/api/webhooks/810374294416654346/T3-TEdKIg7rwMZeDzNr46KPDvO7ZF8pRdJ3lfl39lJw2XEZamAG8uACIXagbNMX_B0YN', # 14
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDY0MDIzMTIzOTcwMS90bkFSTzFvYWo1SWRmb0U4UEVJejRZUVMxNFhKXzdpc0I5Q1otdzVyaXdDN0U0cVVzQ1B6V2pLRnM3WE9OazBvVEo5Qg==').decode('utf-8'), # 14
|
||||||
'https://discord.com/api/webhooks/810374337403289641/_esFkQXwlPlhxJWtlqDAdLg2Nujo-LjGPEG3mUmjiRZto69NQpkBJ0F2xtSNrCH4VAgb', # 15
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDc1NTcxNzIwMTk4MS9WLWQwc0hvNl9QakJTdFpLVmtuSTdDS0RuQks1QzRhS2dPZUZ4azEwam41VE5oZk1PdFNOSFNHN3BpaGNWLVh6Y0kxZg==').decode('utf-8'), # 15
|
||||||
'https://discord.com/api/webhooks/810374384736534568/mH5-OkBVpi7XqJioaQ8Ma-NiL-bOx7B5nYJpL1gZ03JaJaUaIW4bCHeCt5O_VGLJwAtj', # 16
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDg4NDc4NDMyNDYxOS9XVEpHWWVjcjVKOHhtN0hTaUpCbmdnU01Uc3JkMUxiaDVwQzB2Vm5tYVptZWlvd2RRZWZQRHRuZHowRmViWE9xYkNoeA==').decode('utf-8'), # 16
|
||||||
'https://discord.com/api/webhooks/810374428604104724/Z1Tdxz3mb0ytWq5LHWi4rG5CeJnr9KWXy5aO_waeD0NcImQnhRXe7h7ra7UrIDRQ2jOg', # 17
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NTAxMTIxMzA5OTEyOS9neHVVenpsMTBpMUV4NWZtdU5jZGlOQ2FocHBEM3liQlpxaTR3Y3phdlpGeG1OUGx2VFRadU9CalZCMTBOZzJ2QWpLcA==').decode('utf-8'), # 17
|
||||||
'https://discord.com/api/webhooks/810374475773509643/QCPPN4djNzhuOmbS3DlrGBunK0SVR5Py9vMyCiPL-0T2VPgitFZS4YM6GCLfM2fkrn4-', # 18
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NTEzMjg4OTczMTE1My9YcTU4cXdCTGlOOEF4S1djQTl0MFJERkhIT0NDNjg4MlQ1aXBKbkJxY3VSOFVxMGowSzF4Rko3dUZWaGhRR0RFTjc3bw==').decode('utf-8'), # 18
|
||||||
'https://discord.com/api/webhooks/810374527652855819/5ypaKI_r-hYzwmdDlVmgAU6xNgU833L9tFlPnf3nw4ZDaPMSppjt77aYOiFks4KLGQk8', # 19
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NTI5NzYzNzc5MzgxMy9pV3hoZkxRN190dHhkNENIVnNPWjA2ZHFOUjlkVTZUdlNfdHA2OHVnNlI2WmRIa2dESzJKb28xUVNSa3NrRDhLUXRyTg==').decode('utf-8'), # 19
|
||||||
'https://discord.com/api/webhooks/810374587917402162/lHrG7CEysGUM_41DMnrxL2Q8eh1-xPjJXstYE68WWfLQbuUAV3rOfsNB9adncJzinYKi', # 20
|
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NTQ0NDk0MjAxMjQ4OC9zandtaFNDYjI0ZElYbjBVMWhwMmdJRzZDV2REcC1Kb3M0OW1Oc05jQllGenNDNm1KYVZJOVpoQm11dGt4cXd1bDc1ZA==').decode('utf-8'), # 20
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -44,20 +48,60 @@ class SupportDiscord(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def send_discord_message(cls, text, image_url=None, webhook_url=None):
|
def send_discord_message(cls, text, image_url=None, webhook_url=None):
|
||||||
try:
|
try:
|
||||||
|
"""
|
||||||
webhook = DiscordWebhook(url=webhook_url, content=text)
|
webhook = DiscordWebhook(url=webhook_url, content=text)
|
||||||
if image_url is not None:
|
if image_url is not None:
|
||||||
embed = DiscordEmbed()
|
embed = DiscordEmbed()
|
||||||
embed.set_timestamp()
|
embed.set_timestamp()
|
||||||
embed.set_image(url=image_url)
|
embed.set_image(url=image_url)
|
||||||
webhook.add_embed(embed)
|
webhook.add_embed(embed)
|
||||||
response = webhook.execute()
|
"""
|
||||||
return True
|
try:
|
||||||
except Exception as exception:
|
|
||||||
logger.error('Exception:%s', exception)
|
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())
|
logger.error(traceback.format_exc())
|
||||||
return False
|
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
|
@classmethod
|
||||||
def discord_proxy_image(cls, image_url, webhook_url=None, retry=True):
|
def discord_proxy_image(cls, image_url, webhook_url=None, retry=True):
|
||||||
@@ -95,21 +139,21 @@ class SupportDiscord(object):
|
|||||||
return image_url
|
return image_url
|
||||||
else:
|
else:
|
||||||
raise Exception(str(data))
|
raise Exception(str(data))
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
if retry:
|
if retry:
|
||||||
time.sleep(1)
|
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:
|
else:
|
||||||
return image_url
|
return image_url
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def discord_proxy_image_localfile(cls, filepath, retry=True):
|
def discord_proxy_image_localfile(cls, filepath, webhook_url=None, retry=True):
|
||||||
data = None
|
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:
|
try:
|
||||||
webhook = DiscordWebhook(url=webhook_url, content='')
|
webhook = DiscordWebhook(url=webhook_url, content='')
|
||||||
import io
|
import io
|
||||||
@@ -133,8 +177,8 @@ class SupportDiscord(object):
|
|||||||
if retry:
|
if retry:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
return cls.discord_proxy_image_localfile(filepath, retry=False)
|
return cls.discord_proxy_image_localfile(filepath, retry=False)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
if retry:
|
if retry:
|
||||||
@@ -143,15 +187,15 @@ class SupportDiscord(object):
|
|||||||
|
|
||||||
|
|
||||||
@classmethod
|
@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
|
data = None
|
||||||
idx = random.randint(0,len(webhook_list)-1)
|
if webhook_url is None or webhook_url == '':
|
||||||
webhook_url = webhook_list[idx]
|
webhook_url = webhook_list[random.randint(0,len(webhook_list)-1)]
|
||||||
try:
|
try:
|
||||||
webhook = DiscordWebhook(url=webhook_url, content='')
|
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 = DiscordEmbed()
|
||||||
embed.set_image(url="attachment://image.jpg")
|
embed.set_image(url=f"attachment://image.{format}")
|
||||||
response = webhook.execute()
|
response = webhook.execute()
|
||||||
data = None
|
data = None
|
||||||
if type(response) == type([]):
|
if type(response) == type([]):
|
||||||
@@ -168,8 +212,8 @@ class SupportDiscord(object):
|
|||||||
if retry:
|
if retry:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
return cls.discord_proxy_image_bytes(bytes, retry=False)
|
return cls.discord_proxy_image_bytes(bytes, retry=False)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
if retry:
|
if retry:
|
||||||
@@ -181,7 +225,7 @@ class SupportDiscord(object):
|
|||||||
|
|
||||||
# RSS에서 자막 올린거
|
# RSS에서 자막 올린거
|
||||||
@classmethod
|
@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
|
data = None
|
||||||
if webhook_url is None:
|
if webhook_url is None:
|
||||||
webhook_url = webhook_list[random.randint(0,9)]
|
webhook_url = webhook_list[random.randint(0,9)]
|
||||||
@@ -210,8 +254,8 @@ class SupportDiscord(object):
|
|||||||
if retry:
|
if retry:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
return cls.discord_proxy_image_localfile(filepath, retry=False)
|
return cls.discord_proxy_image_localfile(filepath, retry=False)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
if retry:
|
if retry:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
|
import zipfile
|
||||||
|
|
||||||
from . import logger
|
from . import logger
|
||||||
|
|
||||||
@@ -16,8 +17,8 @@ class SupportFile(object):
|
|||||||
data = ifp.read()
|
data = ifp.read()
|
||||||
ifp.close()
|
ifp.close()
|
||||||
return data
|
return data
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -27,8 +28,8 @@ class SupportFile(object):
|
|||||||
ofp = codecs.open(filename, mode, encoding='utf8')
|
ofp = codecs.open(filename, mode, encoding='utf8')
|
||||||
ofp.write(data)
|
ofp.write(data)
|
||||||
ofp.close()
|
ofp.close()
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -37,8 +38,8 @@ class SupportFile(object):
|
|||||||
with open(filepath, "r", encoding='utf8') as json_file:
|
with open(filepath, "r", encoding='utf8') as json_file:
|
||||||
data = json.load(json_file)
|
data = json.load(json_file)
|
||||||
return data
|
return data
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -48,8 +49,8 @@ class SupportFile(object):
|
|||||||
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
||||||
with open(filepath, "w", encoding='utf8') as json_file:
|
with open(filepath, "w", encoding='utf8') as json_file:
|
||||||
json.dump(data, json_file, indent=4, ensure_ascii=False)
|
json.dump(data, json_file, indent=4, ensure_ascii=False)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
@@ -71,8 +72,8 @@ class SupportFile(object):
|
|||||||
response = requests.get(url, headers=headers) # get request
|
response = requests.get(url, headers=headers) # get request
|
||||||
file_is.write(response.content) # write to file
|
file_is.write(response.content) # write to file
|
||||||
return True
|
return True
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.debug('Exception:%s', exception)
|
logger.debug(f"Exception:{str(e)}")
|
||||||
logger.debug(traceback.format_exc())
|
logger.debug(traceback.format_exc())
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -82,118 +83,18 @@ class SupportFile(object):
|
|||||||
#text = text.replace('/', '')
|
#text = text.replace('/', '')
|
||||||
# 2021-07-31 X:X
|
# 2021-07-31 X:X
|
||||||
#text = text.replace(':', ' ')
|
#text = text.replace(':', ' ')
|
||||||
text = re.sub('[\\/:*?\"<>|]', ' ', text).strip()
|
text = re.sub('[\\/:*?\"<>|:]', ' ', text).strip()
|
||||||
text = re.sub("\s{2,}", ' ', text)
|
text = re.sub("\s{2,}", ' ', text)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def size(cls, start_path = '.'):
|
def unzip(cls, zip_filepath, extract_folderpath):
|
||||||
total_size = 0
|
with zipfile.ZipFile(zip_filepath, 'r') as zip_ref:
|
||||||
for dirpath, dirnames, filenames in os.walk(start_path):
|
zip_ref.extractall(extract_folderpath)
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
|
# 파일처리에서 사용. 중복이면 시간값
|
||||||
@classmethod
|
@classmethod
|
||||||
def file_move(cls, source_path, target_dir, target_filename):
|
def file_move(cls, source_path, target_dir, target_filename):
|
||||||
try:
|
try:
|
||||||
@@ -208,9 +109,160 @@ class SupportFile(object):
|
|||||||
new_target_filename = f"{tmp[0]} {str(time.time()).split('.')[0]}{tmp[1]}"
|
new_target_filename = f"{tmp[0]} {str(time.time()).split('.')[0]}{tmp[1]}"
|
||||||
target_path = os.path.join(target_dir, new_target_filename)
|
target_path = os.path.join(target_dir, new_target_filename)
|
||||||
shutil.move(source_path, target_path)
|
shutil.move(source_path, target_path)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.debug('Exception:%s', exception)
|
logger.debug(f"Exception:{str(e)}")
|
||||||
logger.debug(traceback.format_exc())
|
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
|
@classmethod
|
||||||
@@ -234,13 +286,13 @@ class SupportFile(object):
|
|||||||
import shutil
|
import shutil
|
||||||
shutil.rmtree(zip_path)
|
shutil.rmtree(zip_path)
|
||||||
return zipfilepath
|
return zipfilepath
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
return
|
return
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
@classmethod
|
@classmethod
|
||||||
def rmtree(cls, folderpath):
|
def rmtree(cls, folderpath):
|
||||||
import shutil
|
import shutil
|
||||||
@@ -253,19 +305,9 @@ class SupportFile(object):
|
|||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
return False
|
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:
|
try:
|
||||||
with open(filename, 'wb') as f:
|
with open(filename, 'wb') as f:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
@@ -361,8 +403,8 @@ class SupportFile(object):
|
|||||||
if isinstance(data, bytes):
|
if isinstance(data, bytes):
|
||||||
data = data.decode('utf-8')
|
data = data.decode('utf-8')
|
||||||
return data
|
return data
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import os, sys, traceback, requests
|
import os
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
from . import logger
|
from . import logger
|
||||||
|
|
||||||
|
|
||||||
class SupportImage(object):
|
class SupportImage(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -20,6 +26,6 @@ class SupportImage(object):
|
|||||||
from . import SupportDiscord
|
from . import SupportDiscord
|
||||||
return SupportDiscord.discord_proxy_image_bytes(img_byte_arr)
|
return SupportDiscord.discord_proxy_image_bytes(img_byte_arr)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', e)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
|
|||||||
44
lib/support/base/os_command.py
Normal file
44
lib/support/base/os_command.py
Normal 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
25
lib/support/base/slack.py
Normal 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
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import re
|
||||||
|
import traceback
|
||||||
|
|
||||||
from . import logger
|
from . import logger
|
||||||
|
|
||||||
|
|
||||||
@@ -5,22 +8,78 @@ class SupportString(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_cate_char_by_first(cls, title): # get_first
|
def get_cate_char_by_first(cls, title): # get_first
|
||||||
value = ord(title[0].upper())
|
value = ord(title[0].upper())
|
||||||
if value >= ord('0') and value <= ord('9'): return '0Z'
|
if ord('가') <= value < ord('나'): return '가'
|
||||||
elif value >= ord('A') and value <= ord('Z'): return '0Z'
|
if ord('나') <= value < ord('다'): return '나'
|
||||||
elif value >= ord('가') and value < ord('나'): return '가'
|
if ord('다') <= value < ord('라'): return '다'
|
||||||
elif value < ord('다'): return '나'
|
if ord('라') <= value < ord('마'): return '라'
|
||||||
elif value < ord('라'): return '다'
|
if ord('마') <= value < ord('바'): return '마'
|
||||||
elif value < ord('마'): return '라'
|
if ord('바') <= value < ord('사'): return '바'
|
||||||
elif value < ord('바'): return '마'
|
if ord('사') <= value < ord('아'): return '사'
|
||||||
elif value < ord('사'): return '바'
|
if ord('아') <= value < ord('자'): return '아'
|
||||||
elif value < ord('아'): return '사'
|
if ord('자') <= value < ord('차'): return '자'
|
||||||
elif value < ord('자'): return '아'
|
if ord('차') <= value < ord('카'): return '차'
|
||||||
elif value < ord('차'): return '자'
|
if ord('카') <= value < ord('타'): return '카'
|
||||||
elif value < ord('카'): return '차'
|
if ord('타') <= value < ord('파'): return '타'
|
||||||
elif value < ord('타'): return '카'
|
if ord('파') <= value < ord('하'): return '파'
|
||||||
elif value < ord('파'): return '타'
|
if ord('하') <= value < ord('힣'): return '하'
|
||||||
elif value < ord('하'): return '파'
|
return '0Z'
|
||||||
elif value <= ord('힣'): return '하'
|
|
||||||
else: 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
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
|
import locale
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import queue
|
import queue
|
||||||
@@ -20,7 +21,7 @@ def demote(user_uid, user_gid):
|
|||||||
class SupportSubprocess(object):
|
class SupportSubprocess(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def command_for_windows(cls, command: list) -> str or list:
|
def command_for_windows(cls, command: list):
|
||||||
if platform.system() == 'Windows':
|
if platform.system() == 'Windows':
|
||||||
tmp = []
|
tmp = []
|
||||||
if type(command) == type([]):
|
if type(command) == type([]):
|
||||||
@@ -43,17 +44,32 @@ class SupportSubprocess(object):
|
|||||||
|
|
||||||
iter_arg = ''
|
iter_arg = ''
|
||||||
if platform.system() == 'Windows':
|
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:
|
else:
|
||||||
if uid == None:
|
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')
|
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=shell, env=env, encoding='utf8')
|
||||||
else:
|
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')
|
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}
|
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:
|
try:
|
||||||
|
#process.communicate()
|
||||||
process_ret = process.wait(timeout=timeout) # wait for the subprocess to exit
|
process_ret = process.wait(timeout=timeout) # wait for the subprocess to exit
|
||||||
except:
|
except:
|
||||||
import psutil
|
import psutil
|
||||||
@@ -62,14 +78,17 @@ class SupportSubprocess(object):
|
|||||||
proc.kill()
|
proc.kill()
|
||||||
process.kill()
|
process.kill()
|
||||||
new_ret['status'] = "timeout"
|
new_ret['status'] = "timeout"
|
||||||
|
#logger.error(process_ret)
|
||||||
ret = []
|
thread.join()
|
||||||
with process.stdout:
|
#ret = []
|
||||||
for line in iter(process.stdout.readline, iter_arg):
|
#with process.stdout:
|
||||||
ret.append(line.strip())
|
# for line in iter(process.stdout.readline, iter_arg):
|
||||||
if log:
|
# ret.append(line.strip())
|
||||||
logger.debug(ret[-1])
|
# if log:
|
||||||
|
# logger.debug(ret[-1])
|
||||||
|
|
||||||
|
ret = result
|
||||||
|
#logger.error(ret)
|
||||||
if format is None:
|
if format is None:
|
||||||
ret2 = '\n'.join(ret)
|
ret2 = '\n'.join(ret)
|
||||||
elif format == 'json':
|
elif format == 'json':
|
||||||
@@ -82,20 +101,29 @@ class SupportSubprocess(object):
|
|||||||
break
|
break
|
||||||
ret2 = json.loads(''.join(ret[index:]))
|
ret2 = json.loads(''.join(ret[index:]))
|
||||||
except:
|
except:
|
||||||
ret2 = None
|
ret2 = ret
|
||||||
|
|
||||||
new_ret['log'] = ret2
|
new_ret['log'] = ret2
|
||||||
return new_ret
|
return new_ret
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
logger.error('command : %s', command)
|
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 = []
|
__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.command = command
|
||||||
self.print_log = print_log
|
self.print_log = print_log
|
||||||
self.shell = shell
|
self.shell = shell
|
||||||
@@ -108,6 +136,7 @@ class SupportSubprocess(object):
|
|||||||
self.stdout_queue = None
|
self.stdout_queue = None
|
||||||
self.call_id = call_id
|
self.call_id = call_id
|
||||||
self.timestamp = time.time()
|
self.timestamp = time.time()
|
||||||
|
self.callback_line = callback_line
|
||||||
|
|
||||||
|
|
||||||
def start(self, join=True):
|
def start(self, join=True):
|
||||||
@@ -127,13 +156,15 @@ class SupportSubprocess(object):
|
|||||||
self.command = self.command_for_windows(self.command)
|
self.command = self.command_for_windows(self.command)
|
||||||
logger.debug(f"{self.command=}")
|
logger.debug(f"{self.command=}")
|
||||||
if platform.system() == 'Windows':
|
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:
|
else:
|
||||||
if self.uid == None:
|
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:
|
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)
|
SupportSubprocess.__instance_list.append(self)
|
||||||
|
self.send_stdout_callback(self.call_id, 'START', None)
|
||||||
self.__start_communicate()
|
self.__start_communicate()
|
||||||
self.__start_send_callback()
|
self.__start_send_callback()
|
||||||
if self.process is not None:
|
if self.process is not None:
|
||||||
@@ -142,17 +173,18 @@ class SupportSubprocess(object):
|
|||||||
self.process_close()
|
self.process_close()
|
||||||
else:
|
else:
|
||||||
self.process.wait()
|
self.process.wait()
|
||||||
|
self.remove_instance(self)
|
||||||
logger.info(f"{self.command} END")
|
logger.info(f"{self.command} END")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f'Exception:{str(e)}')
|
logger.error(f'Exception:{str(e)}')
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
logger.warning(self.command)
|
logger.warning(self.command)
|
||||||
if self.stdout_callback != None:
|
self.send_stdout_callback(self.call_id, 'ERROR', str(e))
|
||||||
self.stdout_callback('error', str(e))
|
self.send_stdout_callback(self.call_id, 'ERROR', str(traceback.format_exc()))
|
||||||
self.stdout_callback('error', str(traceback.format_exc()))
|
|
||||||
finally:
|
finally:
|
||||||
if self.stdout_callback != None:
|
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):
|
def __start_communicate(self):
|
||||||
@@ -164,7 +196,11 @@ class SupportSubprocess(object):
|
|||||||
|
|
||||||
def rdr():
|
def rdr():
|
||||||
while True:
|
while True:
|
||||||
buf = self.process.stdout.read(1)
|
try:
|
||||||
|
buf = self.process.stdout.read(1)
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
#print(buf)
|
||||||
if buf:
|
if buf:
|
||||||
_queue.put( buf )
|
_queue.put( buf )
|
||||||
else:
|
else:
|
||||||
@@ -192,7 +228,9 @@ class SupportSubprocess(object):
|
|||||||
if r is not None:
|
if r is not None:
|
||||||
#print(f"{r=}")
|
#print(f"{r=}")
|
||||||
self.stdout_queue.put(r)
|
self.stdout_queue.put(r)
|
||||||
|
self.stdout_queue.put('\n')
|
||||||
self.stdout_queue.put('<END>')
|
self.stdout_queue.put('<END>')
|
||||||
|
self.stdout_queue.put('\n')
|
||||||
for tgt in [rdr, clct]:
|
for tgt in [rdr, clct]:
|
||||||
th = threading.Thread(target=tgt)
|
th = threading.Thread(target=tgt)
|
||||||
th.setDaemon(True)
|
th.setDaemon(True)
|
||||||
@@ -204,16 +242,36 @@ class SupportSubprocess(object):
|
|||||||
def func():
|
def func():
|
||||||
while self.stdout_queue:
|
while self.stdout_queue:
|
||||||
line = self.stdout_queue.get()
|
line = self.stdout_queue.get()
|
||||||
|
#logger.error(line)
|
||||||
if line == '<END>':
|
if line == '<END>':
|
||||||
if self.stdout_callback != None:
|
self.send_stdout_callback(self.call_id, 'END', None)
|
||||||
self.stdout_callback('end', None)
|
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
if self.stdout_callback != None:
|
self.send_stdout_callback(self.call_id, 'LOG', line)
|
||||||
self.stdout_callback('log', line)
|
|
||||||
self.remove_instance(self)
|
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.setDaemon(True)
|
||||||
th.start()
|
th.start()
|
||||||
|
|
||||||
@@ -243,6 +301,15 @@ class SupportSubprocess(object):
|
|||||||
self.process.stdin.write(f'{cmd}\n')
|
self.process.stdin.write(f'{cmd}\n')
|
||||||
self.process.stdin.flush()
|
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
|
@classmethod
|
||||||
def all_process_close(cls):
|
def all_process_close(cls):
|
||||||
@@ -271,4 +338,7 @@ class SupportSubprocess(object):
|
|||||||
for instance in cls.__instance_list:
|
for instance in cls.__instance_list:
|
||||||
if instance.call_id == call_id:
|
if instance.call_id == call_id:
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_list(cls):
|
||||||
|
return cls.__instance_list
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
import requests
|
||||||
from telepot_mod import Bot
|
from telepot_mod import Bot
|
||||||
|
|
||||||
from . import logger
|
from . import logger
|
||||||
@@ -12,11 +14,21 @@ class SupportTelegram:
|
|||||||
try:
|
try:
|
||||||
bot = Bot(bot_token)
|
bot = Bot(bot_token)
|
||||||
if image_url is not None:
|
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)
|
bot.sendMessage(chat_id, text, disable_web_page_preview=True, disable_notification=disable_notification)
|
||||||
return True
|
return True
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
logger.debug('Chatid:%s', chat_id)
|
logger.debug('Chatid:%s', chat_id)
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -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 . import logger
|
||||||
|
|
||||||
from functools import wraps
|
|
||||||
import time
|
|
||||||
def pt(f):
|
def pt(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def wrapper(*args, **kwds):
|
def wrapper(*args, **kwds):
|
||||||
@@ -26,9 +29,9 @@ class SupportUtil(object):
|
|||||||
def sizeof_fmt(cls, num, suffix='Bytes'):
|
def sizeof_fmt(cls, num, suffix='Bytes'):
|
||||||
for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
|
for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
|
||||||
if abs(num) < 1024.0:
|
if abs(num) < 1024.0:
|
||||||
return "%3.1f%s%s" % (num, unit, suffix)
|
return "%3.1f %s%s" % (num, unit, suffix)
|
||||||
num /= 1024.0
|
num /= 1024.0
|
||||||
return "%.1f%s%s" % (num, 'Y', suffix)
|
return "%.1f %s%s" % (num, 'Y', suffix)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_arm(cls):
|
def is_arm(cls):
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
|
import os
|
||||||
|
import traceback
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
from . import logger
|
||||||
|
|
||||||
|
|
||||||
class SupportYaml(object):
|
class SupportYaml(object):
|
||||||
@classmethod
|
@classmethod
|
||||||
def write_yaml(cls, filepath, data):
|
def write_yaml(cls, filepath, data):
|
||||||
@@ -11,3 +17,41 @@ class SupportYaml(object):
|
|||||||
with open(filepath, encoding='utf8') as file:
|
with open(filepath, encoding='utf8') as file:
|
||||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||||
return data
|
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'
|
||||||
@@ -116,7 +116,11 @@ class SupportFfmpeg(object):
|
|||||||
header_count = 0
|
header_count = 0
|
||||||
if self.proxy is None:
|
if self.proxy is None:
|
||||||
if self.headers 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:
|
else:
|
||||||
headers_command = []
|
headers_command = []
|
||||||
tmp = ""
|
tmp = ""
|
||||||
@@ -136,9 +140,15 @@ class SupportFfmpeg(object):
|
|||||||
if len(tmp) > 0:
|
if len(tmp) > 0:
|
||||||
headers_command.append('-headers')
|
headers_command.append('-headers')
|
||||||
headers_command.append(f'{tmp}')
|
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:
|
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':
|
if platform.system() == 'Windows':
|
||||||
@@ -159,7 +169,7 @@ class SupportFfmpeg(object):
|
|||||||
return
|
return
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
#logger.error(' '.join(command))
|
logger.error(' '.join(command))
|
||||||
command = SupportSubprocess.command_for_windows(command)
|
command = SupportSubprocess.command_for_windows(command)
|
||||||
|
|
||||||
if platform.system() == 'Windows' and header_count > 1:
|
if platform.system() == 'Windows' and header_count > 1:
|
||||||
@@ -216,8 +226,8 @@ SET CRLF=^
|
|||||||
else:
|
else:
|
||||||
if os.path.exists(self.temp_fullpath):
|
if os.path.exists(self.temp_fullpath):
|
||||||
os.remove(self.temp_fullpath)
|
os.remove(self.temp_fullpath)
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
arg = {'type':'last', 'status':self.status, 'data' : self.get_data()}
|
arg = {'type':'last', 'status':self.status, 'data' : self.get_data()}
|
||||||
@@ -347,6 +357,7 @@ SET CRLF=^
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
def send_to_listener(self, **arg):
|
def send_to_listener(self, **arg):
|
||||||
|
print(arg)
|
||||||
if self.total_callback_function != None:
|
if self.total_callback_function != None:
|
||||||
self.total_callback_function(**arg)
|
self.total_callback_function(**arg)
|
||||||
if self.callback_function is not None and self.callback_function != self.total_callback_function:
|
if self.callback_function is not None and self.callback_function != self.total_callback_function:
|
||||||
|
|||||||
27
lib/support/expand/ffprobe.py
Normal file
27
lib/support/expand/ffprobe.py
Normal 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())
|
||||||
@@ -204,9 +204,9 @@ class GoogleSheetBase:
|
|||||||
break
|
break
|
||||||
except gspread.exceptions.APIError:
|
except gspread.exceptions.APIError:
|
||||||
self.sleep_exception()
|
self.sleep_exception()
|
||||||
except Exception as exception:
|
except Exception as e:
|
||||||
logger.error(f"{key} - {value}")
|
logger.error(f"{key} - {value}")
|
||||||
logger.error('Exception:%s', exception)
|
logger.error(f"Exception:{str(e)}")
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
logger.error(self.header_info)
|
logger.error(self.header_info)
|
||||||
self.sleep_exception()
|
self.sleep_exception()
|
||||||
|
|||||||
197
lib/support/expand/rclone.py
Normal file
197
lib/support/expand/rclone.py
Normal 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
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user