This commit is contained in:
flaskfarm
2022-10-19 16:40:29 +09:00
parent 97b1f6bf48
commit c146e03cf1
26 changed files with 965 additions and 246 deletions

View File

@@ -57,6 +57,7 @@ class Framework:
def __initialize(self):
os.environ["PYTHONUNBUFFERED"] = "1"
os.environ['FF'] = "true"
self.__config_initialize("first")
self.__make_default_dir()
@@ -118,7 +119,9 @@ class Framework:
for package_name in plugins:
db_path = os.path.join(self.config['path_data'], 'db', f'{package_name}.db')
self.app.config['SQLALCHEMY_BINDS'][package_name] = f'sqlite:///{db_path}'
self.db = SQLAlchemy(self.app, session_options={"autoflush": False})
self.db = SQLAlchemy(self.app, session_options={"autoflush": False, "expire_on_commit": False})
#with self.app.app_context():
# self.db.session.expunge_all()
def __init_celery(self):
@@ -222,6 +225,7 @@ class Framework:
from . import init_route, log_viewer
self.__make_default_logger()
self.__config_initialize("last")
self.logger.info('### LAST')
self.logger.info(f"### PORT: {self.config.get('port')}")
self.logger.info('### Now you can access App by webbrowser!!')
@@ -266,6 +270,18 @@ class Framework:
self.app.config['JSON_AS_ASCII'] = False
elif mode == 'system_loading_after':
pass
elif mode == 'last':
db_foder = os.path.join(self.config['path_data'], 'db')
for name in os.listdir(db_foder):
if name.endswith('.db'):
db_filepath = os.path.join(db_foder, name)
try:
if os.stat(db_filepath).st_size == 0:
os.remove(db_filepath)
self.logger.debug(f"REMOVE {db_filepath}")
except:
pass
def __init_define(self):
@@ -372,7 +388,7 @@ class Framework:
###################################################
# 로그
###################################################
def get_logger(self, name):
def get_logger(self, name, from_command=False):
logger = logging.getLogger(name)
if not logger.handlers:
level = logging.DEBUG
@@ -396,25 +412,26 @@ class Framework:
except:
pass
logger.setLevel(level)
file_formatter = logging.Formatter(u'[%(asctime)s|%(levelname)s|%(filename)s:%(lineno)s] %(message)s')
def customTime(*args):
utc_dt = utc.localize(datetime.utcnow())
my_tz = timezone("Asia/Seoul")
converted = utc_dt.astimezone(my_tz)
return converted.timetuple()
if from_command == False:
file_formatter = logging.Formatter(u'[%(asctime)s|%(levelname)s|%(filename)s:%(lineno)s] %(message)s')
else:
file_formatter = logging.Formatter(u'[%(asctime)s] %(message)s')
file_formatter.converter = customTime
file_max_bytes = 1 * 1024 * 1024
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)
streamHandler = logging.StreamHandler()
# handler에 fommater 세팅
fileHandler.setFormatter(file_formatter)
streamHandler.setFormatter(CustomFormatter())
# Handler를 logging에 추가
logger.addHandler(fileHandler)
logger.addHandler(streamHandler)
if from_command == False:
streamHandler = logging.StreamHandler()
streamHandler.setFormatter(CustomFormatter())
logger.addHandler(streamHandler)
return logger

View File

@@ -1,7 +1,7 @@
import os
import shutil
from support import SupportYaml
from support import SupportYaml, d
from framework import F
@@ -89,4 +89,5 @@ class MenuManager:
@classmethod
def get_menu_map(cls):
#F.logger.warning(d(cls.menu_map))
return cls.menu_map

View File

@@ -128,4 +128,3 @@ def videojs():
def connect():
pass

View File

@@ -53,7 +53,7 @@ $.notify({
},
offset: 20,
spacing: 10,
z_index: 1031,
z_index: 3000,
delay: 10000,
timer: 1000,
url_target: '_blank',
@@ -81,7 +81,7 @@ $.notify({
function notify(msg, type) {
$.notify('<strong>' + msg + '</strong>', {type: type});
$.notify('<strong>' + msg + '</strong>', {type: type, z_index: 3000});
}
// 메뉴 제거
@@ -289,8 +289,8 @@ $.extend(
{
var form = '';
$.each( args, function( key, value ) {
//console.log(key);
//console.log(value);
console.log(key);
console.log(value);
value = value.split('"').join('\"')
form += '<input type="hidden" name="'+key+'" value="'+value+'">';
});

View File

@@ -1,5 +1,6 @@
// global socketio
$(document).ready(function(){
ResizeTextArea();
});
$(document).ready(function(){
@@ -23,6 +24,7 @@ frameSocket.on('notify', function(data){
target: '_self'
},{
type: data['type'],
z_index: 2000,
});
});
@@ -317,5 +319,17 @@ $("body").on('click', '#command_modal_input_btn', function(e) {
});
});
$(window).resize(function() {
ResizeTextArea();
});
function ResizeTextArea() {
ClientHeight = window.innerHeight
$("#command_modal").height(ClientHeight-100);
$("#command_modal_textarea").height(ClientHeight-380);
}
///////////////////////////////////////

View File

@@ -1,4 +1,4 @@
function m_button_group(h) {
function j_button_group(h) {
var str = '<div class="btn-group btn-group-sm flex-wrap mr-2" role="group">';
str += h
str += '</div>';
@@ -6,7 +6,7 @@ function m_button_group(h) {
}
// primary, secondary, success, danger, warning, info, light, dark, white
function m_button(id, text, data={}, color='success', outline=true, small=false) {
function j_button(id, text, data={}, color='success', outline=true, small=false) {
var str = '<button id="'+id+'" name="'+id+'" class="btn btn-sm btn';
if (outline) {
str += '-outline';
@@ -24,18 +24,48 @@ function m_button(id, text, data={}, color='success', outline=true, small=false)
return str;
}
function m_button_small(id, text, data={}, color='success', outline=true) {
return m_button(id, text, data, color, outline, true);
function j_button_small(id, text, data={}, color='success', outline=true) {
return j_button(id, text, data, color, outline, true);
}
function j_row_start(padding='10', align='center') {
var str = '<div class="row" style="padding-top: '+padding+'px; padding-bottom:'+padding+'px; align-items:'+align+';">';
return str;
}
function j_col(w, h, align='left') {
var str = '<div class="col-sm-' + w + ' " style="text-align: '+align+'; word-break:break-all;">';
str += h;
str += '</div>';
return str;
}
function j_col_wide(w, h, align='left') {
var str = '<div class="col-sm-' + w + ' " style="padding:0px; margin:0px; text-align: '+align+'; word-break:break-all;">';
str += h;
str += '</div>';
return str;
}
function j_row_end() {
var str = '</div>';
return str;
}
function j_hr(margin='5') {
var str = '<hr style="width: 100%; margin:'+margin+'px;" />';
return str;
}
function j_hr_black() {
var str = '<hr style="width: 100%; color: black; height: 2px; background-color:black;" />';
return str;
}
@@ -119,10 +149,9 @@ function m_table(id, heads) {
function m_row_start(padding='10', align='center') {
var str = '<div class="row" style="padding-top: '+padding+'px; padding-bottom:'+padding+'px; align-items:'+align+';">';
return str;
}
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;
@@ -139,39 +168,17 @@ function m_row_start_color2(padding='10', align='center') {
return str;
}
function m_row_end() {
var str = '</div>';
return str;
}
//border
function m_col(w, h, align='left') {
var str = '<div class="col-sm-' + w + ' " style="text-align: '+align+'; word-break:break-all;">';
str += h
str += '</div>';
return str
}
function m_col_wide(w, h, align='left') {
var str = '<div class="col-sm-' + w + ' " style="padding:0px; margin:0px; text-align: '+align+'; word-break:break-all;">';
str += h
str += '</div>';
return str
}
function m_hr(margin='5') {
var str = '<hr style="width: 100%; margin:'+margin+'px;" />';
return str;
}
function m_hr_black() {
var str = '<hr style="width: 100%; color: black; height: 2px; background-color:black;" />';
return str;
}
// 체크박스는 자바로 하면 on/off 스크립트가 안먹힘.

View File

@@ -11,14 +11,7 @@ function m_row_start_hover(padding='10', align='center') {
function m_row_start_top(padding='10') {
return m_row_start(padding, 'top');
}
function m_row_start_color(padding='10', align='center', color='') {
var str = '<div class="row" style="padding-top: '+padding+'px; padding-bottom:'+padding+'px; align-items:'+align+'; background-color:'+color+'">';
return str;
}
function m_row_start_color2(padding='10', align='center') {
var str = '<div class="row bg-dark text-white" style="padding-top: '+padding+'px; padding-bottom:'+padding+'px; align-items:'+align+';">';
return str;
}
function m_row_end() {
var str = '</div>';

View File

@@ -36,17 +36,17 @@
$(document).ready(function() {
setWide();
$('#loading').show();
ResizeTextArea()
ResizeTextAreaLog()
})
function ResizeTextArea() {
function ResizeTextAreaLog() {
ClientHeight = window.innerHeight
$("#log").height(ClientHeight-240);
$("#add").height(ClientHeight-260);
}
$(window).resize(function() {
ResizeTextArea();
ResizeTextAreaLog();
});

View File

@@ -140,6 +140,65 @@
{{ setting_bottom(desc) }}
{% endmacro %}
{% macro setting_input_textarea_wide(id, left, value='', col='12', row='3', desc='', disabled=False, padding='10') %}
<div class='row' style="padding-top: {{padding}}px; padding-bottom:{{padding}}px; align-items: center;">
<div class='col-sm-12'>
{% if left != '' %}
<div class="input-group col-sm-{{col}}">
<strong>{{ left }}</strong>
</div>
{% endif %}
<div class="input-group col-sm-{{col}}">
<textarea id="{{id}}" name="{{id}}" class="col-md-12" rows="{{row}}"
{% if disabled %}
disabled
{% endif %}
>{{ value }}</textarea>
</div>
{% if desc is not none %}
<div style="padding-left:20px; padding-top:{{padding_top}}}px;">
<em>
{% if desc is string %}
{{ desc }}
{% elif desc is iterable %}
{% for d in desc %}
{{ d }}<br>
{% endfor %}
{% endif %}
</em>
</div>
{% endif %}
</div>
</div>
{% endmacro %}
{% macro setting_input_textarea_and_buttons(id, left, buttons, value='', col='9', row='3', desc='', disabled=False) %}
{{ setting_top(left) }}
<div class="input-group col-sm-{{col}}">
<textarea id="{{id}}" name="{{id}}" class="col-md-12" rows="{{row}}"
{% if disabled %}
disabled
{% endif %}
>{{ value }}</textarea>
<div class="btn-group btn-group-sm flex-wrap mr-2" role="group" style="padding-left:5px; padding-top:0px">
{% for b in buttons %}
<button id="{{b[0]}}" class="btn btn-sm btn-outline-primary"
{% if b|length > 2 %}
{% for d in b[2] %}
data-{{d[0]}}="{{d[1]}}""
{% endfor %}
{% endif %}
>{{b[1]}}</button>
{% endfor %}
</div>
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
<!-- TEXTAREA -->
<!-- 라디오버튼 -->
{% macro setting_radio_with_value(id, title, radios, value=None, desc=None, disabled=False) %}
{{ setting_top(title) }}
@@ -635,62 +694,8 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
{% macro setting_input_textarea_wide(id, left, value='', col='12', row='3', desc='', disabled=False, padding='10') %}
<div class='row' style="padding-top: {{padding}}px; padding-bottom:{{padding}}px; align-items: center;">
<div class='col-sm-12'>
{% if left != '' %}
<div class="input-group col-sm-{{col}}">
<strong>{{ left }}</strong>
</div>
{% endif %}
<div class="input-group col-sm-{{col}}">
<textarea id="{{id}}" name="{{id}}" class="col-md-12" rows="{{row}}"
{% if disabled %}
disabled
{% endif %}
>{{ value }}</textarea>
</div>
{% if desc is not none %}
<div style="padding-left:20px; padding-top:{{padding_top}}}px;">
<em>
{% if desc is string %}
{{ desc }}
{% elif desc is iterable %}
{% for d in desc %}
{{ d }}<br>
{% endfor %}
{% endif %}
</em>
</div>
{% endif %}
</div>
</div>
{% endmacro %}
{% macro setting_input_textarea_and_buttons(id, left, buttons, value='', col='9', row='3', desc='', disabled=False) %}
{{ setting_top(left) }}
<div class="input-group col-sm-{{col}}">
<textarea id="{{id}}" name="{{id}}" class="col-md-12" rows="{{row}}"
{% if disabled %}
disabled
{% endif %}
>{{ value }}</textarea>
<div class="btn-group btn-group-sm flex-wrap mr-2" role="group" style="padding-left:5px; padding-top:0px">
{% for b in buttons %}
<button id="{{b[0]}}" class="btn btn-sm btn-outline-primary"
{% if b|length > 2 %}
{% for d in b[2] %}
data-{{d[0]}}="{{d[1]}}""
{% endfor %}
{% endif %}
>{{b[1]}}</button>
{% endfor %}
</div>
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
<!--progress-bar-striped progress-bar-animated-->

View File

@@ -18,6 +18,8 @@
{% for category in menu_map %}
{% if 'uri' in category and category['uri'].startswith('http') %}
<li class="nav-item"> <a class="nav-link" href="{{ category['uri']}}" target="_blank">{{category['name']}}</a></li>
{% elif 'uri' in category and category['uri'].startswith('http') == False %}
<li class="nav-item"> <a class="nav-link" href="{{ category['uri']}}">{{category['name']}}</a></li>
{% else %}
<!--{{ category }}-->
<li class="nav-item dropdown">

View File

@@ -1 +1 @@
VERSION="4.0.26"
VERSION="4.0.27"

View File

@@ -241,3 +241,15 @@ class Logic(object):
self.P.logger.error(traceback.format_exc())
ret = {'ret' : 'danger', 'msg':str(e)}
return ret
def arg_to_dict(self, arg):
import urllib.parse
tmp = urllib.parse.unquote(arg)
tmps = tmp.split('&')
ret = {}
for tmp in tmps:
_ = tmp.split('=')
ret[_[0]] = _[1]
return ret

View File

@@ -19,8 +19,10 @@ class ModelBase(F.db.Model):
def save(self):
try:
F.db.session.add(self)
F.db.session.commit()
with F.app.app_context():
F.db.session.add(self)
F.db.session.commit()
return self
except Exception as e:
self.logger.error(f'Exception:{str(e)}')
self.logger.error(traceback.format_exc())
@@ -43,55 +45,59 @@ class ModelBase(F.db.Model):
paging['next_page'] = False
paging['current_page'] = current_page
paging['count'] = count
cls.logger.debug('paging : c:%s %s %s %s %s %s', count, paging['total_page'], paging['prev_page'], paging['next_page'] , paging['start_page'], paging['last_page'])
F.logger.debug('paging : c:%s %s %s %s %s %s', count, paging['total_page'], paging['prev_page'], paging['next_page'] , paging['start_page'], paging['last_page'])
return paging
except Exception as e:
cls.logger.error(f'Exception:{str(e)}')
cls.logger.error(traceback.format_exc())
F.logger.error(f'Exception:{str(e)}')
F.logger.error(traceback.format_exc())
@classmethod
def get_by_id(cls, id):
try:
return F.db.session.query(cls).filter_by(id=id).first()
with F.app.app_context():
return F.db.session.query(cls).filter_by(id=int(id)).first()
except Exception as e:
cls.logger.error(f'Exception:{str(e)}')
cls.logger.error(traceback.format_exc())
F.logger.error(f'Exception:{str(e)}')
F.logger.error(traceback.format_exc())
@classmethod
def get_list(cls, by_dict=False):
try:
tmp = F.db.session.query(cls).all()
if by_dict:
tmp = [x.as_dict() for x in tmp]
return tmp
with F.app.app_context():
tmp = F.db.session.query(cls).all()
if by_dict:
tmp = [x.as_dict() for x in tmp]
return tmp
except Exception as e:
cls.logger.error(f'Exception:{str(e)}')
cls.logger.error(traceback.format_exc())
F.logger.error(f'Exception:{str(e)}')
F.logger.error(traceback.format_exc())
@classmethod
def delete_by_id(cls, id):
try:
F.db.session.query(cls).filter_by(id=id).delete()
F.db.session.commit()
return True
with F.app.app_context():
F.db.session.query(cls).filter_by(id=int(id)).delete()
F.db.session.commit()
return True
except Exception as e:
cls.logger.error(f'Exception:{str(e)}')
cls.logger.error(traceback.format_exc())
F.logger.error(f'Exception:{str(e)}')
F.logger.error(traceback.format_exc())
return False
@classmethod
def delete_all(cls):
try:
F.db.session.query(cls).delete()
F.db.session.commit()
return True
with F.app.app_context():
F.db.session.query(cls).delete()
F.db.session.commit()
return True
except Exception as e:
cls.logger.error(f'Exception:{str(e)}')
cls.logger.error(traceback.format_exc())
F.logger.error(f'Exception:{str(e)}')
F.logger.error(traceback.format_exc())
return False
@@ -113,7 +119,7 @@ class ModelBase(F.db.Model):
query = cls.make_query(order=order, search=search, option1=option1, option2=option2)
count = query.count()
query = query.limit(page_size).offset((page-1)*page_size)
cls.logger.debug('cls count:%s', count)
F.logger.debug('cls count:%s', count)
lists = query.all()
ret['list'] = [item.as_dict() for item in lists]
ret['paging'] = cls.get_paging_info(count, page, page_size)
@@ -121,18 +127,19 @@ class ModelBase(F.db.Model):
if cls.model_setting is not None and cls.__tablename__ is not None:
cls.model_setting.set(f'{cls.__tablename__}_last_list_option', f'{order}|{page}|{search}|{option1}|{option2}')
except Exception as e:
cls.logger.error('Exception:%s', e)
cls.logger.error(traceback.format_exc())
cls.logger.error(f'{cls.__tablename__}_last_list_option ERROR!' )
F.logger.error('Exception:%s', e)
F.logger.error(traceback.format_exc())
F.logger.error(f'{cls.__tablename__}_last_list_option ERROR!' )
return ret
except Exception as e:
cls.logger.error('Exception:%s', e)
cls.logger.error(traceback.format_exc())
F.logger.error('Exception:%s', e)
F.logger.error(traceback.format_exc())
# 오버라이딩
@classmethod
def make_query(cls, order='desc', search='', option1='all', option2='all'):
query = F.db.session.query(cls)
return query
with F.app.app_context():
query = F.db.session.query(cls)
return query

View File

@@ -136,7 +136,7 @@ def default_route(P):
@login_required
def sub_ajax(module_name, page_name, command):
try:
ins_module = P.get_module(module_name)
ins_module = P.logic.get_module(module_name)
ins_page = ins_module.get_page(page_name)
if ins_page != None:
if command == 'scheduler':

View File

@@ -90,7 +90,7 @@ class SupportSubprocess(object):
instance_list = []
def __init__(self, command, print_log=False, shell=False, env=None, timeout=None, uid=None, gid=None, stdout_callback=None):
def __init__(self, command, print_log=False, shell=False, env=None, timeout=None, uid=None, gid=None, stdout_callback=None, call_id=None):
self.command = command
self.print_log = print_log
self.shell = shell
@@ -101,6 +101,8 @@ class SupportSubprocess(object):
self.stdout_callback = stdout_callback
self.process = None
self.stdout_queue = None
self.call_id = call_id
self.timestamp = time.time()
def start(self, join=True):
@@ -127,13 +129,14 @@ class SupportSubprocess(object):
tmp.append(f'"{x}"')
self.command = ' '.join(tmp)
logger.debug(f"{self.command=}")
if platform.system() == 'Windows':
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')
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)
else:
if self.uid == None:
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')
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)
else:
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')
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)
SupportSubprocess.instance_list.append(self)
self.start_communicate()
self.start_send_callback()
@@ -207,11 +210,14 @@ class SupportSubprocess(object):
else:
if self.stdout_callback != None:
self.stdout_callback('log', line)
self.remove_instance(self)
th = threading.Thread(target=func, args=())
th.setDaemon(True)
th.start()
def process_close(self):
try:
if self.process is not None and self.process.poll() is None:
@@ -228,6 +234,8 @@ class SupportSubprocess(object):
#self.stdout_queue = None
self.process.kill()
except: pass
self.remove_instance(self)
def input_command(self, cmd):
if self.process != None:
@@ -241,4 +249,25 @@ class SupportSubprocess(object):
instance.process_close()
cls.instance_list = []
@classmethod
def remove_instance(cls, remove_instance):
new = []
for instance in cls.instance_list:
if remove_instance.timestamp == instance.timestamp:
continue
new.append(instance)
cls.instance_list = new
@classmethod
def print(cls):
for instance in cls.instance_list:
logger.info(instance.command)
@classmethod
def get_instance_by_call_id(cls, call_id):
for instance in cls.instance_list:
if instance.call_id == call_id:
return instance

View File

@@ -23,6 +23,11 @@ class ModuleLog(PluginModuleBase):
arg['log_list'] = '|'.join(log_list)
arg['all_list'] = '|'.join(log_files)
arg['filename'] = 'framework.log'
print(request.form)
print(request.form)
print(request.form)
print(request.form)
if 'filename' in request.form:
arg['filename'] = request.form['filename']
return render_template(f'{__package__}_{name}.html', arg=arg)

View File

@@ -1,14 +1,15 @@
from support import SupportFile
from .page_command import PageCommand
from .setup import *
name = 'tool'
class ModuleTool(PluginModuleBase):
def __init__(self, P):
super(ModuleTool, self).__init__(P, name=name, first_menu='upload')
self.set_page_list([PageUpload, PageCrypt])
super(ModuleTool, self).__init__(P, name=name, first_menu='command')
self.set_page_list([PageUpload, PageCrypt, PagePython, PageCommand])
@@ -23,8 +24,6 @@ class PageUpload(PluginPageBase):
class PageCrypt(PluginPageBase):
def __init__(self, P, parent):
super(PageCrypt, self).__init__(P, parent, name='crypt')
@@ -32,4 +31,9 @@ class PageCrypt(PluginPageBase):
f'{self.parent.name}_{self.name}_use_user_key': 'False',
f'{self.parent.name}_{self.name}_user_key': '',
f'{self.parent.name}_{self.name}_user_key': '',
}
}
class PagePython(PluginPageBase):
def __init__(self, P, parent):
super(PagePython, self).__init__(P, parent, name='python')

291
lib/system/page_command.py Normal file
View File

@@ -0,0 +1,291 @@
import queue
from support import SupportSubprocess
from tool import ToolModalCommand
from .setup import *
class PageCommand(PluginPageBase):
def __init__(self, P, parent):
super(PageCommand, self).__init__(P, parent, name='command')
self.db_default = {
f'{self.parent.name}_{self.name}_recent': '',
}
def process_menu(self, req):
arg = self.P.ModelSetting.to_dict()
arg['path_data'] = F.config['path_data']
return render_template(f'{self.P.package_name}_{self.parent.name}_{self.name}.html', arg=arg)
def process_command(self, command, arg1, arg2, arg3, req):
ret = {'ret':'success'}
if command == 'foreground_command':
P.ModelSetting.set(f'{self.parent.name}_{self.name}_recent', arg1)
self.__foreground_execute(arg1, arg1.split(' '))
return jsonify('')
elif command == 'job_new':
db_item = ModelCommand.job_new(arg1)
ret['msg'] = f"ID:{db_item.id} 작업을 생성하였습니다."
elif command == 'job_list':
ret['data'] = ModelCommand.job_list()
elif command == 'job_save':
data = P.logic.arg_to_dict(arg1)
db_item = ModelCommand.get_by_id(data['job_id'])
db_item.set_command(data['job_command'])
db_item.args = data['job_command_args']
db_item.description = data['job_description']
db_item.schedule_mode = data['job_schedule_mode']
db_item.schedule_auto_start = (data.get('job_schedule_auto_start', 'False') == 'True')
db_item.schedule_interval = data.get('job_schedule_interval', '')
db_item.save()
ret['msg'] = '수정하였습니다.'
elif command == 'job_remove':
if ModelCommand.delete_by_id(arg1):
ret['msg'] = '삭제하였습니다.'
else:
ret['ret'] = 'danger'
ret['msg'] = '삭제에 실패하였습니다.'
elif command == 'job_fore_execute':
db_item = ModelCommand.get_by_id(arg1)
cmd = (db_item.command + ' ' + db_item.args).strip()
self.__foreground_execute(f"Command ID: {db_item.id}", cmd.split(' '), db_item.id)
elif command == 'job_back_execute':
self.execute_thread_start(arg1)
ret['msg'] = "실행 요청을 하였습니다.<br>로그를 확인하세요."
elif command == 'job_log':
ret['filename'] = f"command_{arg1}.log"
if os.path.exists(os.path.join(F.config['path_data'], 'log', f"command_{arg1}.log")) == False:
ret['ret'] = 'danger'
ret['msg'] = "로그 파일이 없습니다."
elif command == 'task_sched':
job_id = req.form['arg1']
flag = (req.form['arg2'] == 'true')
scheduler_id = f'command_{job_id}'
if flag and F.scheduler.is_include(scheduler_id):
ret['msg'] = '이미 스케쥴러에 등록되어 있습니다.'
elif flag and F.scheduler.is_include(scheduler_id) == False:
result = self.__sched_add(job_id)
ret['msg'] = '스케쥴러에 추가하였습니다.'
elif flag == False and scheduler.is_include(scheduler_id):
result = scheduler.remove_job(scheduler_id)
ret['msg'] = '스케쥴링 취소'
elif flag == False and scheduler.is_include(scheduler_id) == False:
ret['msg'] = '등록되어 있지 않습니다.'
elif command == 'job_process_stop':
process_ins = SupportSubprocess.get_instance_by_call_id(f"command_{arg1}")
if process_ins == None:
ret['msg'] = "실행중인 Process가 없습니다."
else:
process_ins.process_close()
ret['msg'] = "Process를 중지하였습니다."
return jsonify(ret)
def __foreground_execute(self, title, command, job_id=None):
if command[0] != 'LOAD':
ToolModalCommand.start(title, [command])
else:
F.socketio.emit("command_modal_show", title, namespace='/framework', broadcast=True)
def start_communicate_load(load_log_list):
def func():
while True:
logs = load_log_list.getvalue()
load_log_list.truncate(0)
if logs:
P.logger.error(logs)
F.socketio.emit("command_modal_add_text", logs.strip() + '\n', namespace='/framework', broadcast=True)
if logs == '<<END>>':
break
time.sleep(0.3)
th = threading.Thread(target=func)
th.setDaemon(True)
th.start()
def func():
import io
from contextlib import redirect_stdout
load_log_list = io.StringIO()
with redirect_stdout(self.load_log_list):
start_communicate_load(load_log_list)
if job_id is not None:
command_logger = get_logger(f'command_{job_id}')
else:
command_logger = P.logger
self.__module_load(command, logger=command_logger)
load_log_list.write("<<END>>")
load_log_list.flush()
th = threading.Thread(target=func, args=())
th.setDaemon(True)
th.start()
return 'success'
def __module_load(self, command, **kwargs):
try:
python_filename = command[1]
python_sys_path = os.path.dirname(python_filename)
if python_sys_path not in sys.path:
sys.path.append(python_sys_path)
module_name = os.path.basename(python_filename).split('.py')[0]
if module_name not in sys.path:
sys.path.append(module_name)
import importlib
mod = importlib.import_module(module_name)
importlib.reload(mod)
args = command
mod_command_load = getattr(mod, 'main')
if mod_command_load:
ret = mod_command_load(*args, **kwargs)
return ret
except Exception as e:
P.logger.error(f'Exception:{str(e)}')
P.logger.error(traceback.format_exc())
def execute_thread_start(self, job_id):
th = threading.Thread(target=self.execute_thread_function_by_job_id, args=(job_id,))
th.setDaemon(True)
th.start()
def execute_thread_function_by_job_id(self, *args, **kwargs):
P.logger.error(d(args))
P.logger.error(d(kwargs))
db_item = ModelCommand.get_by_id(args[0])
kwargs['id'] = args[0]
self.execute_thread_function((db_item.command + ' ' + db_item.args).strip(), **kwargs)
def execute_thread_function(self, command, **kwargs):
try:
cmd = command.split(' ')
if cmd[0] == 'LOAD':
command_logger = F.get_logger(f"command_{kwargs['id']}")
kwargs['logger'] = command_logger
return self.__module_load(cmd, **kwargs)
else:
class LogReceiver:
def __init__(self, logger):
self.logger = logger
def stdout_callback(self, mode, text):
if mode == 'log':
self.logger.debug(text)
else:
self.logger.debug(mode)
command_logger = F.get_logger(f"command_{kwargs['id']}", from_command=True)
receiver = LogReceiver(command_logger)
process = SupportSubprocess(cmd, stdout_callback=receiver.stdout_callback, call_id=f"command_{kwargs['id']}")
process.start()
except Exception as e:
P.logger.error(f'Exception:{str(e)}')
P.logger.error(traceback.format_exc())
def plugin_load(self):
def plugin_load_thread():
try:
db_items = ModelCommand.get_list()
for db_item in db_items:
if db_item.schedule_mode == 'startup':
self.execute_thread_start(db_item.id)
elif db_item.schedule_mode == 'scheduler' and db_item.schedule_auto_start:
self.__sched_add(db_item.id, db_item=db_item)
except Exception as exception:
logger.error('Exception:%s', exception)
logger.error(traceback.format_exc())
try:
th = threading.Thread(target=plugin_load_thread)
th.setDaemon(True)
th.start()
except Exception as e:
P.logger.error(f'Exception:{str(e)}')
P.logger.error(traceback.format_exc())
def __sched_add(self, id, db_item=None):
try:
if db_item is None:
db_item = ModelCommand.get_by_id(id)
job_id = f"command_{db_item.id}"
if scheduler.is_include(job_id):
return
job = Job(self.P.package_name, job_id, db_item.schedule_interval, self.execute_thread_function_by_job_id, db_item.description, args=db_item.id)
scheduler.add_job_instance(job)
return True
except Exception as e:
P.logger.error(f'Exception:{str(e)}')
P.logger.error(traceback.format_exc())
return False
class ModelCommand(ModelBase):
__tablename__ = 'command_job'
__table_args__ = {'mysql_collate': 'utf8_general_ci'}
__bind_key__ = 'system'
id = db.Column(db.Integer, primary_key=True)
command = db.Column(db.String)
filepath = db.Column(db.String)
args = db.Column(db.String)
description = db.Column(db.String)
schedule_mode = db.Column(db.String) # none, startup, scheduler
schedule_auto_start = db.Column(db.Boolean) # 시작시 스케쥴링 등록
schedule_interval = db.Column(db.String) # 주기
def __init__(self, command):
self.args = ''
self.description = ''
self.schedule_mode = 'none'
self.schedule_auto_start = False
self.schedule_interval = ''
self.set_command(command)
def set_command(self, command):
self.command = command
tmp = command.split(' ')
for t in tmp:
for ext in ['.py', '.sh', '.bat']:
if t.endswith(ext):
self.filepath = t
break
@classmethod
def job_new(cls, command):
item = ModelCommand(command)
return item.save()
@classmethod
def job_list(cls):
try:
data = cls.get_list(by_dict=True)
for item in data:
item['scheduler_is_include'] = F.scheduler.is_include(f"command_{item['id']}")
item['scheduler_is_running'] = F.scheduler.is_running(f"command_{item['id']}")
item['process'] = (SupportSubprocess.get_instance_by_call_id(f"command_{item['id']}") != None)
return data
except Exception as exception:
logger.error('Exception:%s', exception)
logger.error(traceback.format_exc())

View File

@@ -29,6 +29,7 @@ __menu = {
'uri': 'tool',
'name': '시스템 툴',
'list': [
{'uri': 'command', 'name': 'Command'},
{'uri': 'upload', 'name': '업로드'},
{'uri': 'python', 'name': 'Python'},
{'uri': 'db', 'name': 'DB'},

View File

@@ -3,7 +3,7 @@
<div>
{{ macros.setting_select_empty('log_select1', '로그 파일 선택 (.log)') }}
{{ macros.setting_select_empty('log_select2', '로그 파일 선택 (.logX)') }}
<!--{{ macros.setting_select_empty('log_select2', '로그 파일 선택 (.logX)') }}-->
<nav>
{{ macros.m_tab_head_start() }}
{{ macros.m_tab_head('이전', true) }}
@@ -42,7 +42,7 @@ var start_filename = "{{arg['filename']}}";
$(document).ready(function() {
$('#main_container').attr('class', 'container-fluid');
ResizeTextArea()
ResizeTextAreaAllLog()
make_form()
if (start_filename != '')
document.getElementById('log_select').value = start_filename;
@@ -56,13 +56,14 @@ function make_form() {
str += '<option value="' + data[i] + '">' + data[i] + '</option>';
}
$("#log_select1_div").html(str);
/*
str = '<select id="log_select" name="log_select" class="form-control form-control-sm">';
data = all_list.split('|')
for(var i in data) {
str += '<option value="' + data[i] + '">' + data[i] + '</option>';
}
$("#log_select2_div").html(str);
*/
}
$("body").on('change', '#log_select', function(e){
@@ -75,14 +76,14 @@ $("body").on('change', '#log_select', function(e){
});
function ResizeTextArea() {
function ResizeTextAreaAllLog() {
ClientHeight = window.innerHeight
$("#log").height(ClientHeight-300);
$("#add").height(ClientHeight-320);
}
$(window).resize(function() {
ResizeTextArea();
ResizeTextAreaAllLog();
});

View File

@@ -159,13 +159,13 @@ function make_scheduler_list(data) {
TD_STR = '<td scope="col" style="width:10%; text-align:center;">';
for(var i in data) {
if (data[i].is_running) {
str += '<tr class="bg-light">';
str += '<tr class="bg-dark text-white">';
} else {
str += '<tr>';
}
str += '<td scope="col" style="width:5%; text-align:center;">' + (parseInt(i)+1) + '</td>';
str += '<td scope="col" style="width:10%; text-align:center;">' + (data[i].plugin) + '<br>' + (data[i].id) + '</td>';
str += '<td scope="col" style="width:10%; text-align:center;">' + ((data[i].is_running) ?'실행중':'대기중') + '</td>';
str += '<td scope="col" style="width:10%; text-align:center;">' + ((data[i].is_running) ? '실행중':'대기중') + '</td>';
str += '<td scope="col" style="width:10%; text-align:center;">' + (data[i].next_run_time) + '</td>';
str += '<td scope="col" style="width:10%; text-align:center;">' + (data[i].remain_time) + '</td>';
@@ -180,54 +180,6 @@ function make_scheduler_list(data) {
$("#scheduler_list_div").html(str);
return;
str = m_row_start(p='0');
str += m_col(1, '<strong>NO</strong>');
str += m_col(2, '<strong>플러그인 & ID</strong>');
//str += m_col(2, '<strong>생성 & 다음 실행</strong>');
str += m_col(2, '<strong>다음 실행 (남은시간)</strong>');
str += m_col(1, '<strong>이전소요/횟수</strong>');
str += m_col(2, '<strong>Interval & Cron</strong>');
str += m_col(1, '<strong>상태</strong>');
str += m_col(3, '<strong>설 명</strong>');
str += m_row_end();
str += m_hr();
for(var i in data) {
if (data[i].is_running) {
str += m_row_start_color2(0);
} else {
str += m_row_start(p='0');
}
str += m_col(1, data[i].no);
tmp = '<strong>'+data[i].plugin+'</strong><br>' + data[i].id;
str += m_col(2, tmp);
//tmp = ''+''+'' + data[i].make_time + '<br>';
//tmp += ''+''+'' + data[i].next_run_time + '<br>';
tmp = ''+''+'' + data[i].next_run_time + '<br>';
if (data[i].remain_time != '') {
tmp += '('+data[i].remain_time+')';
}
str += m_col(2, tmp);
tmp = ''+''+'' + data[i].running_timedelta + '초 / ';
tmp += ''+''+'' + data[i].count + '회';
str += m_col(1, tmp);
tmp = ''+''+'' + data[i].interval + ' <br>';
str += m_col(2, tmp);
tmp = ''+''+'' + ((data[i].is_running) ?'실행중':'대기중') + '';
if (data[i].run == false) {
tmp += '(F)'
}
str += m_col(1, tmp);
str += m_col(3, data[i].description);
str += m_row_end();
str += m_hr();
}
document.getElementById("scheduler_list_div").innerHTML = str;
}

View File

@@ -36,39 +36,39 @@ function make_plugin_list(data) {
console.log(data)
for (i in data) {
console.log(data[i]);
str += m_row_start();
str += m_col_wide(1, (parseInt(i)+1), 'center')
str += j_row_start();
str += j_col_wide(1, (parseInt(i)+1), 'center')
if (data[i].title == null) {
str += m_col_wide(2, '');
str += m_col_wide(2, data[i].package_name);
str += m_col_wide(5, '');
tmp = m_button('uninstall_btn', '삭제', {'package_name':data[i].package_name}, 'danger', false, true);
str += j_col_wide(2, '');
str += j_col_wide(2, data[i].package_name);
str += j_col_wide(5, '');
tmp = j_button('uninstall_btn', '삭제', {'package_name':data[i].package_name}, 'danger', false, true);
} else {
str += m_col_wide(2, data[i].title);
str += j_col_wide(2, data[i].title);
str += m_col_wide(2, data[i].package_name);
str += m_col_wide(1, data[i].developer);
str += m_col_wide(1, data[i].version);
str += j_col_wide(2, data[i].package_name);
str += j_col_wide(1, data[i].developer);
str += j_col_wide(1, data[i].version);
if (data[i].loading == false) {
tmp = data[i].description + '<br>' + text_color('[로딩 실패] ') + data[i].status;
str += m_col_wide(3, tmp);
str += j_col_wide(3, tmp);
} else {
str += m_col_wide(3, data[i].description);
str += j_col_wide(3, data[i].description);
}
tmp = ''
tmp += m_button_small('globalOpenBtn', '홈페이지', {'url':data[i].home}, 'primary', false, true);
tmp += m_button_small('uninstall_btn', '삭제', {'package_name':data[i].package_name, 'title':data[i].title}, 'danger', false, true);
tmp += m_button_small('json_btn', 'JSON', {'idx':i}, 'info', false, true);
tmp += j_button_small('globalOpenBtn', '홈페이지', {'url':data[i].home}, 'primary', false, true);
tmp += j_button_small('uninstall_btn', '삭제', {'package_name':data[i].package_name, 'title':data[i].title}, 'danger', false, true);
tmp += j_button_small('json_btn', 'JSON', {'idx':i}, 'info', false, true);
}
tmp = m_button_group(tmp)
str += m_col_wide(2, tmp, 'right')
str += m_row_end();
if (i != current_data.length -1) str += m_hr(0);
tmp = j_button_group(tmp)
str += j_col_wide(2, tmp, 'right')
str += j_row_end();
if (i != current_data.length -1) str += j_hr(0);
}
document.getElementById("plugin_list_div").innerHTML = str;
$("#plugin_list_div").html(str);
}
$("body").on('click', '#json_btn', function(e){

View File

@@ -16,9 +16,7 @@
{{ macros.m_hr() }}
{{ macros.setting_checkbox('use_apikey', 'APIKEY 사용', value=arg['use_apikey'], desc=['On : 모든 API 요청 시 apikey 값을 입력해야 합니다.', '없거나 틀릴 경우 에러코드 403리턴']) }}
<div id="use_apikey_div" class="collapse">
{{ macros.setting_input_text_and_buttons('apikey', 'APIKEY', [['apikey_generate_btn', '자동생성']], col='4', value=arg['apikey']) }}
</div>
{{ macros.setting_input_text_and_buttons('apikey', 'APIKEY', [['apikey_generate_btn', '자동생성']], col='4', value=arg['apikey']) }}
{{ macros.m_hr() }}
</form>
</div> <!--전체-->
@@ -27,17 +25,12 @@
$(document).ready(function(){
use_collapse("use_login");
use_collapse("use_apikey");
});
$('#use_login').change(function() {
use_collapse('use_login');
});
$('#use_apikey').change(function() {
use_collapse('use_apikey');
});
$("body").on('click', '#apikey_generate_btn', function(e) {
e.preventDefault();
globalSendCommand('apikey_generate', null, null, null, null, function(ret){

View File

@@ -0,0 +1,310 @@
{% extends "base.html" %}
{% block content %}
{{ macros.m_button_group([['foreground_command_btn', 'Foreground 실행'], ['job_new_btn', '저장'], ['select_file_btn', '파일선택', [['path_data', arg['path_data']]] ]])}}
{{ macros.setting_input_textarea_wide('command', 'Command', row='3', value=arg['tool_command_recent'], desc=['예) cmd, bssh, sh, python test.py, LOAD test.py, curl ifconfig.me']) }}
<div id="list_div"></div>
{{ macros.m_modal_start('job_modal', '', 'modal-lg') }}
<form id='item_setting' name='item_setting'>
<input type='hidden' id="job_id" name="job_id">
{{ macros.setting_input_textarea_wide('job_command', 'Command', row=5, desc=['LOAD형으로 실행할 경우 python 대신 LOAD로 시작']) }}
{{ macros.setting_input_text('job_command_args', 'ARGS', desc=['Command에 덧붙여 전달할 값. API로 변경 가능']) }}
{{ macros.setting_input_text('job_description', 'Description') }}
{{ macros.setting_radio_with_value('job_schedule_mode', '스케쥴링 타입', [['none', '없음'], ['startup', '시작시 한번 실행'], ['scheduler', '스케쥴링']]) }}
{{ macros.setting_input_text('job_schedule_interval', '스케쥴링 정보', desc=['Interval(minute 단위)이나 Cron 설정']) }}
{{ macros.setting_checkbox('job_schedule_auto_start', '시작시 스케쥴링 등록', desc=['On : 시작시 자동으로 스케쥴러에 등록됩니다.']) }}
</form>
</div>
<div class="modal-footer">
{{ macros.m_button_group([['job_save_btn', '저장'], ['job_remove_btn', '삭제'], ['modal_hide_btn', '닫기']])}}
</div>
</div></div></div>
<div class="modal fade" id="file_modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="modal_title">Site </h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body" id="modal_body" style="word-break:break-all;">
<form id="file_form" name="file_form">
<div class="input-group col-sm-12">
<textarea id="file_textarea" name="file_textarea" class="col-md-12" rows="50"></textarea>
</div>
<input type="hidden" name="file_job_id" id="file_job_id" value="-1">
</form>
</div>
<div class="modal-footer">
<button id="file_save_btn" type="button" class="btn btn-primary">저장</button>
<button type="button" class="btn btn-default" data-dismiss="modal">닫기</button>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function(){
request_list();
});
////////////////////////////////// 상단 버튼
$("body").on('click', '#foreground_command_btn', function(e){
e.preventDefault();
globalSendCommandPage('foreground_command', $('#command').val());
});
$("body").on('click', '#job_new_btn', function(e){
e.preventDefault();
globalSendCommandPage('job_new', $('#command').val(), null, null, null, function(ret){
request_list();
});
});
$("body").on('click', '#select_file_btn', function(e){
e.preventDefault();
path_data = $(this).data('path_data');
globalSelectLocalFile("파일 선택", path_data, function(ret){
$('#command').val(ret);
});
});
////////////////////////////////// 상단 버튼 END
function request_list() {
globalSendCommandPage('job_list', null, null, null, null, function(ret){
make_list(ret.data);
});
}
function make_list(data) {
current_data = data;
str = '';
str = '<table id="result_table" class="table table-sm" ><thead class="thead-dark"><tr> \
<th style="width:5%; text-align:center;">ID</th> \
<th style="width:60%; text-align:center;">Command & arg & Desc</th> \
<th style="width:5%; text-align:center;">자동</th> \
<th colspan="2" style="width:20%; text-align:center;">스케쥴 상태</th> \
<th style="width:10%; text-align:center;">스케쥴</th> \
</tr></thead><tbody id="list">';
if (data.length == 0) str += '<tr><td colspan="6"><h4>작업이 없습니다.</h4></td></tr>';
for(i in data) {
console.log(data[i]);
str += '<tr class="chover" style="cursor: pointer;" data-toggle="collapse" data-target="#collapse_' + i + '" aria-expanded="true" >';
str += '<td rowspan="2" scope="col" style="width:5%; text-align:center;">'+ (data[i].id) + '</td>';
// command
tmp = '';
tmp += text_color(data[i].command, 'blue') + '<br>';
tmp += data[i].args + '<br>';
tmp += data[i].description + '<br>';
str += '<td scope="col" style="width:60%; text-align:left;">'+ tmp + '</td>';
tmp = (data[i].schedule_auto_start) ? text_color("ON", 'blue') : "OFF";
str += '<td scope="col" style="width:5%; text-align:center;">'+ tmp + '</td>';
tmp2 = null;
if (data[i].schedule_mode == 'none') {
tmp1 = "없음";
} else if (data[i].schedule_mode == 'startup') {
tmp1 = "시작시 한번 실행";
} else if (data[i].schedule_mode == 'scheduler') {
tmp1 = "스케쥴링";
tmp2 = '<input id="use_checkbox|'+data[i].id+'" type="checkbox" data-id='+data[i].id+' data-toggle="toggle" data-on="On" data-off="Off" data-onstyle="info" data-offstyle="danger" data-size="small" ' + ((data[i].scheduler_is_include) ? 'checked' : '') + '>';
if (data[i].scheduler_is_include) {
tmp2 += (data[i].scheduler_is_running) ? "<br>실행중" : "<br>대기중";
}
}
if (tmp2 == null) {
str += '<td scope="col" colspan="2" style="width:20%; text-align:center;">'+ tmp1 + '</td>';
} else {
str += '<td scope="col" style="width:10%; text-align:right;">'+ tmp1 + '</td>';
str += '<td scope="col" style="width:10%; text-align:left;">'+ tmp2 + '</td>';
}
str += '<td scope="col" style="width:10%; text-align:center;">'+ data[i].schedule_interval + '</td>';
str += '</tr>'
str += '<tr>'
tmp = j_row_start(0);
tmp += j_col('0', '');
btn = j_button('job_edit_btn', '작업 편집', {'idx':i}, 'secondary', false, true);
btn += j_button('job_remove_from_table_btn', '삭제', {'id':data[i].id}, 'danger', true, true);
btn += j_button('job_fore_execute_btn', 'Foreground 실행', {'id':data[i].id}, 'primary', true, true);
btn += j_button('job_back_execute_btn', 'Background 실행', {'id':data[i].id}, 'primary', true, true);
btn += j_button('job_log_btn', '로그', {'id':data[i].id}, 'info', true, true);
btn += j_button('job_cmd_input_btn', 'Command에 입력', {'idx':i}, 'info', true, true);
if ( data[i].filepath != '' && data[i].filepath != null )
btn += j_button('globalEditBtn', '파일 수정', {'file':data[i].filepath}, 'info', false, true);
if ( data[i].process )
btn += j_button('job_process_stop_btn', '실행중인 Process 중지', {'id':data[i].id}, 'danger', false, true);
tmp += j_col('12', j_button_group(btn));
tmp += j_row_end();
str += '<td colspan="1" scope="col" style="width:5%; text-align:center;">'+ tmp + '</td>';
str += '</tr>'
}
str += '</table>';
document.getElementById("list_div").innerHTML = str;
$('input[id^="use_checkbox|"]').bootstrapToggle();
}
$('input[type=radio][name=job_schedule_mode]').change(function() {
set_schedule_mode(this.value);
});
function set_schedule_mode(mode) {
$('input:radio[name="job_schedule_mode"][value="'+mode+'"]').attr('checked',true);
if ( mode == 'none' || mode == 'startup') {
$("#job_schedule_interval").attr('disabled', true);
$("#job_schedule_auto_start").attr('disabled', true);
} else {
$("#job_schedule_interval").attr('disabled', false);
$("#job_schedule_auto_start").attr('disabled', false);
}
}
// 아이템 저장 버튼
$("body").on('click', '#job_save_btn', function(e){
e.preventDefault();
//tmp = document.getElementById("schedule_radio2").getAttribute("checked");
schedule_mode = $('input[name=job_schedule_mode]:checked').val();
console.log(tmp);
schedule_interval = $("#job_schedule_interval").val();
if (schedule_mode == 'scheduler' && schedule_interval == '') {
notify("스케쥴링 정보를 입력하세요", 'warning');
return
}
var formData = getFormdata('#item_setting');
globalSendCommandPage('job_save', formData, null, null, null, function(ret){
if (ret.ret == 'success') {
$('#job_modal').modal('hide');
request_list();
}
});
});
$("body").on('click', '#modal_hide_btn', function(e){
e.preventDefault();
$('#job_modal').modal('hide');
});
$("body").on('click', '#job_remove_btn', function(e){
e.preventDefault();
remove_job($("#job_id").val());
});
function remove_job(job_id) {
globalSendCommandPage('job_remove', job_id, null, null, null, function(ret){
if (ret.ret == 'success') {
$('#job_modal').modal('hide');
request_list();
}
});
}
////////////////////////////////////////////////////////////
// JOB 테이블 제어
$("body").on('click', '#job_edit_btn', function(e){
e.preventDefault();
idx = parseInt($(this).data('idx'));
item = current_data[idx];
$("#job_id").val(item.id);
$("#job_modal_title").html('ID: ' + item.id + ' Command ID: ' + item.command_id);
$("#job_command").val(item.command);
$("#job_description").val(item.description);
$("#job_command_args").val(item.args);
set_schedule_mode(item.schedule_mode);
if ( item.schedule_mode == 'scheduler') {
$("#scheduler_swtich_btn").attr('disabled', false);
} else {
$("#scheduler_swtich_btn").attr('disabled', true);
}
$("#job_schedule_interval").val(item.schedule_interval);
if (item.schedule_auto_start) {
$("#job_schedule_auto_start").val('on');
$('#job_schedule_auto_start').bootstrapToggle('on')
} else {
$("#job_schedule_auto_start").val('off');
$('#job_schedule_auto_start').bootstrapToggle('off')
}
$("#job_modal").modal();
});
$("body").on('click', '#job_remove_from_table_btn', function(e){
e.preventDefault();
remove_job($(this).data('id'));
});
$("body").on('click', '#job_fore_execute_btn', function(e){
e.preventDefault();
globalSendCommandPage('job_fore_execute', $(this).data('id'));
});
$("body").on('click', '#job_back_execute_btn', function(e){
e.preventDefault();
globalSendCommandPage('job_back_execute', $(this).data('id'), null, null, null, function(e) {
request_list();
});
});
$("body").on('click', '#job_log_btn', function(e){
e.preventDefault();
globalSendCommandPage('job_log', $(this).data('id'), null, null, null, function(data){
if (data.ret == 'success') {
redirect = '/system/all_log/list';
$.redirectPost(redirect, {filename: data.filename});
}
});
});
$("body").on('click', '#job_cmd_input_btn', function(e){
e.preventDefault();
idx = parseInt($(this).data('idx'));
$("#command").val((current_data[idx].command + ' ' + current_data[idx].args).trim());
window.scrollTo(0,0);
});
$("body").on('change', 'input[id^="use_checkbox|"]', function(e){
e.preventDefault();
globalSendCommandPage('task_sched', $(this).data('id'), $(this).prop('checked'), null, null, function(e) {
request_list();
});
});
$("body").on('click', '#job_process_stop_btn', function(e){
e.preventDefault();
globalSendCommandPage('job_process_stop', $(this).data('id'), null, null, null, function(e) {
request_list();
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,76 @@
{% extends "base.html" %}
{% block content %}
<div>
{{ macros.m_button_group([['globalSettingSaveBtn', '설정 저장']])}}
{{ macros.m_row_start('5') }}
{{ macros.m_row_end() }}
<nav>
{{ macros.m_tab_head_start() }}
{{ macros.m_tab_head2('normal', '일반', true) }}
{{ macros.m_tab_head_end() }}
</nav>
<form id='setting' name='setting'>
<div class="tab-content" id="nav-tabContent">
{{ macros.m_tab_content_start('normal', true) }}
{{ macros.setting_checkbox('tool_crypt_use_user_key', '암호화 키 직접 입력', value=arg['tool_crypt_use_user_key'], desc=['On : 본인 키 사용', '주의) 변경 후 일반설정-인증-로그인 암호를 새로 저장해야 합니다.', 'Off : 앱 고정 키 사용']) }}
<div id="tool_crypt_use_user_key_div" class="collapse">
{{ macros.setting_input_text('tool_crypt_user_key', '암호화 키', value=arg['tool_crypt_user_key'], desc=['16진수(숫자, a~e)로 이루어진 32글자. 미만시 앞을 0으로 채움. 초과시 뒤에 무시']) }}
</div>
{{ macros.setting_input_text_and_buttons('tool_crypt_encrypt_word', '암호화', [['tool_crypt_encrypt_word_btn', '암호화']], value=arg['tool_crypt_encrypt_word']) }}
{{ macros.setting_input_text('tool_crypt_encrypt_word_result', '', disabled=True) }}
{{ macros.setting_input_text_and_buttons('tool_crypt_decrypt_word', '평문화', [['tool_crypt_decrypt_word_btn', '평문화']], value=arg['tool_crypt_decrypt_word']) }}
{{ macros.setting_input_text('tool_crypt_decrypt_word_result', '', disabled=True) }}
{{ macros.m_tab_content_end() }}
</div><!--tab-content-->
</form>
</div> <!--전체-->
<script type="text/javascript">
var package_name = "{{arg['package_name'] }}";
var sub = "{{arg['sub'] }}";
$(document).ready(function(){
use_collapse("tool_crypt_use_user_key");
});
$("body").on('change', '#tool_crypt_use_user_key', function(e){
use_collapse('tool_crypt_use_user_key');
});
$("body").on('click', '#tool_crypt_encrypt_word_btn', function(e) {
e.preventDefault();
word = document.getElementById("tool_crypt_encrypt_word").value
crypt_test('encrypt', word);
});
$("body").on('click', '#tool_crypt_decrypt_word_btn', function(e) {
e.preventDefault();
word = document.getElementById("tool_crypt_decrypt_word").value
crypt_test('decrypt', word);
});
function crypt_test(mode, word) {
$.ajax({
url: '/' + package_name + '/ajax/'+sub+'/crypt_test',
type: "POST",
cache: false,
data: {mode:mode, word:word},
dataType: "json",
success: function (ret) {
if (ret.ret == 'success') {
if (mode == "encrypt")
document.getElementById("tool_crypt_encrypt_word_result").value = ret.data;
else
document.getElementById("tool_crypt_decrypt_word_result").value = ret.data;
} else {
notify(ret.log, 'warning');
}
}
});
}
</script>
{% endblock %}