From 2e7910a84c8eed94c37de0fdc7b9f7d2ba02308f Mon Sep 17 00:00:00 2001 From: midoks Date: Sat, 28 Jan 2023 01:34:48 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=80=E9=94=AE=E8=BF=81=E7=A7=BB=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/migration_api/ico.png | Bin 0 -> 478 bytes plugins/migration_api/index.html | 223 +++++++++++++++++++++++++ plugins/migration_api/index.py | 270 +++++++++++++++++++++++++++++++ plugins/migration_api/info.json | 18 +++ plugins/migration_api/install.sh | 31 ++++ plugins/migration_api/js/app.js | 58 +++++++ 6 files changed, 600 insertions(+) create mode 100644 plugins/migration_api/ico.png create mode 100755 plugins/migration_api/index.html create mode 100755 plugins/migration_api/index.py create mode 100755 plugins/migration_api/info.json create mode 100755 plugins/migration_api/install.sh create mode 100755 plugins/migration_api/js/app.js diff --git a/plugins/migration_api/ico.png b/plugins/migration_api/ico.png new file mode 100644 index 0000000000000000000000000000000000000000..f828676085a65d91fe91dd42b5e5f8439bf556c3 GIT binary patch literal 478 zcmV<40U`d0P)Px$m`OxIRA@u(nPE``F$_lEDF6pZ00)2rPyjf<0pI`}fC6v;C;$maI0rD_usv>= z>1^9%FFn}&y(ax$a@{7o3%1-ATdtoOAi9Eot^!wvjNSoDgMX9j?+Sc2MfrAm2aY!l zkpqyB8oLGdfhXX!#?iVq2T*%9c7c}x;vTrL3M=*Z0?>Yf_yBf*8{oQBgk_y4fOH%T zl@P?`a4szRa=x$W2XBA`@iv9(wU{g2Qvhh6lc5^k9Ou9u{B#gg0eA~f@${fLoB^gv zZ$aF!3g}E|b8D*+0&oO8JGB}i0QzU>c0O}}2m|nf2nFzf2nVo2bOB(2=mww_qAP$F zi0%MtA$Q0E_r=0IT>=01xqD0AAw505lIj7Cz_4?wPQAKrM(iw^|s0Re19M zKL9FukETXaebea6-5$lSYsvy_ +.migration_content { + position: relative; + height: 150px; +} +.step_head::after { + background-color: #ddd; + border-top: 2px solid #ccc; + content: ""; + display: block; + height: 3px; + left: 84px; + position: absolute; + top: 14px; + width: 500px; + z-index: -1; +} +.step_head { + margin: 20px 0; +} +.step_head ul li { + width: 24.5%; + display: inline-block; +} +.step_head ul li span { + width: 30px; + height: 30px; + line-height: 30px; + display: block; + border-radius: 15px; + background-color: #ddd; + color: #878787; + margin: 0 auto; + text-align: center; + font-size: 16px; + font-weight: 600; +} +.step_head ul li p { + text-align: center; + margin-top: 15px; +} +.step_head ul li.active span { + background-color: #20A53A; + color: #fff; +} +.step_content { + text-align: center; +} +.boxHide { + display: none +} +.step_content .psync_info input { + width: 300px; +} +.step_content .psync_info .panel_setp_span { + height: 32px; + line-height: 32px; + overflow: hidden; + padding-right: 20px; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + width: 130px; + display: inline-block; + float: left; +} +.step_content .psync_info .mtb20{ + padding-left: 70px; + text-align: left; +} +.psync_path .table { + text-align: left; +} +.psync_path .table > tbody > tr > td:nth-child(2n), +.psync_path .table > thead > tr > th:nth-child(2n), +.terlist .table > thead > tr > th:nth-child(2n), +.terlist .table > tbody > tr > td:nth-child(2n), +.terlist .table > tbody > tr > td:nth-child(1), +.terlist .table > thead > tr > th:nth-child(1) { + border-right: #ddd 1px solid; +} + +.checkbox_conten { + background-color: #f3f3f3; + border: #ddd 1px solid; + border-radius: 4px; + padding: 10px; + margin: 0px 50px; +} + +.checkbox_item span { + display: inline-block; + height: 15px; + overflow: hidden; + text-align: left; + display: block; + padding-left: 20px; + height: 20px; + line-height: 20px; + width: 152px; + text-overflow: ellipsis; +} + +label.checkbox_label { + margin-left: 6px; + margin-bottom: 0; +} + +label.checkbox_label span { + color: #000; +} +.psync_data { + display: none; +} +.psync_data .checkbox_item input[type="checkbox"] { + width: 15px; + height: 15px; + position: absolute; + top: 50%; + margin-top: -6px; +} + +.checkbox_item label { + font-weight: normal; + white-space: nowrap; + position: relative; +} + +.psync_data .checkbox_data { + margin: 0 5px 5px; + display: inline-block; + width: 166px; + vertical-align: text-top; +} + +.psync_data .checkbox_data:last-child { + margin-right: 0; +} + +.checkbox_item ul { + background-color: #fff; + max-height: 174px; + overflow: auto; + width: 100%; + display: block; + margin-top: 10px; +} + +.checkbox_item ul li { + padding: 0 6px; +} + +.psync_data .checkbox_con { + display: block +} + +.progress { + background-color: #e2e2e2; + border-radius: 8px; + height: 16px; + line-height: 16px; + position: relative; +} + +.progress-bar { + background-color: #5ab76c; + border-radius: 8px; + height: 16px; + max-width: 100%; + position: absolute; + text-align: right; + transition: all 0.3s ease 0s; + width: 0; +} + +.progress-text { + font-size: 12px; + color: #fff; + padding: 0 10px; + position: static; +} + +.qystatus { + color: #666; + margin-bottom: 10px; + margin-left: 5px; +} + +.success { + padding: 50px 0 60px; + margin-left: -60px; +} + +.success p { + margin-top: 20px; + font-size: 16px; + color: #666; +} +.psync_tips{ + color: red; + font-size: 15px; + background-color: #fbfbfb; + border: 1px solid #eee; + line-height: 46px; + margin-bottom: 15px; + padding-left: 10px; +} +.psync_tips span{ + font-weight: 600; +} + +
+
+
+
    +
  • 1

    填写信息

  • +
  • 2

    检测环境

  • +
  • 3

    选择数据

  • +
  • 4

    一键迁移

  • +
+
+
+
\ No newline at end of file diff --git a/plugins/migration_api/index.py b/plugins/migration_api/index.py new file mode 100755 index 000000000..15fe59413 --- /dev/null +++ b/plugins/migration_api/index.py @@ -0,0 +1,270 @@ +# coding:utf-8 + +import sys +import io +import os +import time +import re + +sys.path.append(os.getcwd() + "/class/core") +import mw + +app_debug = False +if mw.isAppleSystem(): + app_debug = True + + +def getPluginName(): + return 'migration_api' + + +def getPluginDir(): + return mw.getPluginDir() + '/' + getPluginName() + + +def getServerDir(): + return mw.getServerDir() + '/' + getPluginName() + + +def getInitDFile(): + if app_debug: + return '/tmp/' + getPluginName() + return '/etc/init.d/' + getPluginName() + + +def getConf(): + path = getServerDir() + "/init.d/memcached" + return path + + +def getConfEnv(): + path = getServerDir() + "/memcached.env" + return path + + +def getConfTpl(): + path = getPluginDir() + "/init.d/memcached.tpl" + return path + + +def getMemPort(): + path = getServerDir() + '/memcached.env' + content = mw.readFile(path) + rep = 'PORT\s*=\s*(\d*)' + tmp = re.search(rep, content) + return tmp.groups()[0] + + +def getArgs(): + args = sys.argv[2:] + tmp = {} + args_len = len(args) + + if args_len == 1: + t = args[0].strip('{').strip('}') + t = t.split(':') + tmp[t[0]] = t[1] + elif args_len > 1: + for i in range(len(args)): + t = args[i].split(':') + tmp[t[0]] = t[1] + + return tmp + + +def checkArgs(data, ck=[]): + for i in range(len(ck)): + if not ck[i] in data: + return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!')) + return (True, mw.returnJson(True, 'ok')) + + +def status(): + return 'start' + + +def initDreplace(): + + file_tpl = getConfTpl() + service_path = mw.getServerDir() + + initD_path = getServerDir() + '/init.d' + if not os.path.exists(initD_path): + os.mkdir(initD_path) + file_bin = initD_path + '/memcached' + + if not os.path.exists(file_bin): + content = mw.readFile(file_tpl) + content = content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(file_bin, content) + mw.execShell('chmod +x ' + file_bin) + + # systemd + systemDir = mw.systemdCfgDir() + systemService = systemDir + '/memcached.service' + systemServiceTpl = getPluginDir() + '/init.d/memcached.service.tpl' + if os.path.exists(systemDir) and not os.path.exists(systemService): + service_path = mw.getServerDir() + se_content = mw.readFile(systemServiceTpl) + se_content = se_content.replace('{$SERVER_PATH}', service_path) + mw.writeFile(systemService, se_content) + mw.execShell('systemctl daemon-reload') + + envFile = getServerDir() + '/memcached.env' + if not os.path.exists(envFile): + wbody = "IP=127.0.0.1\n" + wbody = wbody + "PORT=11211\n" + wbody = wbody + "USER=root\n" + wbody = wbody + "MAXCONN=1024\n" + wbody = wbody + "CACHESIZE=1024\n" + wbody = wbody + "OPTIONS=''\n" + mw.writeFile(envFile, wbody) + + return file_bin + + +def memOp(method): + file = initDreplace() + + if not mw.isAppleSystem(): + data = mw.execShell('systemctl ' + method + ' ' + getPluginName()) + if data[1] == '': + return 'ok' + return data[1] + + data = mw.execShell(file + ' ' + method) + if data[1] == '': + return 'ok' + return data[1] + + +def start(): + return memOp('start') + + +def stop(): + return memOp('stop') + + +def restart(): + return memOp('restart') + + +def reload(): + return memOp('reload') + + +def runInfo(): + # 获取memcached状态 + import telnetlib + import re + port = getMemPort() + try: + tn = telnetlib.Telnet('127.0.0.1', int(port)) + tn.write(b"stats\n") + tn.write(b"quit\n") + data = tn.read_all() + if type(data) == bytes: + data = data.decode('utf-8') + data = data.replace('STAT', '').replace('END', '').split("\n") + result = {} + res = ['cmd_get', 'get_hits', 'get_misses', 'limit_maxbytes', 'curr_items', 'bytes', + 'evictions', 'limit_maxbytes', 'bytes_written', 'bytes_read', 'curr_connections'] + for d in data: + if len(d) < 3: + continue + t = d.split() + if not t[0] in res: + continue + result[t[0]] = int(t[1]) + result['hit'] = 1 + if result['get_hits'] > 0 and result['cmd_get'] > 0: + result['hit'] = float(result['get_hits']) / \ + float(result['cmd_get']) * 100 + + conf = mw.readFile(getServerDir() + '/memcached.env') + result['bind'] = re.search('IP=(.+)', conf).groups()[0] + result['port'] = int(re.search('PORT=(\d+)', conf).groups()[0]) + result['maxconn'] = int(re.search('MAXCONN=(\d+)', conf).groups()[0]) + result['cachesize'] = int( + re.search('CACHESIZE=(\d+)', conf).groups()[0]) + return mw.getJson(result) + except Exception as e: + return mw.getJson({}) + + +def saveConf(): + + args = getArgs() + data = checkArgs(args, ['ip', 'port', 'maxconn', 'maxsize']) + if not data[0]: + return data[1] + + envFile = getServerDir() + '/memcached.env' + + wbody = "IP=" + args['ip'] + "\n" + wbody = wbody + "PORT=" + args['port'] + "\n" + wbody = wbody + "USER=root\n" + wbody = wbody + "MAXCONN=" + args['maxconn'] + "\n" + wbody = wbody + "CACHESIZE=" + args['maxconn'] + "\n" + wbody = wbody + "OPTIONS=''\n" + mw.writeFile(envFile, wbody) + + restart() + return 'save ok' + + +def initdStatus(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + shell_cmd = 'systemctl status ' + \ + getPluginName() + ' | grep loaded | grep "enabled;"' + data = mw.execShell(shell_cmd) + if data[0] == '': + return 'fail' + return 'ok' + + +def initdInstall(): + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl enable ' + getPluginName()) + return 'ok' + + +def initdUinstall(): + if not app_debug: + if mw.isAppleSystem(): + return "Apple Computer does not support" + + mw.execShell('systemctl disable ' + getPluginName()) + return 'ok' + +if __name__ == "__main__": + func = sys.argv[1] + if func == 'status': + print(status()) + elif func == 'start': + print(start()) + elif func == 'stop': + print(stop()) + elif func == 'restart': + print(restart()) + elif func == 'reload': + print(reload()) + elif func == 'initd_status': + print(initdStatus()) + elif func == 'initd_install': + print(initdInstall()) + elif func == 'initd_uninstall': + print(initdUinstall()) + elif func == 'run_info': + print(runInfo()) + elif func == 'conf_env': + print(getConfEnv()) + elif func == 'save_conf': + print(saveConf()) + else: + print('error') diff --git a/plugins/migration_api/info.json b/plugins/migration_api/info.json new file mode 100755 index 000000000..9d6a94b4b --- /dev/null +++ b/plugins/migration_api/info.json @@ -0,0 +1,18 @@ +{ + "sort": 7, + "ps": "一键迁移", + "name": "migration_api", + "title": "一键迁移", + "shell": "install.sh", + "versions":["1.0"], + "updates":["1.0"], + "tip": "soft", + "checks": "server/migration_api", + "path":"server/migration_api", + "display": 1, + "author": "midoks", + "date": "2022-01-17", + "home": "https://github.com/midoks/mdserver-web", + "type": 0, + "pid": "4" +} \ No newline at end of file diff --git a/plugins/migration_api/install.sh b/plugins/migration_api/install.sh new file mode 100755 index 000000000..9a1ad2098 --- /dev/null +++ b/plugins/migration_api/install.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +curPath=`pwd` +rootPath=$(dirname "$curPath") +rootPath=$(dirname "$rootPath") +serverPath=$(dirname "$rootPath") + +install_tmp=${rootPath}/tmp/mw_install.pl + +VERSION=1.0 + +Install_App(){ + mkdir -p $serverPath/migration_api + echo "${VERSION}" > $serverPath/migration_api/version.pl + echo '正在安装脚本文件...' > $install_tmp +} + +Uninstall_App() +{ + rm -rf $serverPath/migration_api +} + +action=$1 +if [ "${1}" == 'install' ];then + Install_App +else + Uninstall_App +fi diff --git a/plugins/migration_api/js/app.js b/plugins/migration_api/js/app.js new file mode 100755 index 000000000..c28750855 --- /dev/null +++ b/plugins/migration_api/js/app.js @@ -0,0 +1,58 @@ +function maPost(method,args,callback){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/plugins/run', {name:'migration_api', func:method, args:_args}, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +} + +function maAsyncPost(method,args){ + var _args = null; + if (typeof(args) == 'string'){ + _args = JSON.stringify(toArrayObject(args)); + } else { + _args = JSON.stringify(args); + } + return syncPost('/plugins/run', {name:'migration_api', func:method, args:_args}); +} + +function maPostCallbak(method, args, callback){ + var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 }); + + var req_data = {}; + req_data['name'] = 'migration_api'; + req_data['func'] = method; + args['version'] = '1.0'; + + if (typeof(args) == 'string'){ + req_data['args'] = JSON.stringify(toArrayObject(args)); + } else { + req_data['args'] = JSON.stringify(args); + } + + $.post('/plugins/callback', req_data, function(data) { + layer.close(loadT); + if (!data.status){ + layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']}); + return; + } + + if(typeof(callback) == 'function'){ + callback(data); + } + },'json'); +}