This commit is contained in:
flaskfarm
2022-10-06 14:39:43 +09:00
parent 435c5c9c36
commit b4e737a6b2
19 changed files with 378 additions and 248 deletions

View File

@@ -1,14 +1,11 @@
path_data: "/data" path_data: "/data"
use_gevent: true
use_celery: true
redis_port: 46379
port: 9999
debug: false
use_reloader: false
plugin_update: true
#use_gevent: true
#use_celery: true
#debug: false
#redis_port: 46379
#port: 9999
#plugin_update: false
#running_type: "docker" #running_type: "docker"
#plugin_loading_only_devpath: true #plugin_loading_only_devpath: true
#plugin_loading_list: [] #plugin_loading_list: []

View File

@@ -6,39 +6,36 @@ path_data: "."
#path_data: "/mnt/c/work/FlaskFarm/working" #path_data: "/mnt/c/work/FlaskFarm/working"
# gevent 사용여부 # gevent 사용여부
# 거의 항상 true로 사용.
# 플러그인 개발이나 termux 환경에서의 실행 같이 특수한 경우에만 false로 사용. # 플러그인 개발이나 termux 환경에서의 실행 같이 특수한 경우에만 false로 사용.
# 실행환경에 gevent 관련 패키지가 설치되어 있지 않는다면 값과 상관 없이 false로 동작. # 실행환경에 gevent 관련 패키지가 설치되어 있지 않는다면 값과 상관 없이 false로 동작.
# false인 경우 #use_gevent: true
use_gevent: true
# celery 사용 여부 # celery 사용 여부
use_celery: true #use_celery: true
# redis port # redis port
# celery를 사용하는 경우 사용하는 redis 포트 # celery를 사용하는 경우 사용하는 redis 포트
redis_port: 6379 # 환경변수 REDIS_PORT 값이 있는 경우 무시됨.
#redis_port: 6379
# 포트 # 포트
# 생략시 DB 값을 사용. # 생략시 DB 값을 사용.
port: 9999 #port: 9999
# 소스 수정시 재로딩 # 소스 수정시 재로딩
# 두번 로딩되는 것을 감안하여 코딩해야 함. 기본실행, subporcess 실행 # 두번 로딩되는 것을 감안하여 코딩해야 함.
debug: false #debug: false
use_reloader: false
# 플러그인 업데이트 여부 # 플러그인 업데이트 여부
# - true인 경우 로딩시 플러그인을 업데이트 함. # - true인 경우 로딩시 플러그인을 업데이트 함.
# 데이터폴더/plugins 폴더에 플러그인 만을 대상으로 함. # /data/plugins 폴더에 있는 플러그인 만을 대상으로 함.
# - debug 값이 true인 경우에는 항상 false # - debug 값이 true인 경우에는 항상 false
plugin_update: true #plugin_update: true
# running_type # running_type
# termux, entware 인 경우 입력 함. (이외 사용하는 값 native, docker) # termux, entware 인 경우 입력 함.
#running_type: "native" #running_type: "native"

View File

@@ -1,40 +1,29 @@
# 카테고리 # 카테고리
# uri 가 plugin인 경우 name 값은 대체 # uri 가 plugin인 경우 name 값은 대체
- name: "토렌트" #- name: "토렌트"
list: # list:
- uri: "rss2" # - uri: "rss"
- name: "즐겨찾기" #- name: "기본 기능"
list: # list:
- uri: "number_baseball" # - uri: "terminal"
# - uri: "command" # - uri: "command"
# - uri: "flaskfilemanager" # - uri: "flaskfilemanager"
# - uri: "flaskcode" # - uri: "flaskcode"
# - uri: "number_baseball"
- name: "기본 기능"
list:
- uri: "terminal"
- uri: "command"
- uri: "flaskfilemanager"
- uri: "flaskcode"
- uri: "number_baseball"
- name: "링크" #- name: "링크"
list: # list:
- uri: "https://app.plex.tv" # - uri: "https://app.plex.tv"
name: "Plex" # name: "Plex"
target: "_self" # target: "_self"
- uri: "https://app.plex.tv" # - uri: "https://www.netflix.com"
name: "Netflix" # name: "Netflix"
- name: "Plex"
uri: "https://app.plex.tv"
- name: "시스템" - name: "시스템"
list: list:
- uri: "system" - uri: "system"

View File

@@ -97,14 +97,14 @@ class Framework:
#if frame.config['use_celery'] == False or platform.system() == 'Windows': #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('no celery')
try:
redis_port = os.environ['REDIS_PORT'] redis_port = os.environ.get('REDIS_PORT', None)
except: if redis_port == None:
try: redis_port = self.config.get('redis_port', None)
redis_port = self.config['redis_port'] if redis_port == None:
except:
redis_port = '6379' redis_port = '6379'
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
celery = Celery(self.app.name, broker=self.app.config['CELERY_BROKER_URL'], backend=self.app.config['CELERY_RESULT_BACKEND']) celery = Celery(self.app.name, broker=self.app.config['CELERY_BROKER_URL'], backend=self.app.config['CELERY_RESULT_BACKEND'])
@@ -183,7 +183,7 @@ class Framework:
if self.config['run_flask']: if self.config['run_flask']:
if self.config.get('port') == None: if self.config.get('port') == None:
self.config['port'] = SP.SystemModelSetting.get_int('port') self.config['port'] = self.SystemModelSetting.get_int('port')
from . import init_route, log_viewer from . import init_route, log_viewer
@@ -216,9 +216,12 @@ class Framework:
self.config['path_working'] = os.getcwd() self.config['path_working'] = os.getcwd()
if os.environ.get('RUNNING_TYPE') == 'docker': if os.environ.get('RUNNING_TYPE') == 'docker':
self.config['running_type'] = 'docker' self.config['running_type'] = 'docker'
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.__process_args() self.__process_args()
self.__load_config() self.__load_config()
self.__init_define() self.__init_define()
self.config['menu_yaml_filepath'] = os.path.join(self.config['path_data'], 'db', 'menu.yaml')
elif mode == "flask": elif mode == "flask":
self.app.secret_key = os.urandom(24) self.app.secret_key = os.urandom(24)
#self.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data/db/system.db?check_same_thread=False' #self.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data/db/system.db?check_same_thread=False'
@@ -300,6 +303,21 @@ class Framework:
self.config['path_data'] = os.path.join(self.config['path_working'], 'data') self.config['path_data'] = os.path.join(self.config['path_working'], 'data')
self.path_data = self.config['path_data'] self.path_data = self.config['path_data']
if self.config.get('use_gevent') == None:
self.config['use_gevent'] = True
if self.config.get('use_celery') == None:
self.config['use_celery'] = True
if self.config.get('debug') == None:
self.config['debug'] = False
if self.config.get('plugin_update') == None:
self.config['plugin_update'] = True
if self.config.get('plugin_loading_only_devpath') == None:
self.config['plugin_loading_only_devpath'] = False
if self.config.get('plugin_loading_list') == None:
self.config['plugin_loading_list'] = []
if self.config.get('plugin_except_list') == None:
self.config['plugin_except_list'] = []
def __make_default_dir(self): def __make_default_dir(self):
@@ -399,7 +417,7 @@ class Framework:
for i in range(10): for i in range(10):
try: try:
#self.logger.debug(d(self.config)) #self.logger.debug(d(self.config))
self.socketio.run(self.app, host=host, port=self.config['port'], debug=self.config['debug'], use_reloader=self.config['use_reloader']) self.socketio.run(self.app, host=host, port=self.config['port'], debug=self.config['debug'], use_reloader=self.config['debug'])
self.logger.warning(f"EXIT CODE : {self.__exit_code}") self.logger.warning(f"EXIT CODE : {self.__exit_code}")
# 2021-05-18 # 2021-05-18
if self.config['running_type'] in ['termux', 'entware']: if self.config['running_type'] in ['termux', 'entware']:

View File

@@ -87,9 +87,6 @@ class MenuManager:
#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):
return cls.menu_map return cls.menu_map

View File

@@ -63,7 +63,7 @@ class PluginManager:
# plugin_loading_list # plugin_loading_list
try: try:
plugin_loading_list = F.config.get('plugin_loading_list', None) plugin_loading_list = F.config.get('plugin_loading_list', None)
if plugin_loading_list != None and type(plugin_loading_list) == type([]): if plugin_loading_list != None and (type(plugin_loading_list) == type([]) and len(plugin_loading_list)) > 0:
new_plugins = [] new_plugins = []
for _ in plugins: for _ in plugins:
if _ in plugin_loading_list: if _ in plugin_loading_list:
@@ -76,7 +76,7 @@ class PluginManager:
# plugin_except_list # plugin_except_list
try: try:
plugin_except_list = F.config.get('plugin_except_list', None) plugin_except_list = F.config.get('plugin_except_list', None)
if plugin_except_list != None and type(plugin_except_list) == type([]): if plugin_except_list != None and (type(plugin_except_list) == type([]) and len(plugin_except_list)) > 0:
new_plugins = [] new_plugins = []
for _ in plugins: for _ in plugins:
if _ not in plugin_except_list: if _ not in plugin_except_list:

View File

@@ -146,6 +146,13 @@ function globalSendCommand(command, arg1, arg2, arg3, modal_title, callback) {
}); });
} }
function shutdown_confirm() {
$("#confirm_title").html("종료 확인");
$("#confirm_body").html("종료 하시겠습니까?");
$('#confirm_button').attr('onclick', 'window.location.href = "/system/shutdown";');
$("#confirm_modal").modal();
}
/////////////////////////////////////// ///////////////////////////////////////
// 파일 선택 모달 // 파일 선택 모달

View File

@@ -166,12 +166,7 @@ function global_relay_test(remote) {
function shutdown_confirm() {
document.getElementById("confirm_title").innerHTML = "종료 확인";
document.getElementById("confirm_body").innerHTML = "종료 하시겠습니까?";
$('#confirm_button').attr('onclick', 'window.location.href = "/system/shutdown";');
$("#confirm_modal").modal();
}
$("#video_modal").on('hidden.bs.modal', function () { $("#video_modal").on('hidden.bs.modal', function () {
document.getElementById("video_modal_video").pause(); document.getElementById("video_modal_video").pause();

View File

@@ -19,13 +19,10 @@
{% 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>
{% else %} {% else %}
<!--{{ category }}--> <!--{{ category }}-->
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{category['name']}}</a> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{category['name']}}</a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% for category_child in category['list'] %} {% for category_child in category['list'] %}
{% if category_child['uri'] == 'setting' %} {% if category_child['uri'] == 'setting' %}
<li><a class="dropdown-item" href="#" style="font-size: .850rem; font-weight:bold">{{category_child['name']}}</a> <li><a class="dropdown-item" href="#" style="font-size: .850rem; font-weight:bold">{{category_child['name']}}</a>
@@ -35,8 +32,6 @@
{% endfor %} {% endfor %}
</ul> </ul>
</li> </li>
{% elif category_child['uri'] == '-' %} {% elif category_child['uri'] == '-' %}
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
{% elif 'uri' in category_child and category_child['uri'].startswith('http') %} {% elif 'uri' in category_child and category_child['uri'].startswith('http') %}
@@ -45,6 +40,8 @@
{% else %} {% else %}
<a class="dropdown-item" href="{{ category_child['uri'] }}" style="font-size: .850rem; font-weight:bold">{{ category_child['name'] }}</a> <a class="dropdown-item" href="{{ category_child['uri'] }}" style="font-size: .850rem; font-weight:bold">{{ category_child['name'] }}</a>
{% endif %} {% endif %}
{% elif 'uri' in category_child and category_child['uri'].startswith('javascript') %}
<a class="dropdown-item" href="{{ category_child['uri'] }}" style="font-size: .850rem; font-weight:bold">{{ category_child['name'] }}</a>
{% else %} {% else %}
{% if category_child['uri'] == menu[0] %} {% if category_child['uri'] == menu[0] %}
<a class="dropdown-item active" href="/{{ category_child['uri'] }}" style="font-size: .850rem; font-weight:bold">{{ category_child['name'] }}</a> <a class="dropdown-item active" href="/{{ category_child['uri'] }}" style="font-size: .850rem; font-weight:bold">{{ category_child['name'] }}</a>

View File

@@ -1,26 +1,25 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
######################################################### #########################################################
# python # python
import os
import traceback
import logging
import json import json
import zipfile import logging
import time import os
import platform import platform
import time
import traceback
import zipfile
# third-party # third-party
import requests import requests
# sjva 공용 # sjva 공용
from framework import frame, app, path_data, logger from framework import app, frame, logger, path_data
from framework.util import Util from framework.util import Util
from support.base.process import SupportProcess from support.base.process import SupportProcess
# 패키지
from .model import ModelSetting
import system import system
# 패키지
from .model import ModelSetting
class LogicPlugin(object): class LogicPlugin(object):
@@ -153,7 +152,7 @@ class LogicPlugin(object):
return return
if frame.config['debug'] == True: if frame.config['debug'] == True:
return return
if frame.config['plugin_update'] != True: if frame.config.get('plugin_update', True) != True:
return return
custom_path = os.path.join(path_data, 'plugins') custom_path = os.path.join(path_data, 'plugins')
@@ -213,6 +212,7 @@ class LogicPlugin(object):
pass pass
if zip_filename and zip_filename != '': if zip_filename and zip_filename != '':
import zipfile import zipfile
from tool_base import ToolBaseFile from tool_base import ToolBaseFile
zip_filepath = os.path.join(path_data, 'tmp', zip_filename) zip_filepath = os.path.join(path_data, 'tmp', zip_filename)
extract_filepath = os.path.join(path_data, 'tmp', name) extract_filepath = os.path.join(path_data, 'tmp', name)

View File

@@ -1,6 +1,8 @@
import random import random
import string import string
from support.base.file import SupportFile
from .setup import * from .setup import *
name = 'setting' name = 'setting'
@@ -16,14 +18,19 @@ class ModuleSetting(PluginModuleBase):
'web_title': 'Home', 'web_title': 'Home',
'use_apikey': 'False', 'use_apikey': 'False',
'apikey': ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)), 'apikey': ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)),
f'restart_interval': f'{random.randint(0,59)} {random.randint(1,23)} * * *', f'restart_interval': f'{random.randint(0,59)} {random.randint(1,23)} * * *',
'theme' : 'Cerulean', 'theme' : 'Cerulean',
'log_level' : '20', 'log_level' : '20',
'plugin_dev_path': os.path.join(F.config['path_data'], 'dev'), 'plugin_dev_path': os.path.join(F.config['path_data'], 'dev'),
'system_start_time': '', 'system_start_time': '',
# notify
'notify_telegram_use' : 'False',
'notify_telegram_token' : '',
'notify_telegram_chat_id' : '',
'notify_telegram_disable_notification' : 'False',
'notify_discord_use' : 'False',
'notify_discord_webhook' : '',
'notify_advaned_use' : 'False',
} }
def __init__(self, P): def __init__(self, P):
@@ -33,6 +40,18 @@ class ModuleSetting(PluginModuleBase):
def process_menu(self, page, req): def process_menu(self, page, req):
arg = P.ModelSetting.to_dict() arg = P.ModelSetting.to_dict()
try: try:
if page == 'config':
arg['config.yaml'] = SupportFile.read_file(F.config['config_filepath'])
arg['config_filepath'] = F.config['config_filepath']
elif page == 'export':
arg['export_filepath'] = F.config['export_filepath']
if F.config['exist_export']:
arg['export.sh'] = SupportFile.read_file(export)
else:
arg['export.sh'] = "export.sh 파일이 없습니다."
elif page == 'menu':
arg['menu_yaml_filepath'] = F.config['menu_yaml_filepath']
arg['menu.yaml'] = SupportFile.read_file(arg['menu_yaml_filepath'])
return render_template(f'{__package__}_{name}_{page}.html', arg=arg) return render_template(f'{__package__}_{name}_{page}.html', arg=arg)
except Exception as e: except Exception as e:
P.logger.error(f'Exception:{str(e)}') P.logger.error(f'Exception:{str(e)}')
@@ -40,8 +59,31 @@ class ModuleSetting(PluginModuleBase):
return render_template('sample.html', title=f"{__package__}/{name}/{page}") return render_template('sample.html', title=f"{__package__}/{name}/{page}")
def process_command(self, command, arg1, arg2, arg3, req): def process_command(self, command, arg1, arg2, arg3, req):
ret = {'ret':'success'}
if command == 'apikey_generate': if command == 'apikey_generate':
return jsonify(''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10))) return jsonify(''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)))
elif command == 'config_save':
SupportFile.write_file(F.config['config_filepath'], arg1 )
ret['msg'] = '저장하였습니다.'
elif command == 'export_save':
if F.config['exist_export']:
SupportFile.write_file(F.config['export_filepath'], arg1 )
ret['msg'] = '저장하였습니다.'
else:
ret['ret'] = 'warning'
ret['msg'] = 'export.sh 파일이 없습니다.'
elif command == 'menu_save':
SupportFile.write_file(F.config['menu_yaml_filepath'], arg1 )
ret['msg'] = '저장하였습니다.'
from framework.init_menu import MenuManager
MenuManager.init_menu()
F.socketio.emit("refresh", {}, namespace='/framework', broadcast=True)
elif command == 'notify_test':
if arg1 == 'telegram':
pass
return jsonify(ret)
def plugin_load(self): def plugin_load(self):
@@ -56,6 +98,7 @@ class ModuleSetting(PluginModuleBase):
self.__set_restart_scheduler() self.__set_restart_scheduler()
self.__set_scheduler_check_scheduler() self.__set_scheduler_check_scheduler()
F.get_recent_version() F.get_recent_version()
except Exception as e: except Exception as e:
P.logger.error(f'Exception:{str(e)}') P.logger.error(f'Exception:{str(e)}')
P.logger.error(traceback.format_exc()) P.logger.error(traceback.format_exc())

View File

@@ -9,23 +9,23 @@ __menu = {
{'uri': 'basic', 'name': '기본'}, {'uri': 'basic', 'name': '기본'},
{'uri': 'auth', 'name': '인증'}, {'uri': 'auth', 'name': '인증'},
{'uri': 'web', 'name': ''}, {'uri': 'web', 'name': ''},
{'uri': 'env', 'name': '시스템'}, {'uri': 'menu', 'name': '메뉴 구성'},
{'uri': 'menu', 'name': '메뉴'}, {'uri': 'config', 'name': 'config.yaml 파일'},
{'uri': 'export', 'name': 'export.sh 파일'},
{'uri': 'notify', 'name': '알림'}, {'uri': 'notify', 'name': '알림'},
{'uri': 'crypt', 'name': '암호화'},
], ],
}, },
{'uri': 'plugin', 'name': '플러그인'},
{ {
'uri': 'plugin', 'uri': 'tool',
'name': '플러그인' 'name': '시스템 툴',
}, 'list': [
{ {'uri': 'celery', 'name': 'celery 테스트'},
'uri': 'python', {'uri': 'python', 'name': 'Python'},
'name': 'Python' {'uri': 'db', 'name': 'DB'},
}, {'uri': 'crypt', 'name': '암호화'},
{ ]
'uri': 'db',
'name': 'DB'
}, },
{ {
'uri': 'log', 'uri': 'log',
@@ -35,6 +35,19 @@ __menu = {
} }
import os
from framework import F
export = os.path.join(F.config['path_app'], 'export.sh')
if os.path.exists(export) == False:
for mod in __menu['list']:
if mod['uri'] == 'setting':
del mod['list'][5]
setting = { setting = {
'filepath' : __file__, 'filepath' : __file__,
'use_db': True, 'use_db': True,

View File

@@ -0,0 +1,36 @@
{% extends "base.html" %}
{% block content %}
<div>
{{ macros.m_button_group([['saveBtn', '파일 저장']])}}
{{ macros.m_row_start('5') }}
{{ macros.m_row_end() }}
{{ macros.m_hr() }}
{{ macros.info_text_and_buttons('config_filepath', '파일 위치', [['globalEditBtn', '편집기에서 열기', [('file',arg['config_filepath'])]]], value=arg['config_filepath']) }}
{{ macros.setting_input_textarea('config', 'config.yaml', desc=['',
'App을 재시작 해야 적용됩니다.',
'',
'path_data : 필수. 데이터 폴더 경로. 윈도우의 경우 폴더 구분 기호 \ 를 두개 사용',
'use_gevent : 생략시 true',
'use_celery : 생략시 true',
'redis_port : 생략시 6379',
'port : 생략시 설정에 있는 DB port 사용. 설정시 DB 값보다 우선 사용',
'debug : 생략시 false. true인 경우 소스 수정시 재로딩',
'plugin_update : 생략시 true. 환경변수 UPDATE_STOP, PLUGIN_UPDATE_FROM_PYTHON 값이 설정된 경우에도 적용',
'running_type : termux, entware인 경우에만 입력',
'plugin_loading_only_devpath : true인 경우 플러그인 개발 폴더에 있는 것들만 로딩',
'plugin_loading_list : 생략시 적용 안함. 로딩할 모듈 패키지명 리스트',
'plugin_except_list : 생략시 적용 안함. 로딩 제외할 모듈 패키지명 리스트',
],
value=arg['config.yaml'], row='20') }}
</div>
<script type="text/javascript">
$("body").on('click', '#saveBtn', function(e){
e.preventDefault();
globalSendCommand('config_save', $('#config').val());
});
</script>
{% endblock %}

View File

@@ -1,144 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div>
<nav>
{{ macros.m_tab_head_start() }}
{{ macros.m_tab_head2('system', '시스템 변수', true) }}
{{ macros.m_tab_head2('celery', 'celery 상태', false) }}
{{ macros.m_tab_head_end() }}
</nav>
<div class="tab-content" id="nav-tabContent">
{{ macros.m_tab_content_start('system', true) }}
<form id='setting' name='setting'>
{{ macros.setting_input_textarea('export', '환경변수', desc=['',
'도커를 재시작하거나 sjva3.sh 스크립트를 재시작해야 적용됩니다.',
'구동 환경에 따라 사용하는 변수가 다릅니다.',
'',
'USE_CELERY : 멀티프로세싱 작업 사용 여부 설정. true or false',
'CELERY_WORKER_COUNT : 작업 프로세스 개수.',
'USE_GEVENT : 비동기 라이브러리 사용 설정. true or false. 클수록 메모리를 많이 소비하는 대신 더 원할하게 동작', '',
'REDIS_PORT : celery에서 사용하는 redis port. 특별한 경우외에는 변경하지 마세요',
'SJVA_PORT : 이 변수가 있는 경우 DB값을 무시하고 port로 설정',
],
value=arg['export'], row='10') }}
{{ macros.setting_button([['setting_save_btn', '저장'], ['shutdown_btn', '시스템 종료']]) }}
</form>
</form>
{{ macros.m_tab_content_end() }}
{{ macros.m_tab_content_start('celery', false) }}
{{ macros.setting_button([['celery_test_btn', 'Celery Test'], ['worker_start_btn', '워커 재시작'], ['ps_btn', '프로세스 목록']]) }}
{{ macros.m_tab_content_end() }}
</div><!--tab-content-->
</div> <!--전체-->
<script type="text/javascript">
var package_name = "{{arg['package_name']}}";
var sub = "{{arg['sub'] }}";
$(document).ready(function(){
});
$("body").on('click', '#setting_save_btn', function(e){
e.preventDefault();
var formData = get_formdata('#setting');
$.ajax({
url: '/' + package_name + '/ajax/' + sub + '/setting_save',
type: "POST",
cache: false,
data: formData,
dataType: "json",
success: function (ret) {
if (ret) {
$.notify('<strong>시스템 변수를 저장하였습니다.</strong>', {
type: 'success'
});
} else {
$.notify('<strong>시스템 변수 저장에 실패하였습니다.</strong>', {
type: 'warning'
});
}
}
});
});
$("body").on('click', '#shutdown_btn', function(e){
e.preventDefault();
shutdown_confirm();
});
$("body").on('click', '#celery_test_btn', function(e){
e.preventDefault();
$.ajax({
url: '/' + package_name + '/ajax/' + sub + '/celery_test',
type: "POST",
cache: false,
data: {},
dataType: "json",
success: function (ret) {
if (ret.ret == 'success') {
$.notify('<strong>'+ ret.data+'</strong>', {
type: 'success'
});
} else if (ret.ret == 'timeout' || ret.ret == 'no_celery') {
$.notify('<strong>'+ ret.data+'</strong>', {
type: 'warning'
});
}
//m_modal(ret)
}
});
});
$("body").on('click', '#ps_btn', function(e){
e.preventDefault();
$.ajax({
url: '/' + package_name + '/ajax/' + sub + '/ps',
type: "POST",
cache: false,
data: {},
dataType: "json",
success: function (ret) {
}
});
});
$("body").on('click', '#worker_start_btn', function(e){
e.preventDefault();
$.ajax({
url: '/' + package_name + '/ajax/' + sub + '/worker_start',
type: "POST",
cache: false,
data: {},
dataType: "json",
success: function (ret) {
if (ret.ret == 'success') {
$.notify('<strong>'+ ret.data+'</strong>', {
type: 'success'
});
} else if (ret.ret == 'timeout' || ret.ret == 'no_celery' || ret.ret == 'not_registered') {
$.notify('<strong>'+ ret.data+'</strong>', {
type: 'warning'
});
}
//m_modal(ret)
}
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,31 @@
{% extends "base.html" %}
{% block content %}
<div>
{{ macros.m_button_group([['saveBtn', '파일 저장']])}}
{{ macros.m_row_start('5') }}
{{ macros.m_row_end() }}
{{ macros.m_hr() }}
{{ macros.info_text_and_buttons('export_filepath', '파일 위치', [['globalEditBtn', '편집기에서 열기', [('file',arg['export_filepath'])]]], value=arg['export_filepath']) }}
{{ macros.setting_input_textarea('export', 'export.sh', desc=['',
'도커를 재시작하거나 flaskfarm.sh 스크립트를 재시작해야 적용됩니다.',
'구동 환경에 따라 사용하는 변수가 다릅니다.',
'',
'REDIS_PORT : celery에서 사용하는 redis port.',
'CELERY_WORKER_COUNT : celery 작업 프로세스 개수.',
'UPDATE_STOP : true인 경우 앱과 플러그인을 업데이트 하지 않음',
'DOCKER_NONSTOP : true인 경우 App이 종료되어서 도커가 중단되지 않음. 개발시 사용',
'PLUGIN_UPDATE_FROM_PYTHON : false인 경우 shell에서 업데이트 실행. true인 경우 앱에서 업데이트 실행.',
],
value=arg['export.sh'], row='20') }}
</div>
<script type="text/javascript">
$("body").on('click', '#saveBtn', function(e){
e.preventDefault();
globalSendCommand('export_save', $('#export').val());
});
</script>
{% endblock %}

View File

@@ -0,0 +1,27 @@
{% extends "base.html" %}
{% block content %}
<div>
{{ macros.m_button_group([['saveBtn', '파일 저장']])}}
{{ macros.m_row_start('5') }}
{{ macros.m_row_end() }}
{{ macros.m_hr() }}
{{ macros.info_text_and_buttons('menu_yaml_filepath', '파일 위치', [['globalEditBtn', '편집기에서 열기', [('file',arg['menu_yaml_filepath'])]]], value=arg['menu_yaml_filepath']) }}
{{ macros.setting_input_textarea('menu', 'menu.yaml', desc=['',
'uri 값',
'플러그인 : 패키지 이름',
'바로가기 : http로 시작하는 링크. 기본값은 새창에서 열기. target: "__self" 값이 있는 경우 기본창',
'구분선 : -',
],
value=arg['menu.yaml'], row='30') }}
</div>
<script type="text/javascript">
$("body").on('click', '#saveBtn', function(e){
e.preventDefault();
globalSendCommand('menu_save', $('#menu').val());
});
</script>
{% endblock %}

View File

@@ -1,10 +1,10 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<div> <div>
{{ macros.m_button_group([['globalSettingSaveBtn', '설정 저장']])}} {{ macros.m_button_group([['globalSettingSaveBtn', '설정 저장']])}}
{{ macros.m_row_start('5') }} {{ macros.m_row_start('5') }}
{{ macros.m_row_end() }} {{ macros.m_row_end() }}
{{ macros.m_hr() }}
<nav> <nav>
{{ macros.m_tab_head_start() }} {{ macros.m_tab_head_start() }}
{{ macros.m_tab_head2('basic', 'Basic', true) }} {{ macros.m_tab_head2('basic', 'Basic', true) }}
@@ -33,7 +33,7 @@
{{ macros.m_tab_content_start('advanced', false) }} {{ macros.m_tab_content_start('advanced', false) }}
{{ macros.setting_checkbox('notify_advaned_use', '사용', value=arg['notify_advaned_use'], desc=['충분히 내용 숙지하고 사용하세요.', '사용시 기본설정은 무시됩니다.']) }} {{ macros.setting_checkbox('notify_advaned_use', '사용', value=arg['notify_advaned_use'], desc=['충분히 내용 숙지하고 사용하세요.', '사용시 기본설정은 무시됩니다.']) }}
<div id="notify_advaned_use_div" class="collapse"> <div id="notify_advaned_use_div" class="collapse">
{{ macros.setting_input_textarea('notify_advaned_policy', '정책', value=arg['notify_advaned_policy'], row='30') }} {{ macros.setting_input_textarea('_notify_advaned_policy', '정책', value=arg['notify_advaned_policy'], row='30') }}
{{ macros.setting_input_text_and_buttons('tmp_text_advanced', 'Test', [['tmp_advanced_test_btn', '전송']], value='테스트 메시지입니다.', col='9', desc=['메시지 ID = 형식', '형식의 구분자 |', '텔레그램 : bot_token,chat_id | 디스코드 : 웹훅 URL', '예) DEFAULT = 794150118:AAEAAAAAAAAAAAAAAA,186485141|https://discordapp.com/api/webhooks/626295849....', '모든 알림을 텔레그램과 디스코드에 보냄']) }} {{ macros.setting_input_text_and_buttons('tmp_text_advanced', 'Test', [['tmp_advanced_test_btn', '전송']], value='테스트 메시지입니다.', col='9', desc=['메시지 ID = 형식', '형식의 구분자 |', '텔레그램 : bot_token,chat_id | 디스코드 : 웹훅 URL', '예) DEFAULT = 794150118:AAEAAAAAAAAAAAAAAA,186485141|https://discordapp.com/api/webhooks/626295849....', '모든 알림을 텔레그램과 디스코드에 보냄']) }}
{{ macros.setting_input_text('tmp_message_id', 'Test Message ID', value='DEFAULT') }} {{ macros.setting_input_text('tmp_message_id', 'Test Message ID', value='DEFAULT') }}
</div> </div>

125
lib/tool/notify.py Normal file
View File

@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
#########################################################
import os
import traceback
from discord_webhook import DiscordEmbed, DiscordWebhook
from framework import F
from telepot2 import Bot, glance
from telepot2.loop import MessageLoop
from . import logger
class ToolNotify(object):
@classmethod
def send_message(cls, text, message_id=None, image_url=None):
if F.SystemModelSetting.get_bool('notify_advaned_use'):
return cls.send_advanced_message(text, image_url=image_url, message_id=message_id)
else:
if F.SystemModelSetting.get_bool('notify_telegram_use'):
cls.send_telegram_message(text, image_url=image_url, bot_token=F.SystemModelSetting.get('notify_telegram_token'), chat_id=F.SystemModelSetting.get('notify_telegram_chat_id'))
if F.SystemModelSetting.get_bool('notify_discord_use'):
cls.send_discord_message(text, image_url=image_url, webhook_url=F.SystemModelSetting.get('notify_discord_webhook'))
@classmethod
def send_advanced_message(cls, text, image_url=None, policy=None, message_id=None):
from system.model import ModelSetting as SystemModelSetting
try:
if policy is None:
policy = SystemModelSetting.get('notify_advaned_policy')
if message_id is None:
message_id = 'DEFAULT'
policy_list = cls._make_policy_dict(policy)
#logger.debug(policy_list)
#logger.debug(message_id)
if message_id.strip() not in policy_list:
message_id = 'DEFAULT'
for tmp in policy_list[message_id.strip()]:
if tmp.startswith('http'):
cls.send_discord_message(text, image_url=image_url, webhook_url=tmp)
elif tmp.find(',') != -1:
tmp2 = tmp.split(',')
cls.send_telegram_message(text, image_url=image_url, bot_token=tmp2[0], chat_id=tmp2[1])
return True
except Exception as exception:
logger.error('Exception:%s', exception)
logger.error(traceback.format_exc())
#logger.debug('Chatid:%s', chat_id)
return False
@classmethod
def _make_policy_dict(cls, policy):
try:
ret = {}
for t in policy.split('\n'):
t = t.strip()
if t == '' or t.startswith('#'):
continue
else:
tmp2 = t.split('=')
if len(tmp2) != 2:
continue
ret[tmp2[0].strip()] = [x.strip() for x in tmp2[1].split('|')]
return ret
except Exception as exception:
logger.error('Exception:%s', exception)
logger.error(traceback.format_exc())
return False
@classmethod
def send_discord_message(cls, text, image_url=None, webhook_url=None):
from system.model import ModelSetting as SystemModelSetting
try:
if webhook_url is None:
webhook_url = SystemModelSetting.get('notify_discord_webhook')
webhook = DiscordWebhook(url=webhook_url, content=text)
if image_url is not None:
embed = DiscordEmbed()
embed.set_timestamp()
embed.set_image(url=image_url)
webhook.add_embed(embed)
response = webhook.execute()
#discord = response.json()
#logger.debug(discord)
return True
except Exception as exception:
logger.error('Exception:%s', exception)
logger.error(traceback.format_exc())
return False
@classmethod
def send_telegram_message(cls, text, bot_token=None, chat_id=None, image_url=None, disable_notification=None):
from system.model import ModelSetting as SystemModelSetting
try:
if bot_token is None:
bot_token = SystemModelSetting.get('notify_telegram_token')
if chat_id is None:
chat_id = SystemModelSetting.get('notify_telegram_chat_id')
if disable_notification is None:
disable_notification = SystemModelSetting.get_bool('notify_telegram_disable_notification')
bot = Bot(bot_token)
if image_url is not None:
#bot.sendPhoto(chat_id, text, caption=caption, disable_notification=disable_notification)
bot.sendPhoto(chat_id, image_url, disable_notification=disable_notification)
bot.sendMessage(chat_id, text, disable_web_page_preview=True, disable_notification=disable_notification)
#elif mime == 'video':
# bot.sendVideo(chat_id, text, disable_notification=disable_notification)
return True
except Exception as exception:
logger.error('Exception:%s', exception)
logger.error(traceback.format_exc())
logger.debug('Chatid:%s', chat_id)
return False

View File

@@ -12,7 +12,9 @@ sys.path.insert(1, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'lib
try: try:
from framework import frame from framework import frame
if __name__ in ['__main__', 'flaskfarm.main']:
# flaskfarm.main : 패키지로 실행. 패키지로 celry 실행 체크
if __name__ in ['__main__', 'flaskfarm.main'] and sys.argv[0].endswith('celery') == False:
frame.start() frame.start()
else: else:
app = frame.app app = frame.app