插件开发介绍
120071. 江湖面板插件
江湖面板提供了一系列的插件,来方便用户管理服务器。在江湖面板中,你可以通过“软件管理”功能,来查看、安装、卸载、配置相关的插件。你也可以尝试开发一个提供自己需要的功能的插件,并把它上传到服务器中来使用。
本课介绍了江湖面板插件的代码结构、使用机制。并提供了一个自己开发的插件作为演示,你可以自行学习研究。
2. 理解江湖面板插件机制
插件目录结构:每一个江湖面板插件的代码都保存在
/www/server/jh-panel/plugins
目录中在每一个插件代码目录里,必备内容如下
├── ico.png # 插件图标
├── index.html # 插件管理界面的左边栏
├── index.py # 插件的主程序,接收用户通过面板界面发来的指令,按照指令进行必要的操作,并将操作结果返回到面板界面上,以便用户查看
├── info.json # 保存插件的标题、安装脚本、作者、更新日期等基本信息
├── install.sh # 插件安装与卸载脚本
└── js # 保存需要在插件管理界面上执行的Javascript脚本的目录
前后端通信流程:
- 面板与后端程序之间的通信,是通过
/www/server/jh-panel/class/core/plugins_api.py
统一进行的。 - 用户在面板界面上执行的操作,会由前端界面上的
javascript
脚本打包成为一个post
请求,发给后端的plugins_api.py
脚本。 plugins_api.py
收到发来的post
请求后,会将其转发给对应的插件- 插件中的
index.py
会根据post
请求中的method
参数,找到对应的函数,然后由对应的函数处理用户请求,并将处理结果返回给用户。
- 面板与后端程序之间的通信,是通过
3. 插件开发案例:命令管理器插件
在这一节,我们将以江湖面板中的“命令管理器”插件为例,讲解江湖面板的插件开发:
功能设置:
命令管理器插件的功能是保存用户自定义的Shell脚本,用户可以通过命令管理器的插件页面,执行选中的脚本,并在日志中查看执行结果。
安装卸载脚本:
命令管理器的安装与卸载脚本,都保存在插件目录下的install.sh
中:
# install.sh
#!/bin/bash
// 省略其他配置
# 安装脚本
Install_cmd()
{
echo '正在安装cmd...' > $install_tmp
mkdir -p $serverPath/cmd
echo $version > $serverPath/cmd/version.pl
echo '安装完成' > $install_tmp
}
# 卸载脚本
Uninstall_cmd()
{
echo '正在卸载cmd...' > $install_tmp
rm -rf $serverPath/cmd
echo "卸载完成" > $install_tmp
}
# 根据用户输入进行安装或卸载
action=$1
if [ "${1}" == 'install' ];then
Install_cmd
else
Uninstall_cmd
fi
插件管理界面首页:
插件管理界面首页的HTML代码,保存在index.html
中:
<div class="bt-form">
<div class="bt-w-main">
<div class="bt-w-menu">
<p class="bgw" onclick="refreshTable();">脚本列表</p>
</div>
<div class="bt-w-con pd15">
<div class="soft-man-con"></div>
</div>
</div>
</div>
<script type="text/javascript">
resetPluginWinWidth(760);
$.getScript( "/plugins/file?name=cmd&f=js/cmd.js", function(){
refreshTable();
});
</script>
在上面的代码定义了插件管理界面的左边栏,以及需要执行的Javascript脚本命令。
插件管理界面的Javascript脚本:
命令管理器插件的Javascript脚本都保存在js
目录中的cmd.js
中。部分代码如下:
# ./js/cmd.js
// 省略定义变量...
// 获取表格内容
function refreshTable() {
// 省略其他步骤...
requestApi('script_list',{showLoading: firstLoad}, function(data){
// 省略其他步骤...
var tbody = '';
var tmp = rdata['data'];
tableData = tmp;
for(var i=0;i<tmp.length;i++){
var opt = '';
var statusHtml = '';
var loadingStatus = tmp[i].loadingStatus || '';
var loadingStatusColor = {'执行成功': 'green', '执行失败': 'red'}[loadingStatus.trim()]
statusHtml = '<span style="color:' + (loadingStatusColor || '#cecece') + ';">' + (loadingStatus || '未执行') + '</span>';
if(!loadingStatus || loadingStatus != '执行中...') {
opt += '<a href="javascript:scriptExcute(\''+tmp[i].id+'\')" class="btlink">执行</a> | ';
}
tbody += '<tr>\
<td style="width: 180px;">'+tmp[i].name+'</td>\
<td style="width: 100px;">'+statusHtml+'</td>\
<td style="text-align: right;width: 280px;">\
'+opt+
'<a href="javascript:openScriptLogs(\''+tmp[i].id+'\')" class="btlink">日志</a> | ' +
'<a href="javascript:openEditItem(\''+tmp[i].id+'\')" class="btlink">编辑</a> | ' +
'<a href="javascript:deleteItem(\''+tmp[i].id+'\', \''+tmp[i].name+'\')" class="btlink">删除</a>\
</td>\
</tr>';
}
$(".plugin-table-body").html(tbody);
});
}
// 打开添加脚本的弹窗
function openCreateItem() {
addLayer = layer.open({
type: 1,
skin: 'demo-class',
area: '640px',
title: '添加脚本',
closeBtn: 1,
shift: 0,
shadeClose: false,
content: "\
<form class='bt-form pd20 pb70' id='addForm'>\
<div class='line'>\
<span class='tname'>脚本名称</span>\
<div class='info-r c4'>\
<input id='scriptName' class='bt-input-text' type='text' name='name' placeholder='脚本名称' style='width:458px' />\
</div>\
</div>\
<div class='line'>\
<span class='tname'>脚本内容</span>\
<div class='info-r c4'>\
<textarea id='scriptContent' class='bt-input-text' name='script' style='width:458px;height:100px;line-height:22px' /></textarea>\
</div>\
</div>\
<div class='bt-form-submit-btn'>\
<button type='button' class='btn btn-danger btn-sm btn-title' onclick='layer.close(addLayer)'>取消</button>\
<button type='button' class='btn btn-success btn-sm btn-title' onclick=\"submitCreateItem()\">提交</button>\
</div>\
</form>",
});
}
// 提交添加的脚本
function submitCreateItem(){
requestApi('script_add', {
name: $('#addForm #scriptName').val(),
script: $('#addForm #scriptContent').val()
}, function(data){
//省略回调函数...
});
}
// 向后端发送post请求
function requestApi(method,args,callback){
return new Promise(function(resolve, reject) {
// 省略其他步骤...
$.post('/plugins/run', {name:'cmd', func:method, args:_args}, function(data) {
// 省略回调函数...
},'json');
});
}
// 省略其他函数
在上面的代码中,你可以很清楚的看到每一步用户操作所调用的函数、向后端发送的请求,以及在前端显示的界面和脚本内容。
插件主程序:
命令管理器插件的主程序保存在index.html
中。部分代码如下:
# coding:utf-8
# 省略import、配置与其他函数
# 脚本列表
def scriptList():
data = getAll('script')
for item in data:
echo = item.get('echo', '')
# loadingStatus
loadingStatusCmd = "ls -R %s/script | grep %s_status" % (getServerDir() , echo)
loadingStatusExec = mw.execShell(loadingStatusCmd)
if loadingStatusExec[0] != '':
item['loadingStatus'] = mw.readFile(getServerDir() + '/script/' + echo + '_status')
return mw.returnJson(True, 'ok', data)
# 添加脚本
def scriptAdd():
args = getArgs()
data = checkArgs(args, ['name', 'script'])
if not data[0]:
return data[1]
name = args['name']
script = getScriptArg('script')
echo = mw.md5(str(time.time()) + '_docker')
id = int(time.time())
saveOne('script', id, {
'name': name,
'script': script,
'create_time': int(time.time()),
'echo': echo
})
statusFile = '%s/script/%s_status' % (getServerDir(), echo)
finalScript = """
{
touch %(statusFile)s\n
echo "执行中..." >> %(statusFile)s\n
cat /dev/null > %(statusFile)s\n
{
%(script)s\n
} && {
echo "执行成功" >> %(statusFile)s\n
}
} || {
echo "执行失败" >> %(statusFile)s\n
}
""" % {"statusFile": statusFile, "script": script}
makeScriptFile(echo + '.sh', finalScript)
return mw.returnJson(True, '添加成功!')
if __name__ == "__main__":
func = sys.argv[1]
elif func == 'script_list':
print(scriptList())
elif func == 'script_add':
print(scriptAdd())
# 省略其他选项
else:
print('error')
在上面的代码中,你可以看到获取脚本列表与添加脚本这两个功能在后台是如何实现的。也可以看到主程序中是如何根据前端发来的请求寻找待执行的函数的。
4. 使用自己开发的插件
在江湖面板中,你可以按照上面介绍的插件机制,开发一个自己的插件,上传到江湖面板中使用。
如何在江湖面板中使用自己开发的插件:
这一课提供了一个示例插件。你可以下载下来,然后,通过江湖面板“软件管理” --> “添加插件”功能上传进去,解压缩之后就可以在软件列表里看到这个新添加的插件了。点击安装,完成之后就可以开始使用了。
你自己所开发的插件,也可以采用打包上传的方式,添加到江湖面板中开始使用。