Compare commits

...

No commits in common. "1.0" and "main" have entirely different histories.
1.0 ... main

186 changed files with 17253 additions and 4011 deletions

View File

@ -5,13 +5,13 @@ DEFAULT_TIMEZONE = Asia/Shanghai
[DATABASE]
TYPE = mysql
HOSTNAME = localhost
DATABASE = btcloud
USERNAME = btcloud
PASSWORD = 123456
HOSTPORT = 3306
HOSTNAME = {dbhost}
DATABASE = {dbname}
USERNAME = {dbuser}
PASSWORD = {dbpwd}
HOSTPORT = {dbport}
CHARSET = utf8mb4
PREFIX = cloud_
PREFIX = {dbprefix}
DEBUG = false
[LANG]

114
README.md
View File

@ -1,48 +1,66 @@
# 宝塔面板第三方云端
这是一个用php开发的宝塔面板第三方云端站点程序。
你可以使用此程序搭建属于自己的宝塔面板第三方云端实现最新版宝塔面板私有化部署不与宝塔官方接口通信满足隐私安全合规需求。同时还可以去除面板强制绑定账号DIY面板功能等。
网站后台管理可一键同步宝塔官方的插件列表与增量更新插件包还有云端使用记录、IP黑白名单、操作日志、定时任务等功能。
本项目自带的宝塔安装包和更新包是7.9.2最新版已修改适配此第三方云端并且全开源无so等加密文件。
觉得该项目不错的可以给个Star~
## 声明
1.此项目只能以自用为目的,不得侵犯堡塔公司及其他第三方的知识产权和其他合法权利。
2.搭建使用此项目必须有一定的编程和Linux运维基础纯小白不建议使用。
## 环境要求
* `PHP` >= 7.4
* `MySQL` >= 5.6
* `fileinfo`扩展
* `ZipArchive`扩展
## 部署方法
- [下载最新版的Release包](https://github.com/flucont/btcloud/releases)
- 如果是下载的源码包,需要执行 `composer install --no-dev` 安装依赖如果是下载的Release包则不需要
- 设置网站运行目录为`public`
- 设置伪静态为`ThinkPHP`
- 导入`install.sql`到数据库
- 在`.env`里面修改数据库信息包括数据库地址HOSTNAME、数据库名DATABASE、用户名USERNAME、密码PASSWORD
- 访问`/admin`进入网站后台默认管理员用户名密码admin/123456
## 使用方法
- 在`系统基本设置`修改宝塔面板接口设置。你需要一个官方最新脚本安装并绑定账号的宝塔面板,用于获取最新插件列表及插件包。并根据界面提示安装好专用插件。
- 在`定时任务设置`执行所显示的命令从宝塔官方获取最新的插件列表并批量下载插件包(增量更新)。当然你也可以去插件列表,一个一个点击下载。
- 在public/install/src和update文件夹里面分别是bt安装包和更新包解压后源码里面全部的 www.example.com 替换成你自己搭建的云端域名然后重新打包。可使用VSCode等支持批量替换的软件。
- 将bt安装脚本public/install/install_6.0.sh和更新脚本update6.sh里面的 www.example.com 替换成你自己搭建的云端域名。
- 访问网站`/download`查看使用此第三方云端的一键安装脚本
## 其他
- [bt官方更新包修改记录](./wiki/update.md)
# 宝塔面板第三方云端
这是一个用php开发的宝塔面板第三方云端站点程序。
你可以使用此程序搭建属于自己的宝塔面板第三方云端实现最新版宝塔面板私有化部署不与宝塔官方接口通信满足隐私安全合规需求。同时还可以去除面板强制绑定账号DIY面板功能等。
网站后台管理可一键同步宝塔官方的插件列表与增量更新插件包还有云端使用记录、IP黑白名单、操作日志、定时任务等功能。
本项目自带 **宝塔Linux面板**、**宝塔Windows面板**、**aaPanel面板**、**宝塔云监控** 的最新版安装包和更新包,已修改适配此第三方云端,并且全开源,无.so等加密文件。
觉得该项目不错的可以给个Star~
## 声明
1.此项目只能以自用为目的,不得侵犯堡塔公司及其他第三方的知识产权和其他合法权利。
2.搭建使用此项目必须有一定的编程和Linux运维基础纯小白不建议使用。
## 环境要求
* `PHP` >= 7.4
* `MySQL` >= 5.6
* `fileinfo`扩展
* `ZipArchive`扩展
## 部署方法
- [下载最新版的Release包](https://github.com/flucont/btcloud/releases)
- 如果是下载的源码包,需要执行 `composer install --no-dev` 安装依赖如果是下载的Release包则不需要
- 设置网站运行目录为`public`
- 设置伪静态为`ThinkPHP`
- 访问网站,会自动跳转到安装页面,根据提示安装完成
## 使用方法
- 在`批量替换工具`执行页面显示的命令可将bt安装包、更新包和脚本文件里面的`http://www.example.com`批量替换成当前网站的网址。
- 在`系统基本设置`修改宝塔面板接口设置。你需要准备一个使用官方最新脚本安装并绑定账号的宝塔面板,用于获取最新插件列表及插件包。并根据界面提示安装好专用插件。
- 在`定时任务设置`执行所显示的命令从宝塔官方获取最新的插件列表并批量下载插件包(增量更新)。当然你也可以去插件列表,一个一个点击下载。
- 访问网站`/download`查看使用此第三方云端的一键安装脚本。
## 更新方法
- [下载最新版的Release包](https://github.com/flucont/btcloud/releases)
- 上传覆盖除data文件夹以外的全部文件
- 后台使用批量替换工具->获取最新插件列表->修改软件版本设置里面的版本号
## 其他
- [Linux面板官方更新包修改记录](./wiki/update.md)
- [Windows面板官方更新包修改记录](./wiki/updatewin.md)
- [aaPanel面板官方更新包修改记录](./wiki/aapanel.md)
- [宝塔云监控安装包修改记录](./wiki/btmonitor.md)
- 宝塔面板官方版与此第三方云端版对比:
| | 官方版 | 此第三方云端版 |
| ---------- | ------------------------------------------------------------ | -------------------------------------------------- |
| 版本更新 | 支持 | 支持 |
| 面板广告 | 有广告 | 无广告 |
| 是否全开源 | 没有全开源 | 全开源 |
| 资源占用 | 各种统计上报等任务,资源占用略高 | 去除了很多无用的定时任务,资源占较少 |
| 兼容性 | 由于编译的so文件有系统架构限制兼容的系统仅限已编译的so对应的系统架构 | 由于全开源没有已编译的so文件因此无系统架构限制 |

77
app/command/Clean.php Normal file
View File

@ -0,0 +1,77 @@
<?php
declare (strict_types = 1);
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\facade\Db;
use think\facade\Config;
use app\lib\Plugins;
class Clean extends Command
{
protected function configure()
{
$this->setName('clean')
->setDescription('the clean command');
}
protected function execute(Input $input, Output $output)
{
$res = Db::name('config')->cache('configs',0)->column('value','key');
Config::set($res, 'sys');
if(config_get('bt_url')){
$this->clean_plugins($input, $output, 'Linux');
}
if(config_get('wbt_url')){
$this->clean_plugins($input, $output, 'Windows');
}
config_set('cleantime', date('Y-m-d H:i:s'));
}
private function clean_plugins(Input $input, Output $output, $os){
$data_dir = get_data_dir($os) . 'plugins/';
$file_list = [];
$json_arr = Plugins::get_plugin_list($os);
if(count($json_arr['list']) == 0) return;
foreach($json_arr['list'] as $plugin){
foreach($plugin['versions'] as $version){
$ver = $version['m_version'].'.'.$version['version'];
if(!isset($version['download'])){
$file_list[] = $plugin['name'].'-'.$ver;
}
}
}
$count = 0;
$dir = opendir($data_dir.'package');
while(false !== ( $file = readdir($dir)) ) {
if($file == '.' || $file == '..') continue;
$name = str_replace('.zip', '', $file);
if(!in_array($name, $file_list)){
$filepath = $data_dir . 'package/' . $file;
unlink($filepath);
$count++;
}
}
$output->writeln($os.'成功清理'.$count.'个历史版本插件包');
$count = 0;
$dir = opendir($data_dir.'folder');
while(false !== ( $file = readdir($dir)) ) {
if($file == '.' || $file == '..') continue;
if(!in_array($file, $file_list)){
$filepath = $data_dir . 'folder/' . $file;
deleteDir($filepath);
$count++;
}
}
$output->writeln($os.'成功清理'.$count.'个历史版本插件目录');
}
}

386
app/command/CleanViteJs.php Normal file
View File

@ -0,0 +1,386 @@
<?php
declare (strict_types = 1);
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\facade\Db;
use think\facade\Config;
use app\lib\Plugins;
class CleanViteJs extends Command
{
protected function configure()
{
$this->setName('cleanvitejs')
->addArgument('dir', Argument::REQUIRED, '/BTPanel/static/vite/js/路径')
->setDescription('处理宝塔面板vite/js文件');
}
protected function execute(Input $input, Output $output)
{
$dir = trim($input->getArgument('dir'));
if(!file_exists($dir)){
$output->writeln('目录不存在');
return;
}
//$this->handlefile($dir.'/DockerImages.js');
$this->checkdir($dir);
}
private function getExtendCode($content, $part, $n = 1, $startChar = '{', $endChar = '}'){
if(!$part) return false;
$length = strlen($content);
$start = strpos($content, $part);
if($start===false)return false;
$end = $start+strlen($part);
$start--;
$c = 0;
for($i=$start;$i>=0;$i--){
if(substr($content,$i,1) == $startChar) $c++;
if(substr($content,$i,1) == $endChar) $c--;
if($c == $n){
$start = $i;
break;
}
}
$c = 0;
for($i=$end;$i<=$length;$i++){
if(substr($content,$i,1) == $endChar) $c++;
if(substr($content,$i,1) == $startChar) $c--;
if($c == $n){
$end = $i;
break;
}
}
return substr($content, $start, $end - $start + 1);
}
private function getExtendFunction($content, $part, $startChar = '(', $endChar = ')'){
$code = $this->getExtendCode($content, $part, 1, $startChar, $endChar);
if(!$code) return false;
$start = strpos($content, $code) - 1;
$end = $start + strlen($code);
for($i=$start;$i>=0;$i--){
$char = substr($content,$i,1);
if(!ctype_alpha($char)&&$char!='_'){
$start = $i+1;
break;
}
}
if(substr($content,$start-1,1) == ',') $start--;
else if(substr($content,$end+1,1) == ',') $end++;
return substr($content, $start, $end - $start + 1);
}
private function checkdir($basedir){
if($dh=opendir($basedir)){
while (($file=readdir($dh)) !== false){
if($file != '.' && $file != '..'){
if(!is_dir($basedir.'/'.$file) && substr($file,-3)=='.js'){
$this->handlefile($basedir.'/'.$file);
}else if(!is_dir($basedir.'/'.$file) && substr($file,-4)=='.map'){
unlink($basedir.'/'.$file);
}
}
}
closedir($dh);
}
}
private function str_replace_once($needle, $replace, $haystack) {
$pos = strpos($haystack, $needle);
if ($pos === false) {
return $haystack;
}
return substr_replace($haystack, $replace, $pos, strlen($needle));
}
private function handlefile($filepath){
//echo $filepath."\n";
$file = file_get_contents($filepath);
if(!$file)return;
$flag = false;
if(strpos($file, 'window.location.protocol.indexOf("https")>=0')!==false){ //index
$file = str_replace('window.location.protocol.indexOf("https")>=0', '!0', $file);
$code = $this->getExtendCode($file, 'isGetCoupon:', 2);
if($code){
$file = str_replace($code, '{}', $file);
}
$file = preg_replace('!recommendShow:\w+,!', 'recommendShow:!1,', $file, 1);
$code = $this->getExtendCode($file, '"打开需求反馈"', 1, '[', ']');
if($code){
$file = str_replace($code, '[]', $file);
}
$flag = true;
}
if(strpos($file, '"点击打开调查问卷"')!==false){ //index
$code = $this->getExtendCode($file, '"点击打开调查问卷"', 2);
if($code){
$file = str_replace($code, '{}', $file);
}
$flag = true;
}
if(strpos($file, '您有{0}个优惠券待领取')!==false){ //win-index
$code = $this->getExtendCode($file, 'isGetCoupon:', 2);
if($code){
$file = str_replace($code, '{}', $file);
}
$flag = true;
}
if(strpos($file, '论坛求助')!==false && strpos($file, '"/other/customer-qrcode.png"')!==false){ //main
$code = $this->getExtendCode($file, '"微信公众号"', 1);
$code = $this->getExtendFunction($file, $code);
$start = strpos($file, $code) - 1;
for($i=$start;$i>=0;$i--){
if(substr($file,$i,1) == ','){
$start = $i;
break;
}
}
$code = $this->getExtendCode($file, '"/other/customer-qrcode.png"', 2);
$code = $this->getExtendFunction($file, $code);
$end = strpos($file, $code)+strlen($code);
$code = substr($file, $start, $end - $start);
$file = str_replace($code, '', $file);
$flag = true;
}
if(strpos($file, 'useNegotiate')!==false){ //utils
$code = $this->getExtendCode($file, 'createPeerConnection()', 1);
if($code){
$file = str_replace($code, '{}', $file);
}
$file = preg_replace('!computed\(\(\)=>"calc"===\w+\.\w+\.type\)!', '!1', $file);
$file = preg_replace('!computed\(\(\)=>"input"===\w+\.\w+\.type\)!', '!1', $file);
$file = preg_replace('!computed\(function\(\)\{return"calc"===\w+\.\w+\.type\}\)!', '!1', $file);
$file = preg_replace('!computed\(function\(\)\{return"input"===\w+\.\w+\.type\}\)!', '!1', $file);
$file = preg_replace('!computed\(\(\)=>\w+\.\w+\.type==="calc"\)!', '!1', $file);
$file = preg_replace('!computed\(\(\)=>\w+\.\w+\.type==="input"\)!', '!1', $file);
$file = preg_replace('!computed\(function\(\)\{return\w+\.\w+\.type==="calc"\}\)!', '!1', $file);
$file = preg_replace('!computed\(function\(\)\{return\w+\.\w+\.type==="input"\}\)!', '!1', $file);
$code = $this->getExtendCode($file, '"自动部署"', 2);
if($code){
$file = str_replace($code.',', '', $file);
$file = str_replace($code, '', $file);
}
$flag = true;
}
if(strpos($file, '"sqlserver管理"')!==false && strpos($file, '"iis管理"')!==false){ //win-utils
$file = preg_replace('!"calc"===\w+\.\w+\.type!', '!1', $file);
$file = preg_replace('!"input"===\w+\.\w+\.type!', '!1', $file);
$flag = true;
}
if(strpos($file, '请冷静几秒钟,确认以下要删除的数据')!==false && strpos($file, '"计算结果:"')!==false){ //site
$code = $this->getExtendCode($file, '"计算结果:"', 1, '[', ']');
$code = $this->getExtendFunction($file, $code);
$file = str_replace($code, '', $file);
$file = preg_replace('!\w+\.sum===\w+\.addend1\+\w+\.addend2!', '!0', $file);
$file = preg_replace('!value=\!0,(\w+)\.value=5;!', 'value=!1,$1.value=0;', $file);
$flag = true;
}
if(strpos($file, '"left-waf"')!==false && strpos($file, '"iconWaf"')!==false){ //site.table
$code = $this->getExtendCode($file, '"left-waf"');
$code = $this->getExtendCode($file, $code, 1, '[', ']');
$code = $this->getExtendFunction($file, $code);
$file = str_replace($code, '""', $file);
$flag = true;
}
if(strpos($file, 'svgtofont-left-waf')!==false && strpos($file, '"iconWaf"')!==false){ //site.table
$code = $this->getExtendCode($file, 'svgtofont-left-waf');
$code = $this->getExtendCode($file, $code, 1, '[', ']');
$code = $this->getExtendFunction($file, $code);
$file = str_replace($code, '""', $file);
$flag = true;
}
if(strpos($file, 'label:"商用SSL证书"')!==false){ //site-ssl
$code = $this->getExtendFunction($file, 'label:"商用SSL证书"', '{', '}');
$file = str_replace($code, '', $file);
$code = $this->getExtendFunction($file, 'label:"测试证书"', '{', '}');
if($code){
$file = str_replace($code, '', $file);
}
$code = $this->getExtendFunction($file, 'label:"宝塔证书"', '{', '}');
if($code){
$file = str_replace($code, '', $file);
}
$code = $this->getExtendCode($file, '"购买商业证书"', 2);
if($code){
$code2 = str_replace('"busSslList"', '"letsEncryptList"', $code);
$code2 = str_replace($this->getExtendFunction($code, '"购买商业证书"'), '', $code2);
$file = str_replace($code, $code2, $file);
}
$file = str_replace('.value="busSslList"', '.value="letsEncryptList"', $file);
$flag = true;
}
if(strpos($file, '"busSslList"')!==false && strpos($filepath, '/useStore')){ //site-ssl
$file = str_replace('"busSslList"', '"currentCertInfo"', $file);
$flag = true;
}
if(strpos($file, '"商用SSL"')!==false){ //ssl
$code = $this->getExtendFunction($file, '"商用SSL"', '{', '}');
$file = str_replace($code, '', $file);
$code = $this->getExtendFunction($file, '"宝塔证书"', '{', '}');
if($code){
$file = str_replace($code, '', $file);
}
for($i=0;$i<3;$i++){
$code = $this->getExtendCode($file, ',"联系客服"', 2, '[', ']');
if($code){
$file = str_replace($code, '[]', $file);
}
}
$flag = true;
}
if(strpos($file, '"SSL-CERTIFICATE-STORE"')!==false){ //ssl
$file = str_replace('("ssl")', '("encrypt")', $file);
$flag = true;
}
if(strpos($file, '"商业证书"')!==false && strpos($file, 'name:"busSslList"')!==false && strpos($file, 'IIS配置')!==false){ //win-ssl
$code = $this->getExtendFunction($file, 'name:"busSslList"', '{', '}');
$file = str_replace($code, '', $file);
$code = $this->getExtendFunction($file, 'name:"trustAsiaList"', '{', '}');
$file = str_replace($code, '', $file);
$file = $this->str_replace_once('"busSslList"', '"currentCertInfo"', $file);
$flag = true;
}
if(strpos($file, '如果您希望添加其它Docker应用')!==false){
$code = $this->getExtendCode($file, '如果您希望添加其它Docker应用', 1, '[', ']');
$code = $this->getExtendFunction($file, $code);
$file = str_replace($code, '', $file);
$flag = true;
}
if(strpos($file, '"recom-view"')!==false){ //soft
$code = $this->getExtendCode($file, '"recom-view"');
$code = $this->getExtendFunction($file, $code);
$file = str_replace($code, '', $file);
$flag = true;
}
if(strpos($file, '"打开插件文件目录"')!==false && strpos($file, '"(续费)"')!==false){ //soft.table
$code = $this->getExtendFunction($file, '"(续费)"');
$file = str_replace($code, '""', $file);
$code = $this->getExtendCode($file, 'activity_id:47', 2);
if($code){
$file = str_replace($code, '{}', $file);
}
$flag = true;
}
if(strpos($file, 'path:"register"')!==false){ //domain
$code = $this->getExtendCode($file, 'path:"register"');
$file = str_replace($code.',', '', $file);
$flag = true;
}
for($i=0;$i<5;$i++){
$code = $this->getExtendCode($file, ',"需求反馈"', 1, '[', ']');
if($code){
if(strpos($code, 'svgtofont-desired')){
$file = str_replace($code, '[]', $file);
}else{
$code = $this->getExtendFunction($code, ',"需求反馈"');
$file = str_replace($code, '', $file);
}
$flag = true;
}
}
$code = $this->getExtendCode($file, '("需求反馈",-1)', 1, '[', ']');
if($code){
$file = str_replace($code, '[]', $file);
$flag = true;
}
$code = $this->getExtendCode($file, '(" 需求反馈 ",-1)', 1, '[', ']');
if($code){
$file = str_replace($code, '[]', $file);
$flag = true;
}
$code = $this->getExtendFunction($file, 'label:"需求反馈",', '{', '}');
if($code){
$file = str_replace($code, '', $file);
$flag = true;
}
if(strpos($file, '暂无搜索结果,<span class="text-primary cursor-pointer NpsDialog">提交需求反馈</span>')!==false){
$file = str_replace('暂无搜索结果,<span class="text-primary cursor-pointer NpsDialog">提交需求反馈</span>', '暂无搜索结果', $file);
$flag = true;
}
if(strpos($file, 'getReceiveCoupon()')!==false){ //aapanel-优惠券
$code = $this->getExtendCode($file, 'getReceiveCoupon()');
$file = str_replace($code, '{}', $file);
$flag = true;
}
if(strpos($file, '"Site.DelSite.index_1"')!==false){ //aapanel-site
$code = $this->getExtendCode($file, '"Site.DelSite.index_10"', 3, '(', ')');
if($code){
$code = $this->getExtendFunction($file, $code);
$file = str_replace($code, '', $file);
$file = preg_replace('@\w+\.value!==\w+\.value\+\w+\.value@', '!1', $file);
$file = preg_replace('@null==\w+\.value\|\|null==\w+\.value@', '!1', $file);
$file = str_replace('disabled:!0', 'disabled:!1', $file);
$flag = true;
}
}
if(strpos($file, '"Component.Confirm.index_4"')!==false){ //aapanel-public
$code = $this->getExtendCode($file, '"Component.Confirm.index_4"', 2, '(', ')');
if($code){
$code = $this->getExtendFunction($file, $code);
$file = str_replace($code, '', $file);
$file = preg_replace('@\w+\.value===\w+\.value\+\w+\.value@', '!0', $file);
$flag = true;
}
$code = $this->getExtendCode($file, '"Component.Confirm.index_1"', 1, '(', ')');
if($code){
$code = $this->getExtendFunction($file, $code);
$file = str_replace($code, '', $file);
$file = preg_replace('@\w+\.value===\w+\.value\?@', '!0?', $file);
$flag = true;
}
}
if(strpos($file, '"Component.Feedback.index_7"')!==false){ //aapanel-需求反馈
$code = $this->getExtendCode($file, '"Component.Feedback.index_7"', 2);
if($code){
$code = $this->getExtendFunction($file, $code);
$file = str_replace($code, '', $file);
$flag = true;
}
}
if(strpos($file, '"Soft.index_16"')!==false){ //aapanel-soft
$code = $this->getExtendCode($file, '"Soft.index_16"', 2);
if($code){
$file = str_replace($code, '{}', $file);
$flag = true;
}
}
if(!$flag) return;
if(file_put_contents($filepath, $file)){
echo '文件:'.$filepath.' 处理成功'."\n";
}else{
echo '文件:'.$filepath.' 处理失败,可能无写入权限'."\n";
}
}
}

115
app/command/DecryptFile.php Normal file
View File

@ -0,0 +1,115 @@
<?php
declare (strict_types = 1);
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\facade\Db;
use think\facade\Config;
use app\lib\Plugins;
class DecryptFile extends Command
{
protected function configure()
{
$this->setName('decrypt')
->addArgument('type', Argument::REQUIRED, '文件类型,plugin:插件文件,module:模块文件,classdir:宝塔class目录,all:所有py文件')
->addArgument('file', Argument::REQUIRED, '文件路径')
->addArgument('os', Argument::OPTIONAL, '操作系统:Windows/Linux')
->setDescription('解密宝塔面板python文件');
}
protected function execute(Input $input, Output $output)
{
$type = trim($input->getArgument('type'));
$file = trim($input->getArgument('file'));
if(!file_exists($file)){
$output->writeln('文件不存在');
return;
}
if($type == 'plugin'){
$os = trim($input->getArgument('os'));
try{
if(Plugins::decode_plugin_main_local($file, $os)){
$output->writeln('文件解密成功!');
}else{
$output->writeln('文件解密失败!');
}
}catch(\Exception $e){
$output->writeln($e->getMessage());
}
}elseif($type == 'module'){
$this->decode_module_file($output, $file);
}elseif($type == 'classdir'){
$file = rtrim($file, '/');
if(file_exists($file.'/common.py')){
$class_v = 1;
}elseif(file_exists($file.'/common_v2.py')){
$class_v = 2;
}else{
$output->writeln('当前路径非宝塔面板class目录');
return;
}
$dirs = glob($file.'/*Model'.($class_v == 2 ? 'V2' : ''));
foreach($dirs as $dir){
if(!is_dir($dir))continue;
$files = glob($dir.'/*Model.py');
foreach($files as $filepath){
$this->decode_module_file($output, $filepath);
}
}
if($class_v == 2){
$filepath = $file.'/wp_toolkit/core.py';
if(file_exists($filepath)){
$this->decode_module_file($output, $filepath);
}
}else{
$filepath = $file.'/public/authorization.py';
if(file_exists($filepath)){
$this->decode_module_file($output, $filepath);
}
}
}elseif($type == 'all'){
$file = rtrim($file, '/');
$this->scan_all_file($input, $output, $file);
}else{
$output->writeln('未知文件类型');
}
}
private function scan_all_file(Input $input, Output $output, $path) {
$dir = opendir($path);
while(false !== ( $file = readdir($dir)) ) {
if (( $file != '.' ) && ( $file != '..' )) {
$filepath = $path . '/' . $file;
if ( is_dir($filepath) ) {
$this->scan_all_file($input, $output, $filepath);
}
elseif(substr($filepath, -3) == '.py') {
$this->decode_module_file($output, $filepath);
}
}
}
closedir($dir);
}
private function decode_module_file(Output $output, $filepath){
try{
$res = Plugins::decode_module_file($filepath);
if($res == 2){
$output->writeln('文件解密失败:'.$filepath);
}elseif($res == 1){
$output->writeln('文件解密成功:'.$filepath);
}
}catch(\Exception $e){
$output->writeln($e->getMessage().''.$filepath);
}
}
}

View File

@ -25,60 +25,73 @@ class UpdateAll extends Command
$res = Db::name('config')->cache('configs',0)->column('value','key');
Config::set($res, 'sys');
if(!config_get('bt_type') && config_get('bt_url') || config_get('bt_type')==1 && config_get('bt_surl')){
$this->process_plugins($input, $output, 'Linux');
}
if(!config_get('wbt_type') && config_get('wbt_url') || config_get('wbt_type')==1 && config_get('wbt_surl')){
$this->process_plugins($input, $output, 'Windows');
}
if(!config_get('enbt_type') && config_get('enbt_url') || config_get('enbt_type')==1 && config_get('enbt_surl')){
$this->process_plugins($input, $output, 'en');
}
config_set('runtime', date('Y-m-d H:i:s'));
}
private function process_plugins(Input $input, Output $output, $os){
//刷新插件列表
if(!$this->refresh_plugin_list($input, $output)){
if(!$this->refresh_plugin_list($input, $output, $os)){
return;
}
$count = 0;
$type = intval(config_get('updateall_type'));
if($os=='Windows'){
$type = intval(config_get('updateall_type_win'));
}elseif($os=='en'){
$type = intval(config_get('updateall_type_en'));
}else{
$type = intval(config_get('updateall_type'));
}
$json_arr = Plugins::get_plugin_list();
$json_arr = Plugins::get_plugin_list($os);
//循环下载缺少的插件
foreach($json_arr['list'] as $plugin){
if($type == 0 && ($plugin['type']==8 || $plugin['type']==12) || $type == 1 && $plugin['type']==12 || $plugin['type']==10 || $plugin['type']==5) continue;
if(in_array($plugin['name'], \app\lib\BtPlugins::$skip_plugins)) continue;
foreach($plugin['versions'] as $version){
$ver = $version['m_version'].'.'.$version['version'];
if(isset($version['download'])){
if(!file_exists(get_data_dir().'plugins/other/'.$version['download'])){
$this->download_plugin($input, $output, $plugin['name'], $ver);
if(!$this->download_plugin($input, $output, $plugin['name'], $ver, $os)){
sleep(1);
$this->download_plugin($input, $output, $plugin['name'], $ver, $os);
}
sleep(1);
$count++;
}
}else{
if(!file_exists(get_data_dir().'plugins/package/'.$plugin['name'].'-'.$ver.'.zip')){
$this->download_plugin($input, $output, $plugin['name'], $ver);
if(!file_exists(get_data_dir($os).'plugins/package/'.$plugin['name'].'-'.$ver.'.zip')){
if(!$this->download_plugin($input, $output, $plugin['name'], $ver, $os)){
sleep(1);
$this->download_plugin($input, $output, $plugin['name'], $ver, $os);
}
sleep(1);
$count++;
}
}
}
}
$imgcount = 0;
//循环下载缺少的插件图片
foreach($json_arr['list'] as $plugin){
if(isset($plugin['min_image']) && strpos($plugin['min_image'], 'fname=')){
$fname = substr($plugin['min_image'], strpos($plugin['min_image'], '?fname=')+7);
if(!file_exists(get_data_dir().'plugins/other/'.$fname)){
$this->download_plugin_image($input, $output, $fname);
sleep(1);
$imgcount++;
}
}
}
$output->writeln('本次成功下载'.$count.'个插件'.($imgcount>0?''.$imgcount.'个图片':''));
config_set('runtime', date('Y-m-d H:i:s'));
$output->writeln($os.'本次成功下载'.$count.'个插件');
}
private function refresh_plugin_list(Input $input, Output $output){
private function refresh_plugin_list(Input $input, Output $output, $os){
try{
Plugins::refresh_plugin_list();
Db::name('log')->insert(['uid' => 1, 'action' => '刷新插件列表', 'data' => '刷新插件列表成功', 'addtime' => date("Y-m-d H:i:s")]);
$output->writeln('刷新插件列表成功');
Plugins::refresh_plugin_list($os);
Db::name('log')->insert(['uid' => 1, 'action' => '刷新插件列表', 'data' => '刷新'.$os.'插件列表成功', 'addtime' => date("Y-m-d H:i:s")]);
$output->writeln('刷新'.$os.'插件列表成功');
return true;
}catch(\Exception $e){
$output->writeln($e->getMessage());
@ -87,12 +100,12 @@ class UpdateAll extends Command
}
}
private function download_plugin(Input $input, Output $output, $plugin_name, $version){
private function download_plugin(Input $input, Output $output, $plugin_name, $version, $os){
$fullname = $plugin_name.'-'.$version;
try{
Plugins::download_plugin($plugin_name, $version);
Db::name('log')->insert(['uid' => 1, 'action' => '下载插件', 'data' => $fullname, 'addtime' => date("Y-m-d H:i:s")]);
$output->writeln('下载插件: '.$fullname.' 成功');
Plugins::download_plugin($plugin_name, $version, $os);
Db::name('log')->insert(['uid' => 1, 'action' => '下载插件', 'data' => $fullname.' os:'.$os, 'addtime' => date("Y-m-d H:i:s")]);
$output->writeln('下载'.$os.'插件: '.$fullname.' 成功');
return true;
}catch(\Exception $e){
$output->writeln($fullname.' '.$e->getMessage());
@ -100,16 +113,4 @@ class UpdateAll extends Command
return false;
}
}
private function download_plugin_image(Input $input, Output $output, $fname){
try{
Plugins::download_plugin_other($fname);
$output->writeln('下载图片: '.$fname.' 成功');
return true;
}catch(\Exception $e){
$output->writeln($fname.' '.$e->getMessage());
errorlog($fname.' '.$e->getMessage());
return false;
}
}
}

View File

@ -2,8 +2,14 @@
// 应用公共文件
use think\facade\Db;
function get_data_dir(){
return app()->getRootPath().'data/';
function get_data_dir($os = 'Linux'){
if($os == 'en'){
return app()->getRootPath().'data/en/';
}elseif($os == 'Windows'){
return app()->getRootPath().'data/win/';
}else{
return app()->getRootPath().'data/';
}
}
@ -48,6 +54,17 @@ function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
}
}
function random($length, $numeric = 0) {
$seed = base_convert(md5(microtime().$_SERVER['DOCUMENT_ROOT']), 16, $numeric ? 10 : 35);
$seed = $numeric ? (str_replace('0', '', $seed).'012340567890') : ($seed.'zZ'.strtoupper($seed));
$hash = '';
$max = strlen($seed) - 1;
for($i = 0; $i < $length; $i++) {
$hash .= $seed[mt_rand(0, $max)];
}
return $hash;
}
function get_curl($url, $post=0, $referer=0, $cookie=0, $header=0, $ua=0, $nobody=0, $addheader=0)
{
$ch = curl_init();
@ -169,8 +186,144 @@ function checkIfActive($string) {
return null;
}
function checkDomain($domain){
if(empty($domain) || !preg_match('/^[-$a-z0-9_*.]{2,512}$/i', $domain) || (stripos($domain, '.') === false) || substr($domain, -1) == '.' || substr($domain, 0 ,1) == '.' || substr($domain, 0 ,1) == '*' && substr($domain, 1 ,1) != '.' || substr_count($domain, '*')>1 || strpos($domain, '*')>0 || strlen($domain)<4) return false;
return true;
}
function errorlog($msg){
$handle = fopen(app()->getRootPath()."record.txt", 'a');
fwrite($handle, date('Y-m-d H:i:s')."\t".$msg."\r\n");
fclose($handle);
}
function licenseEncrypt($data, $key){
$iv = substr($key, 0, 16);
return openssl_encrypt($data, 'AES-256-CBC', $key, 0, $iv);
}
function licenseDecrypt($data, $key){
$iv = substr($key, 0, 16);
return openssl_decrypt($data, 'AES-256-CBC', $key, 0, $iv);
}
function generateKeyPairs(){
$pkey_dir = app()->getRootPath().'data/config/';
$public_key_path = $pkey_dir.'public_key.pem';
$private_key_path = $pkey_dir.'private_key.pem';
if(file_exists($public_key_path) && file_exists($private_key_path)){
return [file_get_contents($public_key_path), file_get_contents($private_key_path)];
}
$pkey_config = ['private_key_bits'=>4096];
$pkey_res = openssl_pkey_new($pkey_config);
$private_key = '';
openssl_pkey_export($pkey_res, $private_key, null, $pkey_config);
$pkey_details = openssl_pkey_get_details($pkey_res);
if(!$pkey_details) return false;
$public_key = $pkey_details['key'];
file_put_contents($public_key_path, $public_key);
file_put_contents($private_key_path, $private_key);
return [$public_key, $private_key];
}
function pemToBase64($pem){
$lines = explode("\n", $pem);
$encoded = '';
foreach ($lines as $line) {
if (trim($line) != '' && strpos($line, '-----BEGIN') === false && strpos($line, '-----END') === false) {
$encoded .= trim($line);
}
}
return $encoded;
}
function makeSelfSignSSL(string $commonName, array $domainList, $validity = 3650){
// 加载 CA 证书和私钥
$dir = app()->getBasePath().'script/';
$caCert = file_get_contents($dir.'ca.crt');
$caPrivateKey = file_get_contents($dir.'ca.key');
$opensslConfigFile = sys_get_temp_dir().'/openssl'.time().mt_rand(1000, 9999).'.cnf';
$opensslConfigContent = <<<EOF
[req]
req_extensions = extension_section
x509_extensions = extension_section
distinguished_name = dn
[dn]
[extension_section]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
EOF;
$ip_index = 1;
$dns_index = 1;
foreach ($domainList as $value) {
if(empty($value)) continue;
if(filter_var($value, FILTER_VALIDATE_IP)){
$opensslConfigContent .= sprintf("\nIP.%d = %s", $ip_index, $value);
$ip_index++;
}else{
$opensslConfigContent .= sprintf("\nDNS.%d = %s", $dns_index, $value);
$dns_index++;
}
}
if(!file_put_contents($opensslConfigFile, $opensslConfigContent)) return false;
// 生成域名证书的私钥和 CSR
$domainPrivateKey = openssl_pkey_new([
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
]);
if(!$domainPrivateKey) return false;
$csrConfig = ['digest_alg' => 'sha256', 'config' => $opensslConfigFile];
$domainCsr = openssl_csr_new([
'commonName' => $commonName
], $domainPrivateKey, $csrConfig);
if(!$domainCsr) return false;
// 生成域名证书
$domainCertificate = openssl_csr_sign($domainCsr, $caCert, $caPrivateKey, $validity, $csrConfig);
if(!$domainCertificate) return false;
// 导出域名证书
openssl_x509_export($domainCertificate, $certificate);
openssl_pkey_export($domainPrivateKey, $privateKey);
$certificate .= $caCert;
unlink($opensslConfigFile);
return ['cert' => $certificate, 'key' => $privateKey];
}
function deleteDir($dir){
$rd = opendir($dir);
if (!$rd) {
return false;
}
while (($file = readdir($rd)) !== false) {
if ($file == '.' || $file == '..') {
continue;
}
$file = $dir . '/' . $file;
if (is_dir($file)) {
deleteDir($file);
}
else {
unlink($file);
}
}
closedir($rd);
rmdir($dir);
return true;
}

View File

@ -1,341 +1,466 @@
<?php
namespace app\controller;
use app\BaseController;
use think\facade\Db;
use think\facade\View;
use think\facade\Request;
use app\lib\Btapi;
use app\lib\Plugins;
class Admin extends BaseController
{
public function verifycode()
{
return captcha();
}
public function login(){
if(request()->islogin){
return redirect('/admin');
}
if(request()->isAjax()){
$username = input('post.username',null,'trim');
$password = input('post.password',null,'trim');
$code = input('post.code',null,'trim');
if(empty($username) || empty($password)){
return json(['code'=>-1, 'msg'=>'用户名或密码不能为空']);
}
if(!captcha_check($code)){
return json(['code'=>-1, 'msg'=>'验证码错误']);
}
if($username == config_get('admin_username') && $password == config_get('admin_password')){
Db::name('log')->insert(['uid' => 0, 'action' => '登录后台', 'data' => 'IP:'.$this->clientip, 'addtime' => date("Y-m-d H:i:s")]);
$session = md5($username.config_get('admin_password'));
$expiretime = time()+2562000;
$token = authcode("{$username}\t{$session}\t{$expiretime}", 'ENCODE', config_get('syskey'));
cookie('admin_token', $token, ['expire' => $expiretime, 'httponly' => true]);
config_set('admin_lastlogin', date('Y-m-d H:i:s'));
return json(['code'=>0]);
}else{
return json(['code'=>-1, 'msg'=>'用户名或密码错误']);
}
}
return view();
}
public function logout()
{
cookie('admin_token', null);
return redirect('/admin/login');
}
public function index()
{
$stat = ['total'=>0, 'free'=>0, 'pro'=>0, 'ltd'=>0, 'third'=>0];
$json_arr = Plugins::get_plugin_list();
if($json_arr){
foreach($json_arr['list'] as $plugin){
$stat['total']++;
if($plugin['type']==10) $stat['third']++;
elseif($plugin['type']==12) $stat['ltd']++;
elseif($plugin['type']==8) $stat['pro']++;
elseif($plugin['type']==5 || $plugin['type']==6 || $plugin['type']==7) $stat['free']++;
}
}
$stat['runtime'] = Db::name('config')->where('key','runtime')->value('value') ?? '<font color="red">未运行</font>';
$stat['record_total'] = Db::name('record')->count();
$stat['record_isuse'] = Db::name('record')->whereTime('usetime', '>=', strtotime('-7 days'))->count();
View::assign('stat', $stat);
$tmp = 'version()';
$mysqlVersion = Db::query("select version()")[0][$tmp];
$info = [
'framework_version' => app()::VERSION,
'php_version' => PHP_VERSION,
'mysql_version' => $mysqlVersion,
'software' => $_SERVER['SERVER_SOFTWARE'],
'os' => php_uname(),
'date' => date("Y-m-d H:i:s"),
];
View::assign('info', $info);
return view();
}
public function set(){
if(request()->isAjax()){
$params = Request::param();
foreach ($params as $key => $value) {
config_set($key, $value);
}
cache('configs', NULL);
return json(['code'=>0]);
}
$mod = input('param.mod', 'sys');
View::assign('mod', $mod);
View::assign('conf', config('sys'));
$runtime = Db::name('config')->where('key','runtime')->value('value') ?? '<font color="red">未运行</font>';
View::assign('runtime', $runtime);
return view();
}
public function setaccount(){
$params = Request::param();
if(isset($params['username']))$params['username']=trim($params['username']);
if(isset($params['oldpwd']))$params['oldpwd']=trim($params['oldpwd']);
if(isset($params['newpwd']))$params['newpwd']=trim($params['newpwd']);
if(isset($params['newpwd2']))$params['newpwd2']=trim($params['newpwd2']);
if(empty($params['username'])) return json(['code'=>-1, 'msg'=>'用户名不能为空']);
config_set('admin_username', $params['username']);
if(!empty($params['oldpwd']) && !empty($params['newpwd']) && !empty($params['newpwd2'])){
if(config_get('admin_password') != $params['oldpwd']){
return json(['code'=>-1, 'msg'=>'旧密码不正确']);
}
if($params['newpwd'] != $params['newpwd2']){
return json(['code'=>-1, 'msg'=>'两次新密码输入不一致']);
}
config_set('admin_password', $params['newpwd']);
}
cache('configs', NULL);
cookie('admin_token', null);
return json(['code'=>0]);
}
public function testbturl(){
$bt_url = input('post.bt_url');
$bt_key = input('post.bt_key');
if(!$bt_url || !$bt_key)return json(['code'=>-1, 'msg'=>'参数不能为空']);
$btapi = new Btapi($bt_url, $bt_key);
$result = $btapi->get_config();
if($result && isset($result['status']) && $result['status']==1){
$result = $btapi->get_user_info();
if($result && isset($result['username'])){
return json(['code'=>0, 'msg'=>'面板连接测试成功!']);
}else{
return json(['code'=>-1, 'msg'=>'面板连接测试成功,但未安装专用插件']);
}
}else{
return json(['code'=>-1, 'msg'=>isset($result['msg'])?$result['msg']:'面板地址无法连接']);
}
}
public function plugins(){
$typelist = [];
$json_arr = Plugins::get_plugin_list();
if($json_arr){
foreach($json_arr['type'] as $type){
$typelist[$type['id']] = $type['title'];
}
}
View::assign('typelist', $typelist);
return view();
}
public function plugins_data(){
$type = input('post.type/d');
$keyword = input('post.keyword', null, 'trim');
$json_arr = Plugins::get_plugin_list();
if(!$json_arr) return json([]);
$typelist = [];
foreach($json_arr['type'] as $row){
$typelist[$row['id']] = $row['title'];
}
$list = [];
foreach($json_arr['list'] as $plugin){
if($type > 0 && $plugin['type']!=$type) continue;
if(!empty($keyword) && $keyword != $plugin['name'] && stripos($plugin['title'], $keyword)===false) continue;
$versions = [];
foreach($plugin['versions'] as $version){
$ver = $version['m_version'].'.'.$version['version'];
if(isset($version['download'])){
$status = false;
if(file_exists(get_data_dir().'plugins/other/'.$version['download'])){
$status = true;
}
$versions[] = ['status'=>$status, 'type'=>1, 'version'=>$ver, 'download'=>$version['download'], 'md5'=>$version['md5']];
}else{
$status = false;
if(file_exists(get_data_dir().'plugins/package/'.$plugin['name'].'-'.$ver.'.zip')){
$status = true;
}
$versions[] = ['status'=>$status, 'type'=>0, 'version'=>$ver];
}
}
$list[] = [
'id' => $plugin['id'],
'name' => $plugin['name'],
'title' => $plugin['title'],
'type' => $plugin['type'],
'typename' => $typelist[$plugin['type']],
'desc' => str_replace('target="_blank"','target="_blank" rel="noopener noreferrer"',$plugin['ps']),
'price' => $plugin['price'],
'author' => isset($plugin['author']) ? $plugin['author'] : '官方',
'versions' => $versions
];
}
return json($list);
}
public function download_plugin(){
$name = input('post.name', null, 'trim');
$version = input('post.version', null, 'trim');
if(!$name || !$version) return json(['code'=>-1, 'msg'=>'参数不能为空']);
try{
Plugins::download_plugin($name, $version);
Db::name('log')->insert(['uid' => 0, 'action' => '下载插件', 'data' => $name.'-'.$version, 'addtime' => date("Y-m-d H:i:s")]);
return json(['code'=>0,'msg'=>'下载成功']);
}catch(\Exception $e){
return json(['code'=>-1, 'msg'=>$e->getMessage()]);
}
}
public function refresh_plugins(){
try{
Plugins::refresh_plugin_list();
Db::name('log')->insert(['uid' => 0, 'action' => '刷新插件列表', 'data' => '刷新插件列表成功', 'addtime' => date("Y-m-d H:i:s")]);
return json(['code'=>0,'msg'=>'获取最新插件列表成功!']);
}catch(\Exception $e){
return json(['code'=>-1, 'msg'=>$e->getMessage()]);
}
}
public function record(){
return view();
}
public function record_data(){
$ip = input('post.ip', null, 'trim');
$offset = input('post.offset/d');
$limit = input('post.limit/d');
$select = Db::name('record');
if(!empty($ip)){
$select->where('ip', $ip);
}
$total = $select->count();
$rows = $select->order('id','desc')->limit($offset, $limit)->select();
return json(['total'=>$total, 'rows'=>$rows]);
}
public function log(){
return view();
}
public function log_data(){
$action = input('post.action', null, 'trim');
$offset = input('post.offset/d');
$limit = input('post.limit/d');
$select = Db::name('log');
if(!empty($action)){
$select->where('action', $action);
}
$total = $select->count();
$rows = $select->order('id','desc')->limit($offset, $limit)->select();
return json(['total'=>$total, 'rows'=>$rows]);
}
public function list(){
$type = input('param.type', 'black');
View::assign('type', $type);
View::assign('typename', $type=='white'?'白名单':'黑名单');
return view();
}
public function list_data(){
$type = input('param.type', 'black');
$ip = input('post.ip', null, 'trim');
$offset = input('post.offset/d');
$limit = input('post.limit/d');
$tablename = $type == 'black' ? 'black' : 'white';
$select = Db::name($tablename);
if(!empty($ip)){
$select->where('ip', $ip);
}
$total = $select->count();
$rows = $select->order('id','desc')->limit($offset, $limit)->select();
return json(['total'=>$total, 'rows'=>$rows]);
}
public function list_op(){
$type = input('param.type', 'black');
$tablename = $type == 'black' ? 'black' : 'white';
$act = input('post.act', null);
if($act == 'get'){
$id = input('post.id/d');
if(!$id) return json(['code'=>-1, 'msg'=>'no id']);
$data = Db::name($tablename)->where('id', $id)->find();
return json(['code'=>0, 'data'=>$data]);
}elseif($act == 'add'){
$ip = input('post.ip', null, 'trim');
if(!$ip) return json(['code'=>-1, 'msg'=>'IP不能为空']);
if(Db::name($tablename)->where('ip', $ip)->find()){
return json(['code'=>-1, 'msg'=>'该IP已存在']);
}
Db::name($tablename)->insert([
'ip' => $ip,
'enable' => 1,
'addtime' => date("Y-m-d H:i:s")
]);
return json(['code'=>0, 'msg'=>'succ']);
}elseif($act == 'edit'){
$id = input('post.id/d');
$ip = input('post.ip', null, 'trim');
if(!$id || !$ip) return json(['code'=>-1, 'msg'=>'IP不能为空']);
if(Db::name($tablename)->where('ip', $ip)->where('id', '<>', $id)->find()){
return json(['code'=>-1, 'msg'=>'该IP已存在']);
}
Db::name($tablename)->where('id', $id)->update([
'ip' => $ip
]);
return json(['code'=>0, 'msg'=>'succ']);
}elseif($act == 'enable'){
$id = input('post.id/d');
$enable = input('post.enable/d');
if(!$id) return json(['code'=>-1, 'msg'=>'no id']);
Db::name($tablename)->where('id', $id)->update([
'enable' => $enable
]);
return json(['code'=>0, 'msg'=>'succ']);
}elseif($act == 'del'){
$id = input('post.id/d');
if(!$id) return json(['code'=>-1, 'msg'=>'no id']);
Db::name($tablename)->where('id', $id)->delete();
return json(['code'=>0, 'msg'=>'succ']);
}
return json(['code'=>-1, 'msg'=>'no act']);
}
<?php
namespace app\controller;
use app\BaseController;
use think\facade\Db;
use think\facade\View;
use think\facade\Request;
use think\facade\Cache;
use app\lib\Btapi;
use app\lib\Plugins;
class Admin extends BaseController
{
public function verifycode()
{
return captcha();
}
public function login(){
if(request()->islogin){
return redirect('/admin');
}
if(request()->isAjax()){
$username = input('post.username',null,'trim');
$password = input('post.password',null,'trim');
$code = input('post.code',null,'trim');
if(empty($username) || empty($password)){
return json(['code'=>-1, 'msg'=>'用户名或密码不能为空']);
}
if(!captcha_check($code)){
return json(['code'=>-1, 'msg'=>'验证码错误']);
}
if($username == config_get('admin_username') && $password == config_get('admin_password')){
Db::name('log')->insert(['uid' => 0, 'action' => '登录后台', 'data' => 'IP:'.$this->clientip, 'addtime' => date("Y-m-d H:i:s")]);
$session = md5($username.config_get('admin_password'));
$expiretime = time()+2562000;
$token = authcode("{$username}\t{$session}\t{$expiretime}", 'ENCODE', config_get('syskey'));
cookie('admin_token', $token, ['expire' => $expiretime, 'httponly' => true]);
config_set('admin_lastlogin', date('Y-m-d H:i:s'));
return json(['code'=>0]);
}else{
return json(['code'=>-1, 'msg'=>'用户名或密码错误']);
}
}
return view();
}
public function logout()
{
cookie('admin_token', null);
return redirect('/admin/login');
}
public function index()
{
$stat = ['total'=>0, 'free'=>0, 'pro'=>0, 'ltd'=>0, 'third'=>0];
$json_arr = Plugins::get_plugin_list();
if($json_arr){
foreach($json_arr['list'] as $plugin){
$stat['total']++;
if($plugin['type']==10) $stat['third']++;
elseif($plugin['type']==12) $stat['ltd']++;
elseif($plugin['type']==8) $stat['pro']++;
elseif($plugin['type']==5 || $plugin['type']==6 || $plugin['type']==7) $stat['free']++;
}
}
$stat['runtime'] = Db::name('config')->where('key','runtime')->value('value') ?? '<font color="red">未运行</font>';
$stat['record_total'] = Db::name('record')->count();
$stat['record_isuse'] = Db::name('record')->whereTime('usetime', '>=', strtotime('-7 days'))->count();
View::assign('stat', $stat);
$tmp = 'version()';
$mysqlVersion = Db::query("select version()")[0][$tmp];
$info = [
'framework_version' => app()::VERSION,
'php_version' => PHP_VERSION,
'mysql_version' => $mysqlVersion,
'software' => $_SERVER['SERVER_SOFTWARE'],
'os' => php_uname(),
'date' => date("Y-m-d H:i:s"),
];
View::assign('info', $info);
return view();
}
public function set(){
if(request()->isAjax()){
$params = Request::param();
foreach ($params as $key => $value) {
config_set($key, $value);
}
cache('configs', NULL);
return json(['code'=>0]);
}
$mod = input('param.mod', 'sys');
View::assign('mod', $mod);
View::assign('conf', config('sys'));
$runtime = Db::name('config')->where('key','runtime')->value('value') ?? '<font color="red">未运行</font>';
View::assign('runtime', $runtime);
View::assign('is_user_www', isset($_SERVER['USER']) && $_SERVER['USER'] == 'www');
return view();
}
public function setaccount(){
$params = Request::param();
if(isset($params['username']))$params['username']=trim($params['username']);
if(isset($params['oldpwd']))$params['oldpwd']=trim($params['oldpwd']);
if(isset($params['newpwd']))$params['newpwd']=trim($params['newpwd']);
if(isset($params['newpwd2']))$params['newpwd2']=trim($params['newpwd2']);
if(empty($params['username'])) return json(['code'=>-1, 'msg'=>'用户名不能为空']);
config_set('admin_username', $params['username']);
if(!empty($params['oldpwd']) && !empty($params['newpwd']) && !empty($params['newpwd2'])){
if(config_get('admin_password') != $params['oldpwd']){
return json(['code'=>-1, 'msg'=>'旧密码不正确']);
}
if($params['newpwd'] != $params['newpwd2']){
return json(['code'=>-1, 'msg'=>'两次新密码输入不一致']);
}
config_set('admin_password', $params['newpwd']);
}
cache('configs', NULL);
cookie('admin_token', null);
return json(['code'=>0]);
}
public function testbturl(){
$bt_type = input('post.bt_type/d');
if($bt_type == 1){
$bt_surl = input('post.bt_surl');
if(!$bt_surl)return json(['code'=>-1, 'msg'=>'参数不能为空']);
$res = get_curl($bt_surl . 'api/SetupCount');
if(strpos($res, 'ok')!==false){
return json(['code'=>0, 'msg'=>'第三方云端连接测试成功!']);
}else{
return json(['code'=>-1, 'msg'=>'第三方云端连接测试失败']);
}
}else{
$bt_url = input('post.bt_url');
$bt_key = input('post.bt_key');
$os = input('post.os');
if(!$bt_url || !$bt_key)return json(['code'=>-1, 'msg'=>'参数不能为空']);
$btapi = new Btapi($bt_url, $bt_key);
if ($os == 'win') {
$result = $btapi->get_config_go();
if($result && isset($result['config'])){
$result = $btapi->get_user_info();
if($result && isset($result['username'])){
return json(['code'=>0, 'msg'=>'面板连接测试成功!']);
}else{
return json(['code'=>-1, 'msg'=>'面板连接测试成功,但未安装专用插件/未登录账号']);
}
}else{
return json(['code'=>-1, 'msg'=>isset($result['msg'])?$result['msg']:'面板地址无法连接']);
}
} else {
$result = $btapi->get_config();
if($result && isset($result['status']) && ($result['status']==1 || isset($result['sites_path']))){
$result = $btapi->get_user_info();
if($result && isset($result['username'])){
return json(['code'=>0, 'msg'=>'面板连接测试成功!']);
}else{
return json(['code'=>-1, 'msg'=>'面板连接测试成功,但未安装专用插件/未登录账号']);
}
}else{
return json(['code'=>-1, 'msg'=>isset($result['msg'])?$result['msg']:'面板地址无法连接']);
}
}
}
}
public function plugins(){
$typelist = [];
$json_arr = Plugins::get_plugin_list();
if($json_arr){
foreach($json_arr['type'] as $type){
if($type['title'] == '一键部署') continue;
$typelist[$type['id']] = $type['title'];
}
}
View::assign('typelist', $typelist);
View::assign('skip_plugins', \app\lib\BtPlugins::$skip_plugins);
return view();
}
public function pluginswin(){
$typelist = [];
$json_arr = Plugins::get_plugin_list('Windows');
if($json_arr){
foreach($json_arr['type'] as $type){
if($type['title'] == '一键部署') continue;
$typelist[$type['id']] = $type['title'];
}
}
View::assign('typelist', $typelist);
return view();
}
public function pluginsen(){
$typelist = [];
$json_arr = Plugins::get_plugin_list('en');
if($json_arr){
foreach($json_arr['type'] as $type){
if($type['title'] == '一键部署') continue;
$typelist[$type['id']] = $type['title'];
}
}
View::assign('typelist', $typelist);
return view();
}
public function plugins_data(){
$type = input('post.type/d');
$keyword = input('post.keyword', null, 'trim');
$os = input('get.os');
if(!$os) $os = 'Linux';
$json_arr = Plugins::get_plugin_list($os);
if(!$json_arr) return json([]);
$typelist = [];
foreach($json_arr['type'] as $row){
$typelist[$row['id']] = $row['title'];
}
$list = [];
foreach($json_arr['list'] as $plugin){
if($type > 0 && $plugin['type']!=$type) continue;
if(!empty($keyword) && $keyword != $plugin['name'] && stripos($plugin['title'], $keyword)===false) continue;
$versions = [];
foreach($plugin['versions'] as $version){
$ver = $version['m_version'].'.'.$version['version'];
if(isset($version['download'])){
$status = false;
if(file_exists(get_data_dir().'plugins/other/'.$version['download'])){
$status = true;
}
$versions[] = ['status'=>$status, 'type'=>1, 'version'=>$ver, 'download'=>$version['download'], 'md5'=>$version['md5']];
}else{
$status = false;
if(file_exists(get_data_dir($os).'plugins/package/'.$plugin['name'].'-'.$ver.'.zip')){
$status = true;
}
$versions[] = ['status'=>$status, 'type'=>0, 'version'=>$ver];
}
}
if($plugin['name'] == 'obs') $plugin['ps'] = substr($plugin['ps'],0,strpos($plugin['ps'],'<a '));
$list[] = [
'id' => $plugin['id'],
'name' => $plugin['name'],
'title' => $plugin['title'],
'type' => $plugin['type'],
'typename' => isset($typelist[$plugin['type']]) ? $typelist[$plugin['type']] : '未知',
'desc' => str_replace('target="_blank"','target="_blank" rel="noopener noreferrer"',$plugin['ps']),
'price' => $plugin['price'],
'author' => isset($plugin['author']) ? $plugin['author'] : '官方',
'versions' => $versions
];
}
return json($list);
}
public function download_plugin(){
$name = input('post.name', null, 'trim');
$version = input('post.version', null, 'trim');
$os = input('post.os');
if(!$os) $os = 'Linux';
if(!$name || !$version) return json(['code'=>-1, 'msg'=>'参数不能为空']);
try{
Plugins::download_plugin($name, $version, $os);
Db::name('log')->insert(['uid' => 0, 'action' => '下载插件', 'data' => $name.'-'.$version.' os:'.$os, 'addtime' => date("Y-m-d H:i:s")]);
return json(['code'=>0,'msg'=>'下载成功']);
}catch(\Exception $e){
return json(['code'=>-1, 'msg'=>$e->getMessage()]);
}
}
public function refresh_plugins(){
$os = input('get.os');
if(!$os) $os = 'Linux';
try{
Plugins::refresh_plugin_list($os);
Db::name('log')->insert(['uid' => 0, 'action' => '刷新插件列表', 'data' => '刷新'.$os.'插件列表成功', 'addtime' => date("Y-m-d H:i:s")]);
return json(['code'=>0,'msg'=>'获取最新插件列表成功!']);
}catch(\Exception $e){
return json(['code'=>-1, 'msg'=>$e->getMessage()]);
}
}
public function record(){
return view();
}
public function record_data(){
$ip = input('post.ip', null, 'trim');
$offset = input('post.offset/d');
$limit = input('post.limit/d');
$select = Db::name('record');
if(!empty($ip)){
$select->where('ip', $ip);
}
$total = $select->count();
$rows = $select->order('id','desc')->limit($offset, $limit)->select();
return json(['total'=>$total, 'rows'=>$rows]);
}
public function log(){
return view();
}
public function log_data(){
$action = input('post.action', null, 'trim');
$offset = input('post.offset/d');
$limit = input('post.limit/d');
$select = Db::name('log');
if(!empty($action)){
$select->where('action', $action);
}
$total = $select->count();
$rows = $select->order('id','desc')->limit($offset, $limit)->select();
return json(['total'=>$total, 'rows'=>$rows]);
}
public function list(){
$type = input('param.type', 'black');
View::assign('type', $type);
View::assign('typename', $type=='white'?'白名单':'黑名单');
return view();
}
public function list_data(){
$type = input('param.type', 'black');
$ip = input('post.ip', null, 'trim');
$offset = input('post.offset/d');
$limit = input('post.limit/d');
$tablename = $type == 'black' ? 'black' : 'white';
$select = Db::name($tablename);
if(!empty($ip)){
$select->where('ip', $ip);
}
$total = $select->count();
$rows = $select->order('id','desc')->limit($offset, $limit)->select();
return json(['total'=>$total, 'rows'=>$rows]);
}
public function list_op(){
$type = input('param.type', 'black');
$tablename = $type == 'black' ? 'black' : 'white';
$act = input('post.act', null);
if($act == 'get'){
$id = input('post.id/d');
if(!$id) return json(['code'=>-1, 'msg'=>'no id']);
$data = Db::name($tablename)->where('id', $id)->find();
return json(['code'=>0, 'data'=>$data]);
}elseif($act == 'add'){
$ip = input('post.ip', null, 'trim');
if(!$ip) return json(['code'=>-1, 'msg'=>'IP不能为空']);
if(Db::name($tablename)->where('ip', $ip)->find()){
return json(['code'=>-1, 'msg'=>'该IP已存在']);
}
Db::name($tablename)->insert([
'ip' => $ip,
'enable' => 1,
'addtime' => date("Y-m-d H:i:s")
]);
return json(['code'=>0, 'msg'=>'succ']);
}elseif($act == 'edit'){
$id = input('post.id/d');
$ip = input('post.ip', null, 'trim');
if(!$id || !$ip) return json(['code'=>-1, 'msg'=>'IP不能为空']);
if(Db::name($tablename)->where('ip', $ip)->where('id', '<>', $id)->find()){
return json(['code'=>-1, 'msg'=>'该IP已存在']);
}
Db::name($tablename)->where('id', $id)->update([
'ip' => $ip
]);
return json(['code'=>0, 'msg'=>'succ']);
}elseif($act == 'enable'){
$id = input('post.id/d');
$enable = input('post.enable/d');
if(!$id) return json(['code'=>-1, 'msg'=>'no id']);
Db::name($tablename)->where('id', $id)->update([
'enable' => $enable
]);
return json(['code'=>0, 'msg'=>'succ']);
}elseif($act == 'del'){
$id = input('post.id/d');
if(!$id) return json(['code'=>-1, 'msg'=>'no id']);
Db::name($tablename)->where('id', $id)->delete();
return json(['code'=>0, 'msg'=>'succ']);
}
return json(['code'=>-1, 'msg'=>'no act']);
}
public function deplist(){
$deplist_linux = get_data_dir().'config/deployment_list.json';
$deplist_win = get_data_dir('Windows').'config/deployment_list.json';
$deplist_linux_time = file_exists($deplist_linux) ? date("Y-m-d H:i:s", filemtime($deplist_linux)) : '不存在';
$deplist_win_time = file_exists($deplist_win) ? date("Y-m-d H:i:s", filemtime($deplist_win)) : '不存在';
View::assign('deplist_linux_time', $deplist_linux_time);
View::assign('deplist_win_time', $deplist_win_time);
return view();
}
public function refresh_deplist(){
$os = input('get.os');
if(!$os) $os = 'Linux';
try{
Plugins::refresh_deplist($os);
Db::name('log')->insert(['uid' => 0, 'action' => '刷新一键部署列表', 'data' => '刷新'.$os.'一键部署列表成功', 'addtime' => date("Y-m-d H:i:s")]);
return json(['code'=>0,'msg'=>'获取最新一键部署列表成功!']);
}catch(\Exception $e){
return json(['code'=>-1, 'msg'=>$e->getMessage()]);
}
}
public function cleancache(){
Cache::clear();
return json(['code'=>0,'msg'=>'succ']);
}
public function ssl(){
if(request()->isAjax()){
$domain_list = input('post.domain_list', null, 'trim');
$common_name = input('post.common_name', null, 'trim');
$validity = input('post.validity/d');
if(empty($domain_list) || empty($validity)){
return json(['code'=>-1, 'msg'=>'参数不能为空']);
}
$array = explode("\n", $domain_list);
$domain_list = [];
foreach($array as $domain){
$domain = trim($domain);
if(empty($domain)) continue;
if(!checkDomain($domain)) return json(['code'=>-1, 'msg'=>'域名或IP格式不正确:'.$domain]);
$domain_list[] = $domain;
}
if(empty($domain_list)) return json(['code'=>-1, 'msg'=>'域名列表不能为空']);
if(empty($common_name)) $common_name = $domain_list[0];
$result = makeSelfSignSSL($common_name, $domain_list, $validity);
if(!$result){
return json(['code'=>-1, 'msg'=>'生成证书失败']);
}
return json(['code'=>0, 'msg'=>'生成证书成功', 'cert'=>$result['cert'], 'key'=>$result['key']]);
}
$dir = app()->getBasePath().'script/';
$ssl_path = app()->getRootPath().'public/ssl/baota_root.pfx';
$ssl_path_mac = app()->getRootPath().'public/ssl/baota_root.crt';
$isca = file_exists($dir.'ca.crt') && file_exists($dir.'ca.key') && file_exists($ssl_path) && file_exists($ssl_path_mac);
View::assign('isca', $isca);
return view();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -8,13 +8,13 @@ class Index extends BaseController
{
public function index()
{
return 'Server is ok';
return '';
}
public function download()
{
if(config_get('download_page') == '0' && !request()->islogin){
return redirect('/admin/login');
return 'need login';
}
View::assign('siteurl', request()->root(true));
return view();

View File

@ -0,0 +1,83 @@
<?php
namespace app\controller;
use PDO;
use Exception;
use app\BaseController;
use think\facade\View;
use think\facade\Cache;
class Install extends BaseController
{
public function index()
{
if (file_exists(app()->getRootPath().'.env')){
return '当前已经安装成功,如果需要重新安装,请手动删除根目录.env文件';
}
if(request()->isPost()){
$mysql_host = input('post.mysql_host', null, 'trim');
$mysql_port = intval(input('post.mysql_port', '3306'));
$mysql_user = input('post.mysql_user', null, 'trim');
$mysql_pwd = input('post.mysql_pwd', null, 'trim');
$mysql_name = input('post.mysql_name', null, 'trim');
$mysql_prefix = input('post.mysql_prefix', 'cloud_', 'trim');
$admin_username = input('post.admin_username', null, 'trim');
$admin_password = input('post.admin_password', null, 'trim');
if(!$mysql_host || !$mysql_user || !$mysql_pwd || !$mysql_name || !$admin_username || !$admin_password){
return json(['code'=>0, 'msg'=>'必填项不能为空']);
}
$configdata = file_get_contents(app()->getRootPath().'.env.example');
$configdata = str_replace(['{dbhost}','{dbname}','{dbuser}','{dbpwd}','{dbport}','{dbprefix}'], [$mysql_host, $mysql_name, $mysql_user, $mysql_pwd, $mysql_port, $mysql_prefix], $configdata);
try{
$DB=new PDO("mysql:host=".$mysql_host.";dbname=".$mysql_name.";port=".$mysql_port,$mysql_user,$mysql_pwd);
}catch(Exception $e){
if($e->getCode() == 2002){
$errorMsg='连接数据库失败:数据库地址填写错误!';
}elseif($e->getCode() == 1045){
$errorMsg='连接数据库失败:数据库用户名或密码填写错误!';
}elseif($e->getCode() == 1049){
$errorMsg='连接数据库失败:数据库名不存在!';
}else{
$errorMsg='连接数据库失败:'.$e->getMessage();
}
return json(['code'=>0, 'msg'=>$errorMsg]);
}
$DB->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$DB->exec("set sql_mode = ''");
$DB->exec("set names utf8");
$sqls=file_get_contents(app()->getRootPath().'install.sql');
$sqls=explode(';', $sqls);
$sqls[]="REPLACE INTO `".$mysql_prefix."config` VALUES ('syskey', '".random(16)."')";
$sqls[]="REPLACE INTO `".$mysql_prefix."config` VALUES ('admin_username', '".addslashes($admin_username)."')";
$sqls[]="REPLACE INTO `".$mysql_prefix."config` VALUES ('admin_password', '".addslashes($admin_password)."')";
$success=0;$error=0;$errorMsg=null;
foreach ($sqls as $value) {
$value=trim($value);
if(empty($value))continue;
$value = str_replace('cloud_',$mysql_prefix,$value);
if($DB->exec($value)===false){
$error++;
$dberror=$DB->errorInfo();
$errorMsg.=$dberror[2]."\n";
}else{
$success++;
}
}
if(empty($errorMsg)){
if(!file_put_contents(app()->getRootPath().'.env', $configdata)){
return json(['code'=>0, 'msg'=>'保存失败,请确保网站根目录有写入权限']);
}
Cache::clear();
return json(['code'=>1, 'msg'=>'安装完成成功执行SQL语句'.$success.'条']);
}else{
return json(['code'=>0, 'msg'=>$errorMsg]);
}
}
return view();
}
}

308
app/lib/BtPlugins.php Normal file
View File

@ -0,0 +1,308 @@
<?php
namespace app\lib;
use Exception;
use ZipArchive;
class BtPlugins
{
private $btapi;
private $os;
//需屏蔽的插件名称列表
public static $block_plugins = ['dns', 'bt_boce', 'ssl_verify', 'firewall', 'KylinOperatingSystem', 'KingdeeApusicDistributedCache', 'BorlandCacheServer', 'GBase8s', 'KingdeeApusicLoadBalancer', 'BorlandWebServer'];
public static $skip_plugins = ['php_filter', 'enterprise_backup', 'tamper_drive'];
public function __construct($os){
$this->os = $os;
if($os == 'en'){
$bt_url = config_get('enbt_url');
$bt_key = config_get('enbt_key');
}elseif($os == 'Windows'){
$bt_url = config_get('wbt_url');
$bt_key = config_get('wbt_key');
}else{
$bt_url = config_get('bt_url');
$bt_key = config_get('bt_key');
}
if(!$bt_url || !$bt_key) throw new Exception('请先配置好宝塔面板接口信息');
$this->btapi = new Btapi($bt_url, $bt_key);
}
//获取插件列表
public function get_plugin_list(){
$result = $this->btapi->get_plugin_list();
if($result && isset($result['list']) && isset($result['type'])){
if(empty($result['list']) || empty($result['type'])){
throw new Exception('获取插件列表失败:插件列表为空');
}
$newlist = [];
foreach($result['list'] as $item){
if(!in_array($item['name'], self::$block_plugins)) $newlist[] = $item;
}
$result['list'] = $newlist;
return $result;
}else{
throw new Exception('获取插件列表失败:'.(isset($result['msg'])?$result['msg']:'面板连接失败'));
}
}
//下载插件(自动判断是否第三方)
public function download_plugin($plugin_name, $version, $plugin_info){
if($plugin_info['type'] == 10 && isset($plugin_info['versions'][0]['download'])){
if($plugin_info['price'] == 0){
$this->btapi->create_plugin_other_order($plugin_info['id']);
}
$fname = $plugin_info['versions'][0]['download'];
$filemd5 = $plugin_info['versions'][0]['md5'];
$this->download_plugin_other($fname, $filemd5);
if(isset($plugin_info['min_image']) && strpos($plugin_info['min_image'], 'fname=')){
$fname = substr($plugin_info['min_image'], strpos($plugin_info['min_image'], '?fname=')+7);
$this->download_plugin_other($fname);
}
}else{
$this->download_plugin_package($plugin_name, $version);
}
}
//下载插件包
private function download_plugin_package($plugin_name, $version){
$filepath = get_data_dir($this->os).'plugins/package/'.$plugin_name.'-'.$version.'.zip';
$result = $this->btapi->get_plugin_filename($plugin_name, $version);
if($result && isset($result['status'])){
if($result['status'] == true){
$filename = $result['filename'];
$this->download_file($filename, $filepath);
if(file_exists($filepath)){
$zip = new ZipArchive;
if ($zip->open($filepath) === true)
{
$plugins_dir = get_data_dir($this->os).'plugins/folder/'.$plugin_name.'-'.$version;
$zip->extractTo($plugins_dir, $plugin_name.'/'.$plugin_name.'_main.py');
$zip->close();
$main_filepath = $plugins_dir.'/'.$plugin_name.'/'.$plugin_name.'_main.py';
if(file_exists($main_filepath) && filesize($main_filepath)>10){
if(!strpos(file_get_contents($main_filepath), 'import ')){ //加密py文件需要解密
$this->decode_plugin_main($plugin_name, $version, $main_filepath);
$this->noauth_plugin_main($main_filepath);
$zip->open($filepath, ZipArchive::CREATE);
$zip->addFile($main_filepath, $plugin_name.'/'.$plugin_name.'_main.py');
$zip->close();
}
}
deleteDir($plugins_dir);
}else{
unlink($filepath);
throw new Exception('插件包解压缩失败');
}
return true;
}else{
throw new Exception('下载插件包失败,本地文件不存在');
}
}else{
throw new Exception('下载插件包失败:'.($result['msg']?$result['msg']:'未知错误'));
}
}else{
throw new Exception('下载插件包失败,接口返回错误');
}
}
//下载插件主程序文件
public function download_plugin_main($plugin_name, $version){
$filepath = get_data_dir($this->os).'plugins/main/'.$plugin_name.'-'.$version.'.dat';
$result = $this->btapi->get_plugin_main_filename($plugin_name, $version);
if($result && isset($result['status'])){
if($result['status'] == true){
$filename = $result['filename'];
$this->download_file($filename, $filepath);
if(file_exists($filepath)){
return true;
}else{
throw new Exception('下载插件主程序文件失败,本地文件不存在');
}
}else{
throw new Exception('下载插件主程序文件失败:'.($result['msg']?$result['msg']:'未知错误'));
}
}else{
throw new Exception('下载插件主程序文件失败,接口返回错误');
}
}
//解密并下载插件主程序文件
private function decode_plugin_main($plugin_name, $version, $main_filepath){
if($this->decode_plugin_main_local($main_filepath)) return true;
$result = $this->btapi->get_decode_plugin_main($plugin_name, $version);
if($result && isset($result['status'])){
if($result['status'] == true){
$filename = $result['filename'];
$this->download_file($filename, $main_filepath);
return true;
}else{
throw new Exception('解密插件主程序文件失败:'.($result['msg']?$result['msg']:'未知错误'));
}
}else{
throw new Exception('解密插件主程序文件失败,接口返回错误');
}
}
//本地解密插件主程序文件
public function decode_plugin_main_local($main_filepath){
$userinfo = $this->btapi->get_user_info();
if(isset($userinfo['uid'])){
$src = file_get_contents($main_filepath);
if($src===false)throw new Exception('文件打开失败');
if(!$src || strpos($src, 'import ')!==false)return true;
$uid = $userinfo['uid'];
$serverid = $userinfo['serverid'];
$key = md5(substr($serverid, 10, 16).$uid.$serverid);
$iv = md5($key.$serverid);
$key = substr($key, 8, 16);
$iv = substr($iv, 8, 16);
$data_arr = explode("\n", $src);
$de_text = '';
foreach($data_arr as $data){
$data = trim($data);
if(!empty($data)){
$tmp = openssl_decrypt($data, 'aes-128-cbc', $key, 0, $iv);
if($tmp !== false) $de_text .= $tmp;
}
}
if(!empty($de_text) && strpos($de_text, 'import ')!==false){
file_put_contents($main_filepath, $de_text);
return true;
}
return false;
}else{
throw new Exception('解密插件主程序文件失败,获取用户信息失败');
}
}
//去除插件主程序文件授权校验
private function noauth_plugin_main($main_filepath){
$data = file_get_contents($main_filepath);
if(!$data) return false;
$data = str_replace('\'http://www.bt.cn/api/panel/get_soft_list_test', 'public.GetConfigValue(\'home\')+\'/api/panel/get_soft_list_test', $data);
$data = str_replace('\'https://www.bt.cn/api/panel/get_soft_list_test', 'public.GetConfigValue(\'home\')+\'/api/panel/get_soft_list_test', $data);
$data = str_replace('\'http://www.bt.cn/api/panel/get_soft_list', 'public.GetConfigValue(\'home\')+\'/api/panel/get_soft_list', $data);
$data = str_replace('\'https://www.bt.cn/api/panel/get_soft_list', 'public.GetConfigValue(\'home\')+\'/api/panel/get_soft_list', $data);
$data = str_replace('\'http://www.bt.cn/api/panel/notpro', 'public.GetConfigValue(\'home\')+\'/api/panel/notpro', $data);
$data = str_replace('\'https://www.bt.cn/api/panel/notpro', 'public.GetConfigValue(\'home\')+\'/api/panel/notpro', $data);
$data = str_replace('\'http://www.bt.cn/api/wpanel/get_soft_list_test', 'public.GetConfigValue(\'home\')+\'/api/wpanel/get_soft_list_test', $data);
$data = str_replace('\'https://www.bt.cn/api/wpanel/get_soft_list_test', 'public.GetConfigValue(\'home\')+\'/api/wpanel/get_soft_list_test', $data);
$data = str_replace('\'http://www.bt.cn/api/wpanel/get_soft_list', 'public.GetConfigValue(\'home\')+\'/api/wpanel/get_soft_list', $data);
$data = str_replace('\'https://www.bt.cn/api/wpanel/get_soft_list', 'public.GetConfigValue(\'home\')+\'/api/wpanel/get_soft_list', $data);
$data = str_replace('\'http://www.bt.cn/api/wpanel/notpro', 'public.GetConfigValue(\'home\')+\'/api/wpanel/notpro', $data);
$data = str_replace('\'https://www.bt.cn/api/wpanel/notpro', 'public.GetConfigValue(\'home\')+\'/api/wpanel/notpro', $data);
$data = str_replace('"https://www.bt.cn/api/bt_waf/get_malicious', 'public.GetConfigValue(\'home\')+"/api/bt_waf/get_malicious', $data);
$data = str_replace('\'http://www.bt.cn/api/bt_waf/getSpiders', 'public.GetConfigValue(\'home\')+\'/api/bt_waf/getSpiders', $data);
$data = str_replace('\'https://www.bt.cn/api/bt_waf/getSpiders', 'public.GetConfigValue(\'home\')+\'/api/bt_waf/getSpiders', $data);
$data = str_replace('\'http://www.bt.cn/api/bt_waf/addSpider', 'public.GetConfigValue(\'home\')+\'/api/bt_waf/addSpider', $data);
$data = str_replace('\'https://www.bt.cn/api/bt_waf/addSpider', 'public.GetConfigValue(\'home\')+\'/api/bt_waf/addSpider', $data);
$data = str_replace('\'https://www.bt.cn/api/bt_waf/getVulScanInfoList', 'public.GetConfigValue(\'home\')+\'/api/bt_waf/getVulScanInfoList', $data);
$data = str_replace('\'https://www.bt.cn/api/bt_waf/reportInterceptFail', 'public.GetConfigValue(\'home\')+\'/api/bt_waf/reportInterceptFail', $data);
$data = str_replace('"https://www.bt.cn/api/bt_waf/reportInterceptFail', 'public.GetConfigValue(\'home\')+"/api/bt_waf/reportInterceptFail', $data);
$data = str_replace('\'https://www.bt.cn/api/v2/contact/nps/questions', 'public.GetConfigValue(\'home\')+\'/panel/notpro', $data);
$data = str_replace('\'https://www.bt.cn/api/v2/contact/nps/submit', 'public.GetConfigValue(\'home\')+\'/panel/notpro', $data);
$data = str_replace('\'http://www.bt.cn/api/Auth', 'public.GetConfigValue(\'home\')+\'/api/Auth', $data);
$data = str_replace('\'https://www.bt.cn/api/Auth', 'public.GetConfigValue(\'home\')+\'/api/Auth', $data);
$data = str_replace('\'https://brandnew.aapanel.com/api/panel/getSoftList', 'public.OfficialApiBase()+\'/api/panel/getSoftList', $data);
file_put_contents($main_filepath, $data);
}
//下载插件其他文件
private function download_plugin_other($fname, $filemd5 = null){
$filepath = get_data_dir().'plugins/other/'.$fname;
@mkdir(dirname($filepath), 0777, true);
$result = $this->btapi->get_plugin_other_filename($fname);
if($result && isset($result['status'])){
if($result['status'] == true){
$filename = $result['filename'];
$this->download_file($filename, $filepath);
if(file_exists($filepath)){
if($filemd5 && md5_file($filepath) != $filemd5){
$msg = filesize($filepath) < 300 ? file_get_contents($filepath) : '插件文件MD5校验失败';
@unlink($filepath);
throw new Exception($msg);
}
return true;
}else{
throw new Exception('下载插件文件失败,本地文件不存在');
}
}else{
throw new Exception('下载插件文件失败:'.($result['msg']?$result['msg']:'未知错误'));
}
}else{
throw new Exception('下载插件文件失败,接口返回错误');
}
}
//下载文件
private function download_file($filename, $filepath){
try{
$this->btapi->download($filename, $filepath);
}catch(Exception $e){
@unlink($filepath);
//宝塔bug小文件下载失败改用base64下载
$result = $this->btapi->get_file($filename);
if($result && isset($result['status']) && $result['status']==true){
$filedata = base64_decode($result['data']);
if(strlen($filedata) < 4096 && substr($filedata,0,1)=='{' && substr($filedata,-1,1)=='}'){
$arr = json_decode($filedata, true);
if($arr){
throw new Exception('获取文件失败:'.($arr['msg']?$arr['msg']:'未知错误'));
}
}
if(!$filedata){
throw new Exception('获取文件失败:文件内容为空');
}
file_put_contents($filepath, $filedata);
}elseif($result){
throw new Exception('获取文件失败:'.($result['msg']?$result['msg']:'未知错误'));
}else{
throw new Exception('获取文件失败:未知错误');
}
}
}
//获取一键部署列表
public function get_deplist(){
$result = $this->btapi->get_deplist();
if($result && isset($result['list']) && isset($result['type'])){
if(empty($result['list']) || empty($result['type'])){
throw new Exception('获取一键部署列表失败:一键部署列表为空');
}
return $result;
}else{
throw new Exception('获取一键部署列表失败:'.(isset($result['msg'])?$result['msg']:'面板连接失败'));
}
}
//获取蜘蛛IP列表
public function btwaf_getspiders(){
$result = $this->btapi->btwaf_getspiders();
if(isset($result['status']) && $result['status']){
return $result['data'];
}else{
throw new Exception(isset($result['msg'])?$result['msg']:'获取失败');
}
}
//获取堡塔恶意情报IP库
public function btwaf_getmalicious(){
$result = $this->btapi->btwaf_getmalicious();
if(isset($result['success'])){
return $result;
}elseif(isset($result['msg'])){
throw new Exception($result['msg']);
}else{
throw new Exception(isset($result['res'])?$result['res']:'获取失败');
}
}
}

View File

@ -1,203 +1,266 @@
<?php
namespace app\lib;
use Exception;
class Btapi
{
private $BT_KEY; //接口密钥
private $BT_PANEL; //面板地址
public function __construct($bt_panel, $bt_key){
$this->BT_PANEL = $bt_panel;
$this->BT_KEY = $bt_key;
}
//获取面板配置信息
public function get_config(){
$url = $this->BT_PANEL.'/config?action=get_config';
$p_data = $this->GetKeyData();
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//获取已登录用户信息
public function get_user_info(){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=get_user_info';
$p_data = $this->GetKeyData();
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//从云端获取插件列表
public function get_plugin_list(){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=get_plugin_list';
$p_data = $this->GetKeyData();
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//下载插件包,返回文件路径
public function get_plugin_filename($plugin_name, $version){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=download_plugin';
$p_data = $this->GetKeyData();
$p_data['plugin_name'] = $plugin_name;
$p_data['version'] = $version;
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//下载插件主程序文件,返回文件路径
public function get_plugin_main_filename($plugin_name, $version){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=download_plugin_main';
$p_data = $this->GetKeyData();
$p_data['plugin_name'] = $plugin_name;
$p_data['version'] = $version;
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//解密插件主程序py代码返回文件路径
public function get_decode_plugin_main($plugin_name, $version){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=decode_plugin_main';
$p_data = $this->GetKeyData();
$p_data['plugin_name'] = $plugin_name;
$p_data['version'] = $version;
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//下载插件其他文件,返回文件路径
public function get_plugin_other_filename($fname){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=download_plugin_other';
$p_data = $this->GetKeyData();
$p_data['fname'] = $fname;
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//下载文件
public function download($filename, $localpath){
$url = $this->BT_PANEL.'/download';
$p_data = $this->GetKeyData();
$p_data['filename'] = $filename;
$result = $this->curl_download($url.'?'.http_build_query($p_data), $localpath);
return $result;
}
//获取文件base64
public function get_file($filename){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=get_file';
$p_data = $this->GetKeyData();
$p_data['filename'] = $filename;
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
private function GetKeyData(){
$now_time = time();
$p_data = array(
'request_token' => md5($now_time.''.md5($this->BT_KEY)),
'request_time' => $now_time
);
return $p_data;
}
private function curl($url, $data = null, $timeout = 60)
{
//定义cookie保存位置
$cookie_file=app()->getRuntimePath().md5($this->BT_PANEL).'.cookie';
if(!file_exists($cookie_file)){
touch($cookie_file);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
if($data){
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
private function curl_download($url, $localpath, $timeout = 60)
{
//定义cookie保存位置
$cookie_file=app()->getRuntimePath().md5($this->BT_PANEL).'.cookie';
if(!file_exists($cookie_file)){
touch($cookie_file);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);
$fp = fopen($localpath, 'w+');
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_exec($ch);
if (curl_errno($ch)) {
$message = curl_error($ch);
curl_close($ch);
fclose($fp);
throw new Exception('下载文件失败:'.$message);
}
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpcode>299){
curl_close($ch);
fclose($fp);
throw new Exception('下载文件失败HTTPCODE-'.$httpcode);
}
curl_close($ch);
fclose($fp);
return true;
}
<?php
namespace app\lib;
use Exception;
class Btapi
{
private $BT_KEY; //接口密钥
private $BT_PANEL; //面板地址
public function __construct($bt_panel, $bt_key){
$this->BT_PANEL = $bt_panel;
$this->BT_KEY = $bt_key;
}
//获取面板配置信息
public function get_config(){
$url = $this->BT_PANEL.'/config?action=get_config';
$p_data = $this->GetKeyData();
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
public function get_config_go(){
$url = $this->BT_PANEL.'/panel/get_config';
$p_data = $this->GetKeyData();
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//获取已登录用户信息
public function get_user_info(){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=get_user_info';
$p_data = $this->GetKeyData();
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//从云端获取插件列表
public function get_plugin_list(){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=get_plugin_list';
$p_data = $this->GetKeyData();
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//下载插件包,返回文件路径
public function get_plugin_filename($plugin_name, $version){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=download_plugin';
$p_data = $this->GetKeyData();
$p_data['plugin_name'] = $plugin_name;
$p_data['version'] = $version;
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//下载插件主程序文件,返回文件路径
public function get_plugin_main_filename($plugin_name, $version){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=download_plugin_main';
$p_data = $this->GetKeyData();
$p_data['plugin_name'] = $plugin_name;
$p_data['version'] = $version;
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//解密插件主程序py代码返回文件路径
public function get_decode_plugin_main($plugin_name, $version){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=decode_plugin_main';
$p_data = $this->GetKeyData();
$p_data['plugin_name'] = $plugin_name;
$p_data['version'] = $version;
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//下载插件其他文件,返回文件路径
public function get_plugin_other_filename($fname){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=download_plugin_other';
$p_data = $this->GetKeyData();
$p_data['fname'] = $fname;
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//下载文件
public function download($filename, $localpath){
$url = $this->BT_PANEL.'/download';
$p_data = $this->GetKeyData();
$p_data['filename'] = $filename;
$result = $this->curl_download($url.'?'.http_build_query($p_data), $localpath);
return $result;
}
//获取文件base64
public function get_file($filename){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=get_file';
$p_data = $this->GetKeyData();
$p_data['filename'] = $filename;
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//购买第三方插件
public function create_plugin_other_order($pid){
$url = $this->BT_PANEL.'/auth?action=create_plugin_other_order';
$p_data = $this->GetKeyData();
$p_data['pid'] = $pid;
$p_data['cycle'] = '999';
$p_data['type'] = '0';
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//获取一键部署列表
public function get_deplist(){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=get_deplist';
$p_data = $this->GetKeyData();
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
//BTWAF-获取蜘蛛列表
public function btwaf_getspiders(){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=btwaf_getspiders';
$p_data = $this->GetKeyData();
$result = $this->curl($url,$p_data);
$result = str_replace("\u0000", '', $result);
$data = json_decode($result,true);
return $data;
}
//BTWAF-获取堡塔恶意情报IP库
public function btwaf_getmalicious(){
$url = $this->BT_PANEL.'/plugin?action=a&name=kaixin&s=btwaf_getmalicious';
$p_data = $this->GetKeyData();
$result = $this->curl($url,$p_data);
$data = json_decode($result,true);
return $data;
}
private function GetKeyData(){
$now_time = time();
$p_data = array(
'request_token' => md5($now_time.''.md5($this->BT_KEY)),
'request_time' => $now_time
);
return $p_data;
}
private function curl($url, $data = null, $timeout = 60)
{
//定义cookie保存位置
$cookie_file=app()->getRuntimePath().md5($this->BT_PANEL).'.cookie';
if(!file_exists($cookie_file)){
touch($cookie_file);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
if($data){
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
}
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
private function curl_download($url, $localpath, $timeout = 300)
{
//定义cookie保存位置
$cookie_file=app()->getRuntimePath().md5($this->BT_PANEL).'.cookie';
if(!file_exists($cookie_file)){
touch($cookie_file);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);
$fp = fopen($localpath, 'w+');
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_exec($ch);
if (curl_errno($ch)) {
$message = curl_error($ch);
curl_close($ch);
fclose($fp);
throw new Exception('下载文件失败:'.$message);
}
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpcode>299){
curl_close($ch);
fclose($fp);
throw new Exception('下载文件失败HTTPCODE-'.$httpcode);
}
curl_close($ch);
fclose($fp);
return true;
}
}

View File

@ -1,229 +1,218 @@
<?php
namespace app\lib;
use Exception;
use ZipArchive;
class Plugins
{
private static function get_btapi(){
$bt_url = config_get('bt_url');
$bt_key = config_get('bt_key');
if(!$bt_url || !$bt_key) throw new Exception('请先配置好宝塔面板接口信息');
$btapi = new Btapi($bt_url, $bt_key);
return $btapi;
}
//刷新插件列表
public static function refresh_plugin_list(){
$btapi = self::get_btapi();
$result = $btapi->get_plugin_list();
if($result && isset($result['list']) && isset($result['type'])){
if(empty($result['list']) || empty($result['type'])){
throw new Exception('获取插件列表失败:插件列表为空');
}
self::save_plugin_list($result);
}else{
throw new Exception('获取插件列表失败:'.($result['msg']?$result['msg']:'面板连接失败'));
}
}
//保存插件列表
private static function save_plugin_list($data){
$data['ip'] = '127.0.0.1';
$data['serverid'] = '';
$data['beta'] = 0;
$data['uid'] = 1;
$data['skey'] = '';
$list = [];
foreach($data['list'] as $plugin){
if(isset($plugin['endtime'])) $plugin['endtime'] = 0;
$list[] = $plugin;
}
$data['list'] = $list;
if($data['pro']>-1) $data['pro'] = 0;
if($data['ltd']>-1) $data['ltd'] = strtotime('+1 year');
$json_file = get_data_dir().'config/plugin_list.json';
if(!file_put_contents($json_file, json_encode($data))){
throw new Exception('保存插件列表失败,文件无写入权限');
}
}
//获取插件列表
public static function get_plugin_list(){
$json_file = get_data_dir().'config/plugin_list.json';
if(file_exists($json_file)){
$data = file_get_contents($json_file);
$json_arr = json_decode($data, true);
if($json_arr){
return $json_arr;
}
}
return false;
}
//获取一个插件信息
public static function get_plugin_info($name){
$json_arr = self::get_plugin_list();
if(!$json_arr) return null;
foreach($json_arr['list'] as $plugin){
if($plugin['name'] == $name){
return $plugin;
}
}
return null;
}
//下载插件(自动判断是否第三方)
public static function download_plugin($plugin_name, $version){
$plugin_info = Plugins::get_plugin_info($plugin_name);
if(!$plugin_info) throw new Exception('未找到该插件信息');
if($plugin_info['type'] == 10 && isset($plugin_info['versions'][0]['download'])){
$fname = $plugin_info['versions'][0]['download'];
$filemd5 = $plugin_info['versions'][0]['md5'];
Plugins::download_plugin_other($fname, $filemd5);
if(isset($plugin_info['min_image']) && strpos($plugin_info['min_image'], 'fname=')){
$fname = substr($plugin_info['min_image'], strpos($plugin_info['min_image'], '?fname=')+7);
Plugins::download_plugin_other($fname);
}
}else{
Plugins::download_plugin_package($plugin_name, $version);
}
}
//下载插件包
public static function download_plugin_package($plugin_name, $version){
$filepath = get_data_dir().'plugins/package/'.$plugin_name.'-'.$version.'.zip';
$btapi = self::get_btapi();
$result = $btapi->get_plugin_filename($plugin_name, $version);
if($result && isset($result['status'])){
if($result['status'] == true){
$filename = $result['filename'];
self::download_file($btapi, $filename, $filepath);
if(file_exists($filepath)){
$zip = new ZipArchive;
if ($zip->open($filepath) === true)
{
$zip->extractTo(get_data_dir().'plugins/folder/'.$plugin_name.'-'.$version);
$zip->close();
$main_filepath = get_data_dir().'plugins/folder/'.$plugin_name.'-'.$version.'/'.$plugin_name.'/'.$plugin_name.'_main.py';
if(file_exists($main_filepath) && filesize($main_filepath)>10){
if(!strpos(file_get_contents($main_filepath), 'import ')){ //加密py文件需要解密
self::decode_plugin_main($plugin_name, $version, $main_filepath);
$zip->open($filepath, ZipArchive::CREATE);
$zip->addFile($main_filepath, $plugin_name.'/'.$plugin_name.'_main.py');
$zip->close();
}
}
}else{
throw new Exception('插件包解压缩失败');
}
return true;
}else{
throw new Exception('下载插件包失败,本地文件不存在');
}
}else{
throw new Exception('下载插件包失败:'.($result['msg']?$result['msg']:'未知错误'));
}
}else{
throw new Exception('下载插件包失败,接口返回错误');
}
}
//下载插件主程序文件
public static function download_plugin_main($plugin_name, $version){
$filepath = get_data_dir().'plugins/main/'.$plugin_name.'-'.$version.'.dat';
$btapi = self::get_btapi();
$result = $btapi->get_plugin_main_filename($plugin_name, $version);
if($result && isset($result['status'])){
if($result['status'] == true){
$filename = $result['filename'];
self::download_file($btapi, $filename, $filepath);
if(file_exists($filepath)){
return true;
}else{
throw new Exception('下载插件主程序文件失败,本地文件不存在');
}
}else{
throw new Exception('下载插件主程序文件失败:'.($result['msg']?$result['msg']:'未知错误'));
}
}else{
throw new Exception('下载插件主程序文件失败,接口返回错误');
}
}
//解密并下载插件主程序文件
public static function decode_plugin_main($plugin_name, $version, $main_filepath){
$btapi = self::get_btapi();
$result = $btapi->get_decode_plugin_main($plugin_name, $version);
if($result && isset($result['status'])){
if($result['status'] == true){
$filename = $result['filename'];
self::download_file($btapi, $filename, $main_filepath);
return true;
}else{
throw new Exception('解密插件主程序文件失败:'.($result['msg']?$result['msg']:'未知错误'));
}
}else{
throw new Exception('解密插件主程序文件失败,接口返回错误');
}
}
//下载插件其他文件
public static function download_plugin_other($fname, $filemd5 = null){
$filepath = get_data_dir().'plugins/other/'.$fname;
@mkdir(dirname($filepath), 0777, true);
$btapi = self::get_btapi();
$result = $btapi->get_plugin_other_filename($fname);
if($result && isset($result['status'])){
if($result['status'] == true){
$filename = $result['filename'];
self::download_file($btapi, $filename, $filepath);
if(file_exists($filepath)){
if($filemd5 && md5_file($filepath) != $filemd5){
unlink($filepath);
throw new Exception('插件文件MD5校验失败');
}
return true;
}else{
throw new Exception('下载插件文件失败,本地文件不存在');
}
}else{
throw new Exception('下载插件文件失败:'.($result['msg']?$result['msg']:'未知错误'));
}
}else{
throw new Exception('下载插件文件失败,接口返回错误');
}
}
//下载文件
private static function download_file($btapi, $filename, $filepath){
try{
$btapi->download($filename, $filepath);
}catch(Exception $e){
unlink($filepath);
//宝塔bug小文件下载失败改用base64下载
$result = $btapi->get_file($filename);
if($result && isset($result['status']) && $result['status']==true){
$filedata = base64_decode($result['data']);
if(strlen($filedata) < 4096 && substr($filedata,0,1)=='{' && substr($filedata,-1,1)=='}'){
$arr = json_decode($filedata, true);
if($arr){
throw new Exception('获取文件失败:'.($arr['msg']?$arr['msg']:'未知错误'));
}
}
if(!$filedata){
throw new Exception('获取文件失败:文件内容为空');
}
file_put_contents($filepath, $filedata);
}elseif($result){
throw new Exception('获取文件失败:'.($result['msg']?$result['msg']:'未知错误'));
}else{
throw new Exception('获取文件失败:未知错误');
}
}
}
<?php
namespace app\lib;
use Exception;
use ZipArchive;
class Plugins
{
private static function get_btapi($os){
if(self::is_third($os)){
return new ThirdPlugins($os);
}else{
return new BtPlugins($os);
}
}
private static function is_third($os){
if($os == 'en'){
$type = config_get('enbt_type');
}elseif($os == 'Windows'){
$type = config_get('wbt_type');
}else{
$type = config_get('bt_type');
}
return $type == 1;
}
//刷新插件列表
public static function refresh_plugin_list($os = 'Linux'){
$btapi = self::get_btapi($os);
$result = $btapi->get_plugin_list();
self::save_plugin_list($result, $os);
}
//保存插件列表
private static function save_plugin_list($data, $os){
$data['ip'] = '127.0.0.1';
if($os == 'en'){
$data['serverId'] = '';
$data['aln'] = self::get_aln();
$data['pro'] = 0;
$data['pro_authorization_sn'] = '0';
if(!empty($data['authorization_map'])){
foreach($data['authorization_map'] as $code => &$plugin){
if($code != '0' && isset($plugin['end_time'])) $plugin['end_time'] = 0;
}
}
if(isset($data['expansions']['mail'])){
$data['expansions']['mail']['total'] = 2000000;
$data['expansions']['mail']['available'] = 2000000;
}
}else{
$data['serverid'] = '';
$data['aln'] = self::get_aln();
$data['beta'] = 0;
$data['uid'] = 1;
$data['skey'] = '';
$data['pro'] = -1;
$data['ltd'] = strtotime('+10 year');
}
foreach($data['list'] as &$plugin){
if(isset($plugin['endtime'])) $plugin['endtime'] = 0;
}
$json_file = get_data_dir($os).'config/plugin_list.json';
if(!file_put_contents($json_file, json_encode($data))){
throw new Exception('保存插件列表失败,文件无写入权限');
}
}
//多账号数量
private static function get_aln($count = '9999'){
$key = 'FB8upo8XMgP5by54';
$iv = 'lOrrq3lNEURZNdK7';
return openssl_encrypt($count, 'aes-128-cbc', $key, 0, $iv);
}
//获取插件列表
public static function get_plugin_list($os = 'Linux'){
$json_file = get_data_dir($os).'config/plugin_list.json';
if(file_exists($json_file)){
$data = file_get_contents($json_file);
$json_arr = json_decode($data, true);
if($json_arr){
return $json_arr;
}
}
return false;
}
//获取一个插件信息
public static function get_plugin_info($name, $os = 'Linux'){
$json_arr = self::get_plugin_list($os);
if(!$json_arr) return null;
foreach($json_arr['list'] as $plugin){
if($plugin['name'] == $name){
return $plugin;
}
}
return null;
}
//下载插件(自动判断是否第三方)
public static function download_plugin($plugin_name, $version, $os = 'Linux'){
$plugin_info = Plugins::get_plugin_info($plugin_name, $os);
if(!$plugin_info) throw new Exception('未找到该插件信息');
$btapi = self::get_btapi($os);
$btapi->download_plugin($plugin_name, $version, $plugin_info);
}
//下载插件主程序文件
public static function download_plugin_main($plugin_name, $version, $os = 'Linux'){
$btapi = self::get_btapi($os);
$btapi->download_plugin_main($plugin_name, $version);
}
//本地解密插件主程序文件
public static function decode_plugin_main_local($main_filepath, $os = 'Linux'){
$btapi = new BtPlugins($os);
return $btapi->decode_plugin_main_local($main_filepath);
}
//本地解密模块文件
public static function decode_module_file($filepath){
$src = file_get_contents($filepath);
if($src===false)throw new Exception('文件打开失败');
if(!$src || strpos($src, 'import ')!==false)return 0;
$key = 'Z2B87NEAS2BkxTrh';
$iv = 'WwadH66EGWpeeTT6';
$data_arr = explode("\n", $src);
$de_text = '';
foreach($data_arr as $data){
$data = trim($data);
if(!empty($data)){
$tmp = openssl_decrypt($data, 'aes-128-cbc', $key, 0, $iv);
if($tmp !== false) $de_text .= $tmp;
}
}
if(!empty($de_text) && strpos($de_text, 'import ')!==false){
file_put_contents($filepath, $de_text);
return 1;
}
return 2;
}
//刷新一键部署列表
public static function refresh_deplist($os = 'Linux'){
$btapi = self::get_btapi($os);
$result = $btapi->get_deplist();
$json_file = get_data_dir($os).'config/deployment_list.json';
if(!file_put_contents($json_file, json_encode($result))){
throw new Exception('保存一键部署列表失败,文件无写入权限');
}
}
//获取一键部署列表
public static function get_deplist($os = 'Linux'){
$json_file = get_data_dir($os).'config/deployment_list.json';
if(file_exists($json_file)){
$data = file_get_contents($json_file);
$json_arr = json_decode($data, true);
if($json_arr){
return $json_arr;
}
}
return false;
}
//获取蜘蛛IP列表
public static function btwaf_getspiders(){
$result = cache('btwaf_getspiders');
if($result){
return $result;
}
$btapi = self::get_btapi('Linux');
$result = $btapi->btwaf_getspiders();
cache('btwaf_getspiders', $result, 3600 * 24 * 3);
return $result;
}
//分类获取蜘蛛IP列表
public static function get_spider($type){
$result = cache('get_spider_'.$type);
if($result){
return $result;
}
$url = 'https://www.bt.cn/api/panel/get_spider?spider='.$type;
$data = get_curl($url);
$result = json_decode($data, true);
if(!$result) return [];
cache('get_spider_'.$type, $result, 3600 * 24);
return $result;
}
//获取堡塔恶意情报IP库
public static function btwaf_getmalicious(){
$btapi = self::get_btapi('Linux');
$result = $btapi->btwaf_getmalicious();
return $result;
}
//加密插件列表
public static function encrypt_plugin_list($list, $server_id, $uid){
$data = json_encode($list);
$block_size = 51200;
$key = md5(substr($server_id, 10, 16) . $uid . $server_id);
$iv = md5($key . $server_id);
$key = substr($key, 8, 16);
$iv = substr($iv, 8, 16);
$encrypted_content = '';
foreach (str_split($data, $block_size) as $block) {
$encrypted_content .= openssl_encrypt($block, 'aes-128-cbc', $key, 0, $iv) . "\n";
}
return $encrypted_content;
}
}

226
app/lib/ThirdPlugins.php Normal file
View File

@ -0,0 +1,226 @@
<?php
namespace app\lib;
use Exception;
use ZipArchive;
class ThirdPlugins
{
private $url;
private $os;
public function __construct($os)
{
$this->os = $os;
if($os == 'en'){
$url = config_get('enbt_surl');
}elseif($os == 'Windows'){
$url = config_get('wbt_surl');
}else{
$url = config_get('bt_surl');
}
if(!$url) throw new Exception('请先配置好第三方云端首页URL');
$this->url = $url;
}
//获取插件列表
public function get_plugin_list()
{
if($this->os == 'en'){
$url = $this->url . 'api/panel/getSoftListEn';
}elseif($this->os == 'Windows'){
$url = $this->url . 'api/wpanel/get_soft_list';
}else{
$url = $this->url . 'api/panel/get_soft_list';
}
$res = $this->curl($url);
$result = json_decode($res, true);
if($result && isset($result['list']) && isset($result['type'])){
if(empty($result['list']) || empty($result['type'])){
throw new Exception('获取插件列表失败:插件列表为空');
}
return $result;
}else{
throw new Exception('获取插件列表失败:'.(isset($result['msg'])?$result['msg']:'第三方云端连接失败'));
}
}
//下载插件(自动判断是否第三方)
public function download_plugin($plugin_name, $version, $plugin_info){
if($plugin_info['type'] == 10 && isset($plugin_info['versions'][0]['download'])){
$fname = $plugin_info['versions'][0]['download'];
$filemd5 = $plugin_info['versions'][0]['md5'];
$this->download_plugin_other($fname, $filemd5);
if(isset($plugin_info['min_image']) && strpos($plugin_info['min_image'], 'fname=')){
$fname = substr($plugin_info['min_image'], strpos($plugin_info['min_image'], '?fname=')+7);
$this->download_plugin_other($fname);
}
}else{
$this->download_plugin_package($plugin_name, $version);
}
}
//下载插件包
private function download_plugin_package($plugin_name, $version){
$filepath = get_data_dir($this->os).'plugins/package/'.$plugin_name.'-'.$version.'.zip';
$url = $this->url . 'down/download_plugin';
$post = ['name'=>$plugin_name, 'version'=>$version, 'os'=>$this->os];
$this->curl_download($url, $post, $filepath);
if(file_exists($filepath)){
$handle = fopen($filepath, "rb");
$file_head = fread($handle, 4);
fclose($handle);
if(bin2hex($file_head) === '504b0304'){
$zip = new ZipArchive;
if ($zip->open($filepath) === true)
{
$zip->close();
return true;
}else{
@unlink($filepath);
throw new Exception('插件包解压缩失败');
}
}else{
$handle = fopen($filepath, "rb");
$errmsg = htmlspecialchars(fgets($handle));
fclose($handle);
@unlink($filepath);
throw new Exception('下载插件包失败:'.($errmsg?$errmsg:'未知错误'));
}
}else{
throw new Exception('下载插件包失败,本地文件不存在');
}
}
//下载插件主程序文件
public function download_plugin_main($plugin_name, $version){
$filepath = get_data_dir($this->os).'plugins/main/'.$plugin_name.'-'.$version.'.dat';
$url = $this->url . 'down/download_plugin_main';
$post = ['name'=>$plugin_name, 'version'=>$version, 'os'=>$this->os];
$this->curl_download($url, $post, $filepath);
if(file_exists($filepath)){
$line = count(file($filepath));
if($line > 3) return true;
$handle = fopen($filepath, "rb");
$errmsg = htmlspecialchars(fgets($handle));
fclose($handle);
@unlink($filepath);
throw new Exception('下载插件主程序文件失败:'.($errmsg?$errmsg:'未知错误'));
}else{
throw new Exception('下载插件主程序文件失败,本地文件不存在');
}
}
//下载插件其他文件
private function download_plugin_other($fname, $filemd5 = null){
$filepath = get_data_dir().'plugins/other/'.$fname;
@mkdir(dirname($filepath), 0777, true);
$url = $this->url . 'api/Pluginother/get_file?fname='.urlencode($fname);
$this->curl_download($url, false, $filepath);
if(file_exists($filepath)){
$handle = fopen($filepath, "rb");
$file_head = fread($handle, 15);
fclose($handle);
if($file_head === '{"status":false'){
$res = file_get_contents($filepath);
$result = json_decode($res, true);
@unlink($filepath);
throw new Exception('下载插件文件失败:'.($result?$result['msg']:'未知错误'));
}
if($filemd5 && md5_file($filepath) != $filemd5){
$msg = filesize($filepath) < 300 ? file_get_contents($filepath) : '插件文件MD5校验失败';
@unlink($filepath);
throw new Exception($msg);
}
return true;
}else{
throw new Exception('下载插件文件失败,本地文件不存在');
}
}
//获取一键部署列表
public function get_deplist(){
$url = $this->url . 'api/panel/get_deplist';
$post = ['os' => $this->os];
$res = $this->curl($url, http_build_query($post));
$result = json_decode($res, true);
if($result && isset($result['list']) && isset($result['type'])){
if(empty($result['list']) || empty($result['type'])){
throw new Exception('获取一键部署列表失败:一键部署列表为空');
}
return $result;
}else{
throw new Exception('获取一键部署列表失败:'.(isset($result['msg'])?$result['msg']:'第三方云端连接失败'));
}
}
//获取蜘蛛IP列表
public function btwaf_getspiders(){
$url = $this->url . 'api/bt_waf/getSpiders';
$res = $this->curl($url);
$result = json_decode($res, true);
if(isset($result['status']) && !$result['status']){
throw new Exception(isset($result['msg'])?$result['msg']:'获取失败');
}else{
return $result;
}
}
//获取堡塔恶意情报IP库
public function btwaf_getmalicious(){
$url = $this->url . 'api/bt_waf/get_malicious';
$res = $this->curl($url);
$result = json_decode($res, true);
if(isset($result['success'])){
return $result;
}else{
throw new Exception(isset($result['res'])?$result['res']:'获取失败');
}
}
private function curl($url, $post = 0){
$ua = "Mozilla/5.0 (BtCloud; ".request()->root(true).")";
return get_curl($url, $post, 0, 0, 0, $ua);
}
private function curl_download($url, $post, $localpath, $timeout = 300)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
$fp = fopen($localpath, 'w+');
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (BtCloud; ".request()->root(true).")");
if($post){
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
}
curl_exec($ch);
if (curl_errno($ch)) {
$message = curl_error($ch);
curl_close($ch);
fclose($fp);
throw new Exception('下载文件失败:'.$message);
}
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpcode>299){
curl_close($ch);
fclose($fp);
throw new Exception('下载文件失败HTTPCODE-'.$httpcode);
}
curl_close($ch);
fclose($fp);
return true;
}
}

1
app/lib/cn.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,24 +1,26 @@
<?php
declare (strict_types=1);
namespace app\middleware;
class AuthAdmin
{
public function handle($request, \Closure $next)
{
$islogin = false;
$cookie = cookie('admin_token');
if($cookie){
$token=authcode($cookie, 'DECODE', config_get('syskey'));
list($user, $sid, $expiretime) = explode("\t", $token);
$session=md5(config_get('admin_username').config_get('admin_password'));
if($session==$sid && $expiretime>time()) {
$islogin = true;
}
}
request()->islogin = $islogin;
return $next($request);
}
}
<?php
declare (strict_types=1);
namespace app\middleware;
class AuthAdmin
{
public function handle($request, \Closure $next)
{
$islogin = false;
$cookie = cookie('admin_token');
if($cookie){
$token=authcode($cookie, 'DECODE', config_get('syskey'));
if($token){
list($user, $sid, $expiretime) = explode("\t", $token);
$session=md5(config_get('admin_username').config_get('admin_password'));
if($session==$sid && $expiretime>time()) {
$islogin = true;
}
}
}
request()->islogin = $islogin;
return $next($request);
}
}

View File

@ -1,19 +1,19 @@
<?php
declare (strict_types=1);
namespace app\middleware;
class CheckAdmin
{
public function handle($request, \Closure $next)
{
if (!request()->islogin) {
if ($request->isAjax() || !$request->isGet()) {
return json(['code'=>-1, 'msg'=>'未登录'])->code(401);
}
return redirect((string)url('/admin/login'));
}
return $next($request);
}
}
<?php
declare (strict_types=1);
namespace app\middleware;
class CheckAdmin
{
public function handle($request, \Closure $next)
{
if (!request()->islogin) {
if ($request->isAjax() || !$request->isGet()) {
return json(['code'=>-1, 'msg'=>'未登录'])->code(401);
}
return redirect((string)url('/admin/login'));
}
return $next($request);
}
}

View File

@ -5,6 +5,7 @@ namespace app\middleware;
use think\facade\Db;
use think\facade\Config;
use think\facade\View;
class LoadConfig
{
@ -17,10 +18,21 @@ class LoadConfig
*/
public function handle($request, \Closure $next)
{
if (!file_exists(app()->getRootPath().'.env')){
if(strpos(request()->url(),'/installapp')===false){
return redirect((string)url('/installapp'))->header([
'Cache-Control' => 'no-store, no-cache, must-revalidate',
'Pragma' => 'no-cache',
]);
}else{
return $next($request);
}
}
$res = Db::name('config')->cache('configs',0)->column('value','key');
Config::set($res, 'sys');
View::assign('cdnpublic', 'https://s4.zstatic.net/ajax/libs/');
return $next($request)->header([
'Cache-Control' => 'no-store, no-cache, must-revalidate',
'Pragma' => 'no-cache',

View File

@ -1,24 +1,24 @@
<?php
declare (strict_types=1);
namespace app\middleware;
use think\facade\View;
class RefererCheck
{
/**
* 处理请求
*
* @param \think\Request $request
* @param \Closure $next
* @return Response
*/
public function handle($request, \Closure $next)
{
if(!checkRefererHost()){
return response('Access Denied', 403);
}
return $next($request);
}
}
<?php
declare (strict_types=1);
namespace app\middleware;
use think\facade\View;
class RefererCheck
{
/**
* 处理请求
*
* @param \think\Request $request
* @param \Closure $next
* @return Response
*/
public function handle($request, \Closure $next)
{
if(!checkRefererHost()){
return response('Access Denied', 403);
}
return $next($request);
}
}

31
app/script/cacert.sh Normal file
View File

@ -0,0 +1,31 @@
#!/bin/bash
OPENSSL_CHECK=$(which openssl)
if [ "$?" != "0" ]; then
echo "未安装OpenSSL"
exit 1
fi
if [ ! -f ca.key ] && [ ! -f ca.crt ]; then
openssl genrsa -out ca.key 2048
openssl req -new -x509 -utf8 -days 3650 -extensions v3_ca -subj "/C=CN/O=宝塔面板/CN=宝塔面板" -key ca.key -out ca.crt
fi
openssl genrsa -out server.key 2048
openssl req -new -nodes -key server.key -subj "/C=CN/O=BTPanel/CN=BTPanel" -out server.csr
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650 -extensions req_ext
cat ca.crt >> server.crt
openssl pkcs12 -export -out baota_root.pfx -inkey server.key -in server.crt -password pass:
if [ "$?" != "0" ]; then
echo "生成CA根证书失败"
exit 1
fi
mkdir -p ../../public/ssl
\cp baota_root.pfx ../../public/ssl/baota_root.pfx
\cp ca.crt ../../public/ssl/baota_root.crt
rm -f server.crt server.key server.csr
echo "生成CA根证书成功"

88
app/script/convert.sh Normal file
View File

@ -0,0 +1,88 @@
#!/bin/bash
Linux_Version="11.5.0"
Windows_Version="8.2.2"
Aapanel_Version="7.0.25"
Btm_Version="2.3.3"
FILES=(
public/install/src/panel6.zip
public/install/update/LinuxPanel-${Linux_Version}.zip
public/install/install_panel.sh
public/install/update_panel.sh
public/install/update6.sh
public/win/install/panel_update.py
public/win/panel/data/setup.py
public/install/src/bt-monitor-${Btm_Version}.zip
public/install/install_btmonitor.sh
public/install/update_btmonitor.sh
public/install/src/panel_7_en.zip
public/install/update/LinuxPanel_EN-${Aapanel_Version}.zip
public/install/install_7.0_en.sh
public/install/update_7.x_en.sh
)
PL_FILE="public/install/update/LinuxPanel-${Linux_Version}.pl"
DIR=$1
SITEURL=$2
if [ ! -d "$DIR" ]; then
echo "网站目录不存在"
exit 1
fi
if [ "$SITEURL" = "" ]; then
echo "网站URL不正确"
exit 1
fi
function handleFile()
{
Filename=$1
if [ "${Filename##*.}" = "zip" ]; then
handleZipFile $Filename
else
handleTextFile $Filename
fi
}
function handleZipFile()
{
Filename=$1
mkdir -p /tmp/package
unzip -o -q $Filename -d /tmp/package
grep -rl --include=\*.py --include=\*.sh --include=index.js 'http://www.example.com' /tmp/package | xargs -I @ sed -i "s|http://www.example.com|${SITEURL}|g" @
Sprit_SITEURK=${SITEURL//\//\\\\\/}
grep -rl --include=\*.sh 'http:\\\/\\\/www.example.com' /tmp/package | xargs -I @ sed -i "s|http:\\\/\\\/www.example.com|${Sprit_SITEURK}|g" @
rm -f $Filename
cd /tmp/package && zip -9 -q -r $Filename * && cd -
rm -rf /tmp/package
}
function handleTextFile()
{
sed -i "s|http://www.example.com|${SITEURL}|g" $1
}
echo "=========================="
echo "正在处理中..."
echo "=========================="
for File in ${FILES[@]}
do
Filename="${DIR}${File}"
if [ -f "$Filename" ]; then
handleFile $Filename
echo -e "成功处理文件:\033[32m${Filename}\033[0m"
else
echo -e "文件不存在:\033[33m${Filename}\033[0m"
fi
done
HASH=$(sha256sum "${DIR}public/install/update/LinuxPanel-${Linux_Version}.zip" | awk '{print $1}')
TIMESTAMP=$(date +%s)
printf '{"hash": "%s", "update_time": "%s"}' "$HASH" "$TIMESTAMP" > "${DIR}${PL_FILE}"
echo "=========================="
echo "处理完成"
echo "=========================="

View File

@ -0,0 +1,49 @@
{extend name="admin/layout" /}
{block name="title"}一键部署列表{/block}
{block name="main"}
<div class="container" style="padding-top:70px;">
<div class="col-sm-12 col-md-10 col-lg-8 center-block" style="float: none;">
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">一键部署列表</h3></div>
<div class="panel-body">
<div class="list-group">
<div class="list-group-item list-group-item-warning">Linux面板</div>
<div class="list-group-item" style="line-height:35px">列表文件更新时间:<font color="blue">{$deplist_linux_time}</font><a href="javascript:refresh_deplist('Linux')" class="btn btn-success pull-right"><i class="fa fa-refresh"></i>重新获取</a></div>
</div>
<div class="list-group">
<div class="list-group-item list-group-item-warning">Windows面板</div>
<div class="list-group-item" style="line-height:35px">列表文件更新时间:<font color="blue">{$deplist_win_time}</font><a href="javascript:refresh_deplist('Windows')" class="btn btn-success pull-right"><i class="fa fa-refresh"></i>重新获取</a></div>
</div>
</div>
</div>
<script src="{$cdnpublic}layer/3.5.1/layer.js"></script>
<script>
function refresh_deplist(os){
var confirm = layer.confirm('是否确定从宝塔官方获取最新一键部署列表?', {
btn: ['确定','取消']
}, function(){
layer.close(confirm)
var ii = layer.msg('正在获取一键部署列表,请稍候...', {icon: 16, shade:0.1, time: 0});
$.ajax({
type : 'GET',
url : '/admin/refresh_deplist?os='+os,
dataType : 'json',
success : function(data) {
layer.close(ii)
if(data.code == 0){
layer.alert(data.msg, {icon:1}, function(){window.location.reload()});
}else{
layer.alert(data.msg, {icon:2});
}
},
error:function(data){
layer.close(ii)
layer.msg('服务器错误', {icon:2});
}
});
}, function(){
layer.close(confirm)
});
}
</script>
{/block}

View File

@ -1,59 +1,60 @@
{extend name="admin/layout" /}
{block name="title"}宝塔第三方云端管理中心{/block}
{block name="main"}
<style>
.query-title {
background-color:#f5fafe;
word-break: keep-all;
}
.query-result{
word-break: break-all;
}
</style>
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 col-sm-10 col-md-8 center-block" style="float: none;">
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">后台管理首页</h3></div>
<div class="list-group">
<div class="list-group-item"><span class="glyphicon glyphicon-stats"></span> <b>宝塔插件统计:</b>共有 {$stat.total} 个,其中免费插件 {$stat.free} 个,专业版插件 {$stat.pro} 个,企业版插件 {$stat.ltd} 个,第三方插件 {$stat.third} 个</div>
<div class="list-group-item"><span class="glyphicon glyphicon-tint"></span> <b>使用记录统计:</b>历史总共数量:{$stat.record_total},正在使用数量:{$stat.record_isuse}</div>
<div class="list-group-item"><span class="glyphicon glyphicon-time"></span> <b>任务运行情况:</b>上次运行时间:{$stat.runtime|raw}&nbsp;&nbsp;<a href="/admin/set/mod/task" class="btn btn-xs btn-info">查看详情</a></div>
<div class="list-group-item"><span class="glyphicon glyphicon-cog"></span> <b>常用功能入口:</b><a href="/admin/plugins" class="btn btn-xs btn-default">插件列表</a>&nbsp;<a href="/admin/record" class="btn btn-xs btn-default">使用记录</a>&nbsp;<a href="/admin/black" class="btn btn-xs btn-default">黑白名单</a>&nbsp;<a href="/download" class="btn btn-xs btn-default" target="_blank">安装脚本</a></div>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">服务器信息</h3>
</div>
<table class="table table-bordered">
<tbody>
<tr>
<td class="query-title">框架版本</td>
<td class="query-result">{$info.framework_version}</td>
</tr>
<tr>
<td class="query-title">PHP版本</td>
<td class="query-result">{$info.php_version}</td>
</tr>
<tr>
<td class="query-title">MySQL版本</td>
<td class="query-result">{$info.mysql_version}</td>
</tr>
<tr>
<td class="query-title">WEB软件</td>
<td class="query-result">{$info.software}</td>
</tr>
<tr>
<td class="query-title">操作系统</td>
<td class="query-result">{$info.os}</td>
</tr>
<tr>
<td class="query-title">服务器时间</td>
<td class="query-result">{$info.date}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
{extend name="admin/layout" /}
{block name="title"}宝塔第三方云端管理中心{/block}
{block name="main"}
<style>
.table>tbody>tr>td{white-space: normal;}
.query-title {
background-color:#f5fafe;
word-break: keep-all;
}
.query-result{
word-break: break-all;
}
</style>
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 col-sm-10 col-md-8 center-block" style="float: none;">
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">后台管理首页</h3></div>
<div class="list-group">
<div class="list-group-item"><span class="glyphicon glyphicon-stats"></span> <b>宝塔插件统计:</b>共有 {$stat.total} 个,其中免费插件 {$stat.free} 个,专业版插件 {$stat.pro} 个,企业版插件 {$stat.ltd} 个,第三方插件 {$stat.third} 个</div>
<div class="list-group-item"><span class="glyphicon glyphicon-tint"></span> <b>使用记录统计:</b>历史总共数量:{$stat.record_total},正在使用数量:{$stat.record_isuse}</div>
<div class="list-group-item"><span class="glyphicon glyphicon-time"></span> <b>任务运行情况:</b>上次运行时间:{$stat.runtime|raw}&nbsp;&nbsp;<a href="/admin/set/mod/task" class="btn btn-xs btn-info">查看详情</a></div>
<div class="list-group-item"><span class="glyphicon glyphicon-cog"></span> <b>常用功能入口:</b><a href="/admin/plugins" class="btn btn-xs btn-default">插件列表</a>&nbsp;<a href="/admin/record" class="btn btn-xs btn-default">使用记录</a>&nbsp;<a href="/admin/list" class="btn btn-xs btn-default">黑白名单</a>&nbsp;<a href="/download" class="btn btn-xs btn-default" target="_blank">安装脚本</a></div>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">服务器信息</h3>
</div>
<table class="table table-bordered">
<tbody>
<tr>
<td class="query-title">框架版本</td>
<td class="query-result">{$info.framework_version}</td>
</tr>
<tr>
<td class="query-title">PHP版本</td>
<td class="query-result">{$info.php_version}</td>
</tr>
<tr>
<td class="query-title">MySQL版本</td>
<td class="query-result">{$info.mysql_version}</td>
</tr>
<tr>
<td class="query-title">WEB软件</td>
<td class="query-result">{$info.software}</td>
</tr>
<tr>
<td class="query-title">操作系统</td>
<td class="query-result">{$info.os}</td>
</tr>
<tr>
<td class="query-title">服务器时间</td>
<td class="query-result">{$info.date}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
{/block}

View File

@ -1,67 +1,78 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8" />
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{block name="title"}标题{/block}</title>
<link href="//cdn.staticfile.org/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet" />
<link href="//cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<link href="/static/css/bootstrap-table.css" rel="stylesheet" />
<script src="//cdn.staticfile.org/modernizr/2.8.3/modernizr.min.js"></script>
<script src="//cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
<script src="//cdn.staticfile.org/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<!--[if lt IE 9]>
<script src="//cdn.staticfile.org/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="//cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<nav class="navbar navbar-fixed-top navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
aria-expanded="false" aria-controls="navbar">
<span class="sr-only">导航按钮</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="./">宝塔第三方云端管理中心</a>
</div><!-- /.navbar-header -->
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li class="{:checkIfActive('index')}">
<a href="/admin"><i class="fa fa-home"></i> 后台首页</a>
</li>
<li class="{:checkIfActive('plugins')}">
<a href="/admin/plugins"><i class="fa fa-cubes"></i> 插件列表</a>
</li>
<li class="{:checkIfActive('record')}">
<a href="/admin/record"><i class="fa fa-list"></i> 使用记录</a>
</li>
<li class="{:checkIfActive('list')}">
<a href="/admin/list"><i class="fa fa-globe"></i> 黑白名单</a>
</li>
<li class="{:checkIfActive('log')}">
<a href="/admin/log"><i class="fa fa-calendar"></i> 操作日志</a>
</li>
<li class="{:checkIfActive('set')}">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-cog"></i> 系统设置<b
class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/admin/set">系统基本设置</a></li>
<li><a href="/admin/set/mod/task">定时任务设置</a></li>
<li><a href="/admin/set/mod/account">管理账号设置</a></li>
</ul>
</li>
<li>
<a href="/admin/logout" onclick="return confirm('确定退出登录吗?')"><i class="fa fa-power-off"></i> 退出登录</a>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container -->
</nav><!-- /.navbar -->
{block name="main"}主内容{/block}
</body>
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8" />
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>{block name="title"}标题{/block}</title>
<link href="{$cdnpublic}twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet" />
<link href="{$cdnpublic}font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<link href="/static/css/bootstrap-table.css" rel="stylesheet" />
<script src="{$cdnpublic}modernizr/2.8.3/modernizr.min.js"></script>
<script src="{$cdnpublic}jquery/2.1.4/jquery.min.js"></script>
<script src="{$cdnpublic}twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<!--[if lt IE 9]>
<script src="{$cdnpublic}html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="{$cdnpublic}respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<nav class="navbar navbar-fixed-top navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
aria-expanded="false" aria-controls="navbar">
<span class="sr-only">导航按钮</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/admin">宝塔第三方云端管理中心</a>
</div><!-- /.navbar-header -->
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li class="{:checkIfActive('index')}">
<a href="/admin"><i class="fa fa-home"></i> 后台首页</a>
</li>
<li class="{:checkIfActive('plugins,pluginswin,pluginsen,deplist')}">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-cubes"></i> 插件列表<b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="{:checkIfActive('plugins')}"><a href="/admin/plugins">Linux面板</a></li>
<li class="{:checkIfActive('pluginswin')}"><a href="/admin/pluginswin">Windows面板</a></li>
<li class="{:checkIfActive('pluginsen')}"><a href="/admin/pluginsen">aaPanel面板</a></li>
<li class="{:checkIfActive('deplist')}"><a href="/admin/deplist">一键部署列表</a></li>
</ul>
</li>
<li class="{:checkIfActive('record')}">
<a href="/admin/record"><i class="fa fa-list"></i> 使用记录</a>
</li>
<li class="{:checkIfActive('list')}">
<a href="/admin/list"><i class="fa fa-globe"></i> 黑白名单</a>
</li>
<li class="{:checkIfActive('log')}">
<a href="/admin/log"><i class="fa fa-calendar"></i> 操作日志</a>
</li>
<li class="{:checkIfActive('ssl')}">
<a href="/admin/ssl"><i class="fa fa-expeditedssl"></i> 自签SSL</a>
</li>
<li class="{:checkIfActive('set')}">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-cog"></i> 系统设置<b
class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/admin/set">软件版本设置</a></li>
<li><a href="/admin/set/mod/api">面板接口设置</a></li>
<li><a href="/admin/set/mod/task">自动更新插件设置</a></li>
<li><a href="/admin/set/mod/tools">替换与清理工具</a></li>
<li><a href="/admin/set/mod/account">管理账号设置</a></li>
</ul>
</li>
<li>
<a href="/admin/logout" onclick="return confirm('确定退出登录吗?')"><i class="fa fa-power-off"></i> 退出登录</a>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container -->
</nav><!-- /.navbar -->
{block name="main"}主内容{/block}
</body>
</html>

View File

@ -1,209 +1,209 @@
{extend name="admin/layout" /}
{block name="title"}黑白名单{/block}
{block name="main"}
<style>
.alert{margin-bottom: 5px;}
</style>
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 col-md-10 center-block" style="float: none;">
<ul class="nav nav-tabs">
<li class="{if $type=='black'}active{/if}"><a href="/admin/list">黑名单列表</a></li><li class="{if $type=='white'}active{/if}"><a href="/admin/list/type/white">白名单列表</a></li>
</ul>
{if $type=='black' && config_get('whitelist')=='1'}
<div class="alert alert-warning">提示:当前为白名单模式,黑名单列表里面的记录不会生效。</div>
{/if}
{if $type=='white' && config_get('whitelist')=='0'}
<div class="alert alert-warning">提示:当前未开启白名单模式,白名单列表里面的记录不会生效。</div>
{/if}
{if $type=='black'}
<div class="alert alert-info">添加到黑名单列表中的服务器IP将无法使用此云端</div>
{/if}
{if $type=='white'}
<div class="alert alert-info">只有添加到白名单列表中的服务器IP才可以使用此云端</div>
{/if}
<div id="searchToolbar">
<form onsubmit="return searchSubmit()" method="GET" class="form-inline">
<div class="form-group">
<label>搜索</label>
<input type="text" class="form-control" name="ip" placeholder="服务器IP">
</div>
<div class="form-group">
<button class="btn btn-primary" type="submit"><i class="fa fa-search"></i>搜索</button>&nbsp;
<a href="javascript:searchClear()" class="btn btn-default"><i class="fa fa-repeat"></i>重置</a>&nbsp;
<a href="javascript:add_item()" class="btn btn-success"><i class="fa fa-plus"></i>添加</a>&nbsp;
</div>
</form>
</div>
<table id="listTable">
</table>
</div>
</div>
<script src="//cdn.staticfile.org/layer/3.5.1/layer.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/bootstrap-table.min.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script>
<script src="/static/js/custom.js"></script>
<script>
function setEnable(id,enable) {
$.ajax({
type : 'POST',
url : '/admin/list_op/type/{$type}',
data: { act:'enable', id:id, enable:enable},
dataType : 'json',
success : function(data) {
if(data.code == 0){
searchSubmit();
}else{
layer.msg(data.msg, {icon:2, time:1500});
}
},
error:function(data){
layer.msg('服务器错误');
}
});
}
function add_item(){
layer.open({
area: ['360px'],
title: '添加IP{$typename}',
content: '<div class="form-group"><input type="text" class="form-control" name="item_input" placeholder="请输入服务器IP" value=""></div>',
yes: function(){
var ip = $("input[name='item_input']").val();
$.ajax({
type : 'POST',
url : '/admin/list_op/type/{$type}',
data: { act:'add', ip:ip},
dataType : 'json',
success : function(data) {
if(data.code == 0){
layer.msg('添加成功', {icon:1, time:800});
searchSubmit();
}else{
layer.alert(data.msg, {icon: 2});
}
},
error:function(data){
layer.msg('服务器错误');
}
});
},
shadeClose: true
});
}
function edit_item(id){
$.ajax({
type : 'POST',
url : '/admin/list_op/type/{$type}',
data: { act:'get', id:id},
dataType : 'json',
success : function(data) {
if(data.code == 0){
layer.open({
area: ['360px'],
title: '编辑IP{$typename}',
content: '<div class="form-group"><input type="text" class="form-control" name="item_input" placeholder="请输入服务器IP" value="'+data.data.ip+'"></div>',
yes: function(){
var ip = $("input[name='item_input']").val();
$.ajax({
type : 'POST',
url : '/admin/list_op/type/{$type}',
data: { act:'edit', id:id, ip:ip},
dataType : 'json',
success : function(data) {
if(data.code == 0){
layer.msg('修改成功', {icon:1, time:800});
searchSubmit();
}else{
layer.alert(data.msg, {icon: 2});
}
},
error:function(data){
layer.msg('服务器错误');
}
});
},
shadeClose: true
});
}else{
layer.alert(data.msg, {icon: 2});
}
},
error:function(data){
layer.msg('服务器错误');
}
});
}
function del_item(id) {
if(confirm('是否确定删除此记录?')){
$.ajax({
type : 'POST',
url : '/admin/list_op/type/{$type}',
data: { act:'del', id:id},
dataType : 'json',
success : function(data) {
if(data.code == 0){
layer.msg('删除成功!', {icon:1, time:800});
searchSubmit();
}else{
layer.alert(data.msg, {icon:2});
}
},
error:function(data){
layer.msg('服务器错误');
}
});
}
}
$(document).ready(function(){
updateToolbar();
const defaultPageSize = 15;
const pageNumber = typeof window.$_GET['pageNumber'] != 'undefined' ? parseInt(window.$_GET['pageNumber']) : 1;
const pageSize = typeof window.$_GET['pageSize'] != 'undefined' ? parseInt(window.$_GET['pageSize']) : defaultPageSize;
$("#listTable").bootstrapTable({
url: '/admin/list_data/type/{$type}',
pageNumber: pageNumber,
pageSize: pageSize,
classes: 'table table-striped table-hover table-bottom-border',
columns: [
{
field: 'id',
title: 'ID',
formatter: function(value, row, index) {
return '<b>'+value+'</b>';
}
},
{
field: 'ip',
title: '服务器IP'
},
{
field: 'enable',
title: '是否生效',
formatter: function(value, row, index) {
return value?'<a href="javascript:setEnable('+row.id+',0)"><font color=green><i class="fa fa-check-circle"></i>已生效</font></a>':'<a href="javascript:setEnable('+row.id+',1)"><font color=red><i class="fa fa-times-circle"></i>未生效</font></a>';
}
},
{
field: 'addtime',
title: '添加时间'
},
{
field: '',
title: '操作',
formatter: function(value, row, index) {
return '<a href="javascript:edit_item('+row.id+')" class="btn btn-xs btn-info">编辑</a>&nbsp;<a href="javascript:del_item('+row.id+')" class="btn btn-xs btn-danger">删除</a>';
},
},
],
})
})
</script>
{extend name="admin/layout" /}
{block name="title"}黑白名单{/block}
{block name="main"}
<style>
.alert{margin-bottom: 5px;}
</style>
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 col-md-10 center-block" style="float: none;">
<ul class="nav nav-tabs">
<li class="{if $type=='black'}active{/if}"><a href="/admin/list">黑名单列表</a></li><li class="{if $type=='white'}active{/if}"><a href="/admin/list/type/white">白名单列表</a></li>
</ul>
{if $type=='black' && config_get('whitelist')=='1'}
<div class="alert alert-warning">提示:当前为白名单模式,黑名单列表里面的记录不会生效。</div>
{/if}
{if $type=='white' && config_get('whitelist')=='0'}
<div class="alert alert-warning">提示:当前未开启白名单模式,白名单列表里面的记录不会生效。</div>
{/if}
{if $type=='black'}
<div class="alert alert-info">添加到黑名单列表中的服务器IP将无法使用此云端</div>
{/if}
{if $type=='white'}
<div class="alert alert-info">只有添加到白名单列表中的服务器IP才可以使用此云端</div>
{/if}
<div id="searchToolbar">
<form onsubmit="return searchSubmit()" method="GET" class="form-inline">
<div class="form-group">
<label>搜索</label>
<input type="text" class="form-control" name="ip" placeholder="服务器IP">
</div>
<div class="form-group">
<button class="btn btn-primary" type="submit"><i class="fa fa-search"></i> 搜索</button>&nbsp;
<a href="javascript:searchClear()" class="btn btn-default"><i class="fa fa-repeat"></i> 重置</a>&nbsp;
<a href="javascript:add_item()" class="btn btn-success"><i class="fa fa-plus"></i> 添加</a>&nbsp;
</div>
</form>
</div>
<table id="listTable">
</table>
</div>
</div>
<script src="{$cdnpublic}layer/3.5.1/layer.js"></script>
<script src="{$cdnpublic}bootstrap-table/1.19.1/bootstrap-table.min.js"></script>
<script src="{$cdnpublic}bootstrap-table/1.19.1/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script>
<script src="/static/js/custom.js"></script>
<script>
function setEnable(id,enable) {
$.ajax({
type : 'POST',
url : '/admin/list_op/type/{$type}',
data: { act:'enable', id:id, enable:enable},
dataType : 'json',
success : function(data) {
if(data.code == 0){
searchSubmit();
}else{
layer.msg(data.msg, {icon:2, time:1500});
}
},
error:function(data){
layer.msg('服务器错误');
}
});
}
function add_item(){
layer.open({
area: ['360px'],
title: '添加IP{$typename}',
content: '<div class="form-group"><input type="text" class="form-control" name="item_input" placeholder="请输入服务器IP" value=""></div>',
yes: function(){
var ip = $("input[name='item_input']").val();
$.ajax({
type : 'POST',
url : '/admin/list_op/type/{$type}',
data: { act:'add', ip:ip},
dataType : 'json',
success : function(data) {
if(data.code == 0){
layer.msg('添加成功', {icon:1, time:800});
searchSubmit();
}else{
layer.alert(data.msg, {icon: 2});
}
},
error:function(data){
layer.msg('服务器错误');
}
});
},
shadeClose: true
});
}
function edit_item(id){
$.ajax({
type : 'POST',
url : '/admin/list_op/type/{$type}',
data: { act:'get', id:id},
dataType : 'json',
success : function(data) {
if(data.code == 0){
layer.open({
area: ['360px'],
title: '编辑IP{$typename}',
content: '<div class="form-group"><input type="text" class="form-control" name="item_input" placeholder="请输入服务器IP" value="'+data.data.ip+'"></div>',
yes: function(){
var ip = $("input[name='item_input']").val();
$.ajax({
type : 'POST',
url : '/admin/list_op/type/{$type}',
data: { act:'edit', id:id, ip:ip},
dataType : 'json',
success : function(data) {
if(data.code == 0){
layer.msg('修改成功', {icon:1, time:800});
searchSubmit();
}else{
layer.alert(data.msg, {icon: 2});
}
},
error:function(data){
layer.msg('服务器错误');
}
});
},
shadeClose: true
});
}else{
layer.alert(data.msg, {icon: 2});
}
},
error:function(data){
layer.msg('服务器错误');
}
});
}
function del_item(id) {
if(confirm('是否确定删除此记录?')){
$.ajax({
type : 'POST',
url : '/admin/list_op/type/{$type}',
data: { act:'del', id:id},
dataType : 'json',
success : function(data) {
if(data.code == 0){
layer.msg('删除成功!', {icon:1, time:800});
searchSubmit();
}else{
layer.alert(data.msg, {icon:2});
}
},
error:function(data){
layer.msg('服务器错误');
}
});
}
}
$(document).ready(function(){
updateToolbar();
const defaultPageSize = 15;
const pageNumber = typeof window.$_GET['pageNumber'] != 'undefined' ? parseInt(window.$_GET['pageNumber']) : 1;
const pageSize = typeof window.$_GET['pageSize'] != 'undefined' ? parseInt(window.$_GET['pageSize']) : defaultPageSize;
$("#listTable").bootstrapTable({
url: '/admin/list_data/type/{$type}',
pageNumber: pageNumber,
pageSize: pageSize,
classes: 'table table-striped table-hover table-bottom-border',
columns: [
{
field: 'id',
title: 'ID',
formatter: function(value, row, index) {
return '<b>'+value+'</b>';
}
},
{
field: 'ip',
title: '服务器IP'
},
{
field: 'enable',
title: '是否生效',
formatter: function(value, row, index) {
return value?'<a href="javascript:setEnable('+row.id+',0)"><font color=green><i class="fa fa-check-circle"></i>已生效</font></a>':'<a href="javascript:setEnable('+row.id+',1)"><font color=red><i class="fa fa-times-circle"></i>未生效</font></a>';
}
},
{
field: 'addtime',
title: '添加时间'
},
{
field: '',
title: '操作',
formatter: function(value, row, index) {
return '<a href="javascript:edit_item('+row.id+')" class="btn btn-xs btn-info">编辑</a>&nbsp;<a href="javascript:del_item('+row.id+')" class="btn btn-xs btn-danger">删除</a>';
},
},
],
})
})
</script>
{/block}

View File

@ -1,74 +1,74 @@
{extend name="admin/layout" /}
{block name="title"}操作日志{/block}
{block name="main"}
<style>
</style>
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 col-md-10 center-block" style="float: none;">
<div id="searchToolbar">
<form onsubmit="return searchSubmit()" method="GET" class="form-inline">
<div class="form-group">
<label>搜索</label>
<input type="text" class="form-control" name="action" placeholder="操作类型">
</div>
<div class="form-group">
<button class="btn btn-primary" type="submit"><i class="fa fa-search"></i>搜索</button>&nbsp;
<a href="javascript:searchClear()" class="btn btn-default"><i class="fa fa-repeat"></i>重置</a>&nbsp;
</div>
</form>
</div>
<table id="listTable">
</table>
</div>
</div>
<script src="//cdn.staticfile.org/layer/3.5.1/layer.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/bootstrap-table.min.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script>
<script src="/static/js/custom.js"></script>
<script>
$(document).ready(function(){
updateToolbar();
const defaultPageSize = 20;
const pageNumber = typeof window.$_GET['pageNumber'] != 'undefined' ? parseInt(window.$_GET['pageNumber']) : 1;
const pageSize = typeof window.$_GET['pageSize'] != 'undefined' ? parseInt(window.$_GET['pageSize']) : defaultPageSize;
$("#listTable").bootstrapTable({
url: '/admin/log_data',
pageNumber: pageNumber,
pageSize: pageSize,
classes: 'table table-striped table-hover table-bottom-border',
columns: [
{
field: 'id',
title: 'ID',
formatter: function(value, row, index) {
return '<b>'+value+'</b>';
}
},
{
field: 'uid',
title: '操作人',
formatter: function(value, row, index) {
return value==1?'<font color="green">定时任务</font>':'<font color="blue">管理员</font>';
}
},
{
field: 'action',
title: '操作类型'
},
{
field: 'data',
title: '操作详情',
},
{
field: 'addtime',
title: '操作时间'
},
],
})
})
</script>
{extend name="admin/layout" /}
{block name="title"}操作日志{/block}
{block name="main"}
<style>
</style>
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 col-md-10 center-block" style="float: none;">
<div id="searchToolbar">
<form onsubmit="return searchSubmit()" method="GET" class="form-inline">
<div class="form-group">
<label>搜索</label>
<input type="text" class="form-control" name="action" placeholder="操作类型">
</div>
<div class="form-group">
<button class="btn btn-primary" type="submit"><i class="fa fa-search"></i> 搜索</button>&nbsp;
<a href="javascript:searchClear()" class="btn btn-default"><i class="fa fa-repeat"></i> 重置</a>&nbsp;
</div>
</form>
</div>
<table id="listTable">
</table>
</div>
</div>
<script src="{$cdnpublic}layer/3.5.1/layer.js"></script>
<script src="{$cdnpublic}bootstrap-table/1.19.1/bootstrap-table.min.js"></script>
<script src="{$cdnpublic}bootstrap-table/1.19.1/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script>
<script src="/static/js/custom.js"></script>
<script>
$(document).ready(function(){
updateToolbar();
const defaultPageSize = 20;
const pageNumber = typeof window.$_GET['pageNumber'] != 'undefined' ? parseInt(window.$_GET['pageNumber']) : 1;
const pageSize = typeof window.$_GET['pageSize'] != 'undefined' ? parseInt(window.$_GET['pageSize']) : defaultPageSize;
$("#listTable").bootstrapTable({
url: '/admin/log_data',
pageNumber: pageNumber,
pageSize: pageSize,
classes: 'table table-striped table-hover table-bottom-border',
columns: [
{
field: 'id',
title: 'ID',
formatter: function(value, row, index) {
return '<b>'+value+'</b>';
}
},
{
field: 'uid',
title: '操作人',
formatter: function(value, row, index) {
return value==1?'<font color="green">定时任务</font>':'<font color="blue">管理员</font>';
}
},
{
field: 'action',
title: '操作类型'
},
{
field: 'data',
title: '操作详情',
},
{
field: 'addtime',
title: '操作时间'
},
],
})
})
</script>
{/block}

View File

@ -1,97 +1,100 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8"/>
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>管理员登录</title>
<link href="//cdn.staticfile.org/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="//cdn.staticfile.org/modernizr/2.8.3/modernizr.min.js"></script>
<script src="//cdn.staticfile.org/jquery/2.1.4/jquery.min.js"></script>
<!--[if lt IE 9]>
<script src="//cdn.staticfile.org/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="//cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<nav class="navbar navbar-fixed-top navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">导航按钮</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="./">宝塔第三方云端管理中心</a>
</div><!-- /.navbar-header -->
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li class="active">
<a href="./login.php"><span class="glyphicon glyphicon-user"></span> 登录</a>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container -->
</nav><!-- /.navbar -->
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 col-sm-10 col-md-8 col-lg-6 center-block" style="float: none;">
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">管理员登录</h3></div>
<div class="panel-body">
<form class="form-horizontal" role="form" onsubmit="return submitlogin()">
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
<input type="text" name="user" value="" class="form-control input-lg" placeholder="用户名" required="required"/>
</div><br/>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></span>
<input type="password" name="pass" class="form-control input-lg" placeholder="密码" required="required"/>
</div><br/>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-adjust"></span></span>
<input type="text" class="form-control input-lg" name="code" placeholder="输入验证码" autocomplete="off" required>
<span class="input-group-addon" style="padding: 0">
<img src="/admin/verifycode" height="45" id="verifycode" onclick="this.src='/admin/verifycode?r='+Math.random();" title="点击更换验证码">
</span>
</div><br/>
<div class="form-group">
<div class="col-xs-12"><input type="submit" value="立即登录" class="btn btn-primary btn-block btn-lg"/></div>
</div>
</form>
</div>
</div>
</div>
</div>
<script src="//cdn.staticfile.org/layer/3.5.1/layer.js"></script>
<script>
function submitlogin(){
var user = $("input[name='user']").val();
var pass = $("input[name='pass']").val();
var code = $("input[name='code']").val();
if(user=='' || pass==''){layer.alert('用户名或密码不能为空!');return false;}
$.ajax({
type : 'POST',
url : '{:request()->url()}',
data: {username:user, password:pass, code:code},
dataType : 'json',
success : function(data) {
if(data.code == 0){
layer.msg('登录成功,正在跳转', {icon: 1,shade: 0.01,time: 15000});
window.location.href='/admin';
}else{
if(data.msg.indexOf('验证码')==-1){
$("#verifycode").attr('src', '/admin/verifycode?r='+Math.random())
}
layer.alert(data.msg, {icon: 2});
}
},
error:function(data){
layer.msg('服务器错误');
}
});
return false;
}
</script>
</body>
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8"/>
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>管理员登录</title>
<link href="{$cdnpublic}twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="{$cdnpublic}modernizr/2.8.3/modernizr.min.js"></script>
<script src="{$cdnpublic}jquery/2.1.4/jquery.min.js"></script>
<!--[if lt IE 9]>
<script src="{$cdnpublic}html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="{$cdnpublic}respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<nav class="navbar navbar-fixed-top navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">导航按钮</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="./">Cloud</a>
</div><!-- /.navbar-header -->
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav navbar-right">
<li class="active">
<a href="#"><span class="glyphicon glyphicon-user"></span> 登录</a>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container -->
</nav><!-- /.navbar -->
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 col-sm-10 col-md-8 col-lg-6 center-block" style="float: none;">
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">管理员登录</h3></div>
<div class="panel-body">
<form class="form-horizontal" role="form" onsubmit="return submitlogin()">
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
<input type="text" name="user" value="" class="form-control input-lg" placeholder="用户名" required="required"/>
</div><br/>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></span>
<input type="password" name="pass" class="form-control input-lg" placeholder="密码" required="required"/>
</div><br/>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-adjust"></span></span>
<input type="text" class="form-control input-lg" name="code" placeholder="输入验证码" autocomplete="off" required>
<span class="input-group-addon" style="padding: 0">
<img src="/admin/verifycode" height="45" id="verifycode" onclick="this.src='/admin/verifycode?r='+Math.random();" title="点击更换验证码">
</span>
</div><br/>
<div class="form-group">
<div class="col-xs-12"><input type="submit" value="立即登录" class="btn btn-primary btn-block btn-lg"/></div>
</div>
</form>
</div>
</div>
</div>
</div>
<script src="{$cdnpublic}layer/3.5.1/layer.js"></script>
<script>
function submitlogin(){
var user = $("input[name='user']").val();
var pass = $("input[name='pass']").val();
var code = $("input[name='code']").val();
if(user=='' || pass==''){layer.alert('用户名或密码不能为空!');return false;}
var ii = layer.load(2);
$.ajax({
type : 'POST',
url : '{:request()->url()}',
data: {username:user, password:pass, code:code},
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.msg('登录成功,正在跳转', {icon: 1,shade: 0.01,time: 15000});
window.location.href='/admin';
}else{
if(data.msg.indexOf('验证码')==-1){
$("#verifycode").attr('src', '/admin/verifycode?r='+Math.random())
}
layer.alert(data.msg, {icon: 2});
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
return false;
}
</script>
</body>
</html>

View File

@ -1,214 +1,282 @@
{extend name="admin/layout" /}
{block name="title"}插件列表{/block}
{block name="main"}
<style>
td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:340px;}
.bt-ico-ask {
border: 1px solid #fb7d00;
border-radius: 8px;
color: #fb7d00;
cursor: help;
display: inline-block;
font-family: arial;
font-size: 11px;
font-style: normal;
height: 16px;
line-height: 16px;
margin-left: 5px;
text-align: center;
width: 16px
}
.bt-ico-ask:hover {
background-color: #fb7d00;
color: #fff
}
</style>
<div class="modal fade" id="help" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">帮助</h4>
</div>
<div class="modal-body">
<p>“版本与状态”一列中,红色的按钮代表本地不存在该版本插件包,需要点击下载;绿色的按钮代表已存在。</p>
<p>插件包本地存储路径是/data/plugins/package/软件标识-版本号.zip对于部分包含二次验证的插件可以自行修改。</p>
<p>点击【重新获取】按钮会从宝塔官方获取最新插件列表,但是插件包需要手动点击下载。如果需要批量下载插件包,可查看<a href="/admin/set/type/task">定时任务设置</a></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 center-block" style="float: none;">
<div id="searchToolbar">
<form onsubmit="return searchSubmit()" method="GET" class="form-inline">
<div class="form-group">
<label>搜索</label>
<input type="text" class="form-control" name="keyword" placeholder="应用名称">
</div>
<div class="form-group">
<select name="type" class="form-control"><option value="0">全部插件</option>
{foreach $typelist as $k=>$v}<option value="{$k}">{$v}</option>{/foreach} </select>
</div>
<div class="form-group">
<button class="btn btn-primary" type="submit"><i class="fa fa-search"></i>搜索</button>&nbsp;
<a href="javascript:searchClear()" class="btn btn-default"><i class="fa fa-repeat"></i>重置</a>&nbsp;
<a href="javascript:refresh_plugins()" class="btn btn-success"><i class="fa fa-refresh"></i>重新获取</a>&nbsp;
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#help"><i class="fa fa-info-circle"></i>帮助</button>
</div>
</form>
</div>
<table id="listTable">
</table>
</div>
</div>
<script src="//cdn.staticfile.org/layer/3.5.1/layer.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/bootstrap-table.min.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script>
<script src="/static/js/custom.js"></script>
<script>
function download_version(name, version, status){
if(status == true){
var confirm = layer.confirm('是否确定重新下载'+version+'版本插件包?', {
btn: ['确定','取消']
}, function(){
download_plugin(name, version)
}, function(){
layer.close(confirm)
});
}else{
download_plugin(name, version)
}
}
function download_plugin(name, version){
var ii = layer.msg('正在下载,请稍候...', {icon: 16, shade:0.1, time: 0});
$.ajax({
type : 'POST',
url : '/admin/download_plugin',
data: { name:name, version:version},
dataType : 'json',
success : function(data) {
layer.close(ii)
if(data.code == 0){
layer.alert(data.msg, {icon:1}, function(){layer.closeAll();searchSubmit();});
}else{
layer.alert(data.msg, {icon:2});
}
},
error:function(data){
layer.close(ii)
layer.msg('服务器错误', {icon:2});
}
});
}
function refresh_plugins(){
var confirm = layer.confirm('是否确定从宝塔官方获取最新插件列表?', {
btn: ['确定','取消']
}, function(){
layer.close(confirm)
var ii = layer.msg('正在获取插件列表,请稍候...', {icon: 16, shade:0.1, time: 0});
$.ajax({
type : 'GET',
url : '/admin/refresh_plugins',
dataType : 'json',
success : function(data) {
layer.close(ii)
if(data.code == 0){
layer.alert(data.msg, {icon:1}, function(){layer.closeAll();searchSubmit();});
}else{
layer.alert(data.msg, {icon:2});
}
},
error:function(data){
layer.close(ii)
layer.msg('服务器错误', {icon:2});
}
});
}, function(){
layer.close(confirm)
});
}
function searchByType(type){
$("input[name=keyword]").val('');
$("select[name=type]").val(type);
searchSubmit()
}
$(document).ready(function(){
updateToolbar();
const defaultPageSize = 20;
$("#listTable").bootstrapTable({
url: '/admin/plugins_data',
pageNumber: 1,
pageSize: 15,
sidePagination: 'client',
classes: 'table table-striped table-hover table-bottom-border',
columns: [
{
field: 'name',
title: '软件标识',
formatter: function(value, row, index) {
return '<b>'+value+'</b>';
}
},
{
field: 'title',
title: '软件名称'
},
{
field: 'type',
title: '软件分类',
formatter: function(value, row, index) {
return '<a href="javascript:searchByType('+value+')" title="查看该分类下的插件">'+row.typename+'</a>';
}
},
{
field: 'desc',
title: '说明',
},
{
field: 'price',
title: '价格',
formatter: function(value, row, index) {
return value > 0 ? '<span style="color:#fc6d26">¥'+value+'</span>' : '免费';
}
},
{
field: 'author',
title: '开发商'
},
{
field: 'versions',
title: '版本与状态',
formatter: function(value, row, index) {
var html = '';
if(row.type == 5){
html += '<a href="javascript:" class="btn btn-xs btn-success" disabled>无需下载</a>';
}else{
$.each(value, function(index,item){
if(item.status)
html += '<a href="javascript:download_version(\''+row.name+'\',\''+item.version+'\','+item.status+')" class="btn btn-xs btn-success">'+item.version+'</a>&nbsp;';
else
html += '<a href="javascript:download_version(\''+row.name+'\',\''+item.version+'\','+item.status+')" class="btn btn-xs btn-danger">'+item.version+'</a>&nbsp;';
})
}
return html
}
},
],
})
})
</script>
{extend name="admin/layout" /}
{block name="title"}插件列表{/block}
{block name="main"}
<style>
td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:340px;}
.bt-ico-ask {
border: 1px solid #fb7d00;
border-radius: 8px;
color: #fb7d00;
cursor: help;
display: inline-block;
font-family: arial;
font-size: 11px;
font-style: normal;
height: 16px;
line-height: 16px;
margin-left: 5px;
text-align: center;
width: 16px
}
.bt-ico-ask:hover {
background-color: #fb7d00;
color: #fff
}
</style>
<div class="modal fade" id="help" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">帮助</h4>
</div>
<div class="modal-body">
<p>“版本与状态”一列中,红色的按钮代表本地不存在该版本插件包,需要点击下载;绿色的按钮代表已存在。</p>
<p>官方插件包本地存储路径是/data/plugins/package/软件标识-版本号.zip第三方插件包路径是/data/plugins/other/other/,对于部分包含二次验证的插件可以自行修改。</p>
<p>若对接的服务器网速较慢,可能会导致下载失败,提示"服务器错误",可稍等一会,进入对接服务器/tmp/plugins目录下载插件包将_改成-,并上传到本站/data/plugins/package目录下。</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 center-block" style="float: none;">
<div id="searchToolbar">
<form onsubmit="return searchSubmit()" method="GET" class="form-inline">
<div class="form-group">
<label>搜索</label>
<input type="text" class="form-control" name="keyword" placeholder="应用名称">
</div>
<div class="form-group">
<select name="type" class="form-control"><option value="0">全部插件</option>
{foreach $typelist as $k=>$v}<option value="{$k}">{$v}</option>{/foreach} </select>
</div>
<div class="form-group">
<button class="btn btn-primary" type="submit"><i class="fa fa-search"></i> 搜索</button>&nbsp;
<a href="javascript:searchClear()" class="btn btn-default"><i class="fa fa-repeat"></i> 重置</a>&nbsp;
<a href="javascript:refresh_plugins()" class="btn btn-success"><i class="fa fa-refresh"></i> 刷新列表</a>&nbsp;
<a href="javascript:download_plugins()" class="btn btn-warning" id="batch_down" style="display:none;"><i class="fa fa-download"></i> 批量下载</a>&nbsp;
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#help"><i class="fa fa-info-circle"></i> 帮助</button>
</div>
</form>
</div>
<table id="listTable">
</table>
</div>
</div>
<script src="{$cdnpublic}layer/3.5.1/layer.js"></script>
<script src="{$cdnpublic}bootstrap-table/1.19.1/bootstrap-table.min.js"></script>
<script src="{$cdnpublic}bootstrap-table/1.19.1/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script>
<script src="/static/js/custom.js"></script>
<script>
var skip_plugins = {:json_encode($skip_plugins)};
function download_version(name, version, status){
if(status == true){
var confirm = layer.confirm('是否确定重新下载'+version+'版本插件包?', {
btn: ['确定','取消']
}, function(){
download_plugin(name, version)
}, function(){
layer.close(confirm)
});
}else{
download_plugin(name, version)
}
}
function download_plugin(name, version){
var ii = layer.msg('正在下载,请稍候...', {icon: 16, shade:0.1, time: 0});
$.ajax({
type : 'POST',
url : '/admin/download_plugin',
data: { name:name, version:version},
dataType : 'json',
success : function(data) {
layer.close(ii)
if(data.code == 0){
layer.alert(data.msg, {icon:1}, function(){layer.closeAll();searchSubmit();});
}else{
layer.alert(data.msg, {icon:2});
}
},
error:function(data){
layer.close(ii)
layer.msg('服务器错误', {icon:2});
}
});
}
function refresh_plugins(){
var confirm = layer.confirm('是否确定从宝塔官方获取最新插件列表?', {
btn: ['确定','取消']
}, function(){
layer.close(confirm)
var ii = layer.msg('正在获取插件列表,请稍候...', {icon: 16, shade:0.1, time: 0});
$.ajax({
type : 'GET',
url : '/admin/refresh_plugins',
dataType : 'json',
success : function(data) {
layer.close(ii)
if(data.code == 0){
layer.alert(data.msg, {icon:1}, function(){layer.closeAll();searchSubmit();});
}else{
layer.alert(data.msg, {icon:2});
}
},
error:function(data){
layer.close(ii)
layer.msg('服务器错误', {icon:2});
}
});
}, function(){
layer.close(confirm)
});
}
function download_plugins(){
var confirm = layer.confirm('批量下载当前分类下未下载的插件包', {
btn: ['确定','取消']
}, function(){
layer.close(confirm)
$.downloadCount = 0;
$.preDownloadCount = $.preDownload.length;
download_item();
}, function(){
layer.close(confirm)
});
}
function download_item(){
if($.preDownload.length == 0){
layer.alert('成功下载'+$.downloadCount+'个插件包!', {icon:1}, function(){layer.closeAll();searchSubmit();});
return;
}
var plugin = $.preDownload[0];
if(skip_plugins.indexOf(plugin.name) != -1){
$.preDownload.shift();
download_item();
return;
}
$.downloadCount++;
var ii = layer.msg('['+$.downloadCount+'/'+$.preDownloadCount+']正在下载'+plugin.name+'-'+plugin.version, {icon: 16, shade:0.1, time: 0});
$.ajax({
type : 'POST',
url : '/admin/download_plugin',
data: { name:plugin.name, version:plugin.version},
dataType : 'json',
success : function(data) {
layer.close(ii)
if(data.code == 0){
$.preDownload.shift();
download_item();
}else{
layer.alert(data.msg, {icon:2});
}
},
error:function(data){
layer.close(ii)
layer.msg('服务器错误', {icon:2});
}
});
}
function searchByType(type){
$("input[name=keyword]").val('');
$("select[name=type]").val(type);
searchSubmit()
}
$(document).ready(function(){
updateToolbar();
const defaultPageSize = 20;
$("#listTable").bootstrapTable({
url: '/admin/plugins_data',
pageNumber: 1,
pageSize: 15,
sidePagination: 'client',
classes: 'table table-striped table-hover table-bottom-border',
columns: [
{
field: 'name',
title: '软件标识',
formatter: function(value, row, index) {
return '<b>'+value+'</b>';
}
},
{
field: 'title',
title: '软件名称'
},
{
field: 'type',
title: '软件分类',
formatter: function(value, row, index) {
return '<a href="javascript:searchByType('+value+')" title="查看该分类下的插件">'+row.typename+'</a>';
}
},
{
field: 'desc',
title: '说明',
},
{
field: 'price',
title: '价格',
formatter: function(value, row, index) {
return value > 0 ? '<span style="color:#fc6d26">¥'+value+'</span>' : '免费';
}
},
{
field: 'author',
title: '开发商'
},
{
field: 'versions',
title: '版本与状态',
formatter: function(value, row, index) {
var html = '';
if(row.type == 5){
html += '<a href="javascript:" class="btn btn-xs btn-success" disabled>无需下载</a>';
}else{
$.each(value, function(index,item){
if(item.status)
html += '<a href="javascript:download_version(\''+row.name+'\',\''+item.version+'\','+item.status+')" class="btn btn-xs btn-success">'+item.version+'</a>&nbsp;';
else
html += '<a href="javascript:download_version(\''+row.name+'\',\''+item.version+'\','+item.status+')" class="btn btn-xs btn-danger">'+item.version+'</a>&nbsp;';
})
}
return html
}
},
],
onLoadSuccess: function(data){
$.preDownload = [];
var type = $("select[name=type] option:selected").text();
if(type != '全部插件' && type != '运行环境' && type != '第三方应用'){
$("#batch_down").show();
if(data.length > 0){
$.each(data, function(index, plugin){
if(plugin.versions.length > 0){
$.each(plugin.versions, function(index, version){
if(!version.status){
$.preDownload.push({name:plugin.name, version:version.version})
}
});
}
});
}
}else{
$("#batch_down").hide();
}
}
})
})
</script>
{/block}

View File

@ -0,0 +1,276 @@
{extend name="admin/layout" /}
{block name="title"}插件列表{/block}
{block name="main"}
<style>
td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:340px;}
.bt-ico-ask {
border: 1px solid #fb7d00;
border-radius: 8px;
color: #fb7d00;
cursor: help;
display: inline-block;
font-family: arial;
font-size: 11px;
font-style: normal;
height: 16px;
line-height: 16px;
margin-left: 5px;
text-align: center;
width: 16px
}
.bt-ico-ask:hover {
background-color: #fb7d00;
color: #fff
}
</style>
<div class="modal fade" id="help" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">帮助</h4>
</div>
<div class="modal-body">
<p>“版本与状态”一列中,红色的按钮代表本地不存在该版本插件包,需要点击下载;绿色的按钮代表已存在。</p>
<p>官方插件包本地存储路径是/data/en/plugins/package/软件标识-版本号.zip第三方插件包路径是/data/plugins/other/other/,对于部分包含二次验证的插件可以自行修改。</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 center-block" style="float: none;">
<div id="searchToolbar">
<form onsubmit="return searchSubmit()" method="GET" class="form-inline">
<div class="form-group">
<label>搜索</label>
<input type="text" class="form-control" name="keyword" placeholder="应用名称">
</div>
<div class="form-group">
<select name="type" class="form-control"><option value="0">全部插件</option>
{foreach $typelist as $k=>$v}<option value="{$k}">{$v}</option>{/foreach} </select>
</div>
<div class="form-group">
<button class="btn btn-primary" type="submit"><i class="fa fa-search"></i> 搜索</button>&nbsp;
<a href="javascript:searchClear()" class="btn btn-default"><i class="fa fa-repeat"></i> 重置</a>&nbsp;
<a href="javascript:refresh_plugins()" class="btn btn-success"><i class="fa fa-refresh"></i> 刷新列表</a>&nbsp;
<a href="javascript:download_plugins()" class="btn btn-warning" id="batch_down" style="display:none;"><i class="fa fa-download"></i> 批量下载</a>&nbsp;
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#help"><i class="fa fa-info-circle"></i> 帮助</button>
</div>
</form>
</div>
<table id="listTable">
</table>
</div>
</div>
<script src="{$cdnpublic}layer/3.5.1/layer.js"></script>
<script src="{$cdnpublic}bootstrap-table/1.19.1/bootstrap-table.min.js"></script>
<script src="{$cdnpublic}bootstrap-table/1.19.1/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script>
<script src="/static/js/custom.js"></script>
<script>
function download_version(name, version, status){
if(status == true){
var confirm = layer.confirm('是否确定重新下载'+version+'版本插件包?', {
btn: ['确定','取消']
}, function(){
download_plugin(name, version)
}, function(){
layer.close(confirm)
});
}else{
download_plugin(name, version)
}
}
function download_plugin(name, version){
var ii = layer.msg('正在下载,请稍候...', {icon: 16, shade:0.1, time: 0});
$.ajax({
type : 'POST',
url : '/admin/download_plugin',
data: { name:name, version:version, os:'en'},
dataType : 'json',
success : function(data) {
layer.close(ii)
if(data.code == 0){
layer.alert(data.msg, {icon:1}, function(){layer.closeAll();searchSubmit();});
}else{
layer.alert(data.msg, {icon:2});
}
},
error:function(data){
layer.close(ii)
layer.msg('服务器错误', {icon:2});
}
});
}
function refresh_plugins(){
var confirm = layer.confirm('是否确定从宝塔官方获取最新插件列表?', {
btn: ['确定','取消']
}, function(){
layer.close(confirm)
var ii = layer.msg('正在获取插件列表,请稍候...', {icon: 16, shade:0.1, time: 0});
$.ajax({
type : 'GET',
url : '/admin/refresh_plugins?os=en',
dataType : 'json',
success : function(data) {
layer.close(ii)
if(data.code == 0){
layer.alert(data.msg, {icon:1}, function(){layer.closeAll();searchSubmit();});
}else{
layer.alert(data.msg, {icon:2});
}
},
error:function(data){
layer.close(ii)
layer.msg('服务器错误', {icon:2});
}
});
}, function(){
layer.close(confirm)
});
}
function download_plugins(){
var confirm = layer.confirm('批量下载当前分类下未下载的插件包', {
btn: ['确定','取消']
}, function(){
layer.close(confirm)
$.downloadCount = 0;
$.preDownloadCount = $.preDownload.length;
download_item();
}, function(){
layer.close(confirm)
});
}
function download_item(){
if($.preDownload.length == 0){
layer.alert('成功下载'+$.downloadCount+'个插件包!', {icon:1}, function(){layer.closeAll();searchSubmit();});
return;
}
$.downloadCount++;
var plugin = $.preDownload[0];
var ii = layer.msg('['+$.downloadCount+'/'+$.preDownloadCount+']正在下载'+plugin.name+'-'+plugin.version, {icon: 16, shade:0.1, time: 0});
$.ajax({
type : 'POST',
url : '/admin/download_plugin',
data: { name:plugin.name, version:plugin.version, os:'en'},
dataType : 'json',
success : function(data) {
layer.close(ii)
if(data.code == 0){
$.preDownload.shift();
download_item();
}else{
layer.alert(data.msg, {icon:2});
}
},
error:function(data){
layer.close(ii)
layer.msg('服务器错误', {icon:2});
}
});
}
function searchByType(type){
$("input[name=keyword]").val('');
$("select[name=type]").val(type);
searchSubmit()
}
$(document).ready(function(){
updateToolbar();
const defaultPageSize = 20;
$("#listTable").bootstrapTable({
url: '/admin/plugins_data?os=en',
pageNumber: 1,
pageSize: 15,
sidePagination: 'client',
classes: 'table table-striped table-hover table-bottom-border',
columns: [
{
field: 'name',
title: '软件标识',
formatter: function(value, row, index) {
return '<b>'+value+'</b>';
}
},
{
field: 'title',
title: '软件名称'
},
{
field: 'type',
title: '软件分类',
formatter: function(value, row, index) {
return '<a href="javascript:searchByType('+value+')" title="查看该分类下的插件">'+row.typename+'</a>';
}
},
{
field: 'desc',
title: '说明',
},
{
field: 'price',
title: '价格',
formatter: function(value, row, index) {
return value > 0 ? '<span style="color:#fc6d26">¥'+value+'</span>' : '免费';
}
},
{
field: 'author',
title: '开发商'
},
{
field: 'versions',
title: '版本与状态',
formatter: function(value, row, index) {
var html = '';
if(row.type == 5 || row.name == 'mail_sys' || row.name == 'dns_manager'){
html += '<a href="javascript:" class="btn btn-xs btn-success" disabled>无需下载</a>';
}else{
$.each(value, function(index,item){
if(item.status)
html += '<a href="javascript:download_version(\''+row.name+'\',\''+item.version+'\','+item.status+')" class="btn btn-xs btn-success">'+item.version+'</a>&nbsp;';
else
html += '<a href="javascript:download_version(\''+row.name+'\',\''+item.version+'\','+item.status+')" class="btn btn-xs btn-danger">'+item.version+'</a>&nbsp;';
})
}
return html
}
},
],
onLoadSuccess: function(data){
$.preDownload = [];
var type = $("select[name=type] option:selected").text();
if(type != '全部插件' && type != '运行环境' && type != '第三方应用'){
$("#batch_down").show();
if(data.length > 0){
$.each(data, function(index, plugin){
if(plugin.versions.length > 0 && plugin.name!='mail_sys' && plugin.name!='dns_manager'){
$.each(plugin.versions, function(index, version){
if(!version.status){
$.preDownload.push({name:plugin.name, version:version.version})
}
});
}
});
}
}else{
$("#batch_down").hide();
}
}
})
})
</script>
{/block}

View File

@ -0,0 +1,276 @@
{extend name="admin/layout" /}
{block name="title"}插件列表{/block}
{block name="main"}
<style>
td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:340px;}
.bt-ico-ask {
border: 1px solid #fb7d00;
border-radius: 8px;
color: #fb7d00;
cursor: help;
display: inline-block;
font-family: arial;
font-size: 11px;
font-style: normal;
height: 16px;
line-height: 16px;
margin-left: 5px;
text-align: center;
width: 16px
}
.bt-ico-ask:hover {
background-color: #fb7d00;
color: #fff
}
</style>
<div class="modal fade" id="help" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">帮助</h4>
</div>
<div class="modal-body">
<p>“版本与状态”一列中,红色的按钮代表本地不存在该版本插件包,需要点击下载;绿色的按钮代表已存在。</p>
<p>官方插件包本地存储路径是/data/win/plugins/package/软件标识-版本号.zip第三方插件包路径是/data/plugins/other/other/,对于部分包含二次验证的插件可以自行修改。</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 center-block" style="float: none;">
<div id="searchToolbar">
<form onsubmit="return searchSubmit()" method="GET" class="form-inline">
<div class="form-group">
<label>搜索</label>
<input type="text" class="form-control" name="keyword" placeholder="应用名称">
</div>
<div class="form-group">
<select name="type" class="form-control"><option value="0">全部插件</option>
{foreach $typelist as $k=>$v}<option value="{$k}">{$v}</option>{/foreach} </select>
</div>
<div class="form-group">
<button class="btn btn-primary" type="submit"><i class="fa fa-search"></i> 搜索</button>&nbsp;
<a href="javascript:searchClear()" class="btn btn-default"><i class="fa fa-repeat"></i> 重置</a>&nbsp;
<a href="javascript:refresh_plugins()" class="btn btn-success"><i class="fa fa-refresh"></i> 刷新列表</a>&nbsp;
<a href="javascript:download_plugins()" class="btn btn-warning" id="batch_down" style="display:none;"><i class="fa fa-download"></i> 批量下载</a>&nbsp;
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#help"><i class="fa fa-info-circle"></i> 帮助</button>
</div>
</form>
</div>
<table id="listTable">
</table>
</div>
</div>
<script src="{$cdnpublic}layer/3.5.1/layer.js"></script>
<script src="{$cdnpublic}bootstrap-table/1.19.1/bootstrap-table.min.js"></script>
<script src="{$cdnpublic}bootstrap-table/1.19.1/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script>
<script src="/static/js/custom.js"></script>
<script>
function download_version(name, version, status){
if(status == true){
var confirm = layer.confirm('是否确定重新下载'+version+'版本插件包?', {
btn: ['确定','取消']
}, function(){
download_plugin(name, version)
}, function(){
layer.close(confirm)
});
}else{
download_plugin(name, version)
}
}
function download_plugin(name, version){
var ii = layer.msg('正在下载,请稍候...', {icon: 16, shade:0.1, time: 0});
$.ajax({
type : 'POST',
url : '/admin/download_plugin',
data: { name:name, version:version, os:'Windows'},
dataType : 'json',
success : function(data) {
layer.close(ii)
if(data.code == 0){
layer.alert(data.msg, {icon:1}, function(){layer.closeAll();searchSubmit();});
}else{
layer.alert(data.msg, {icon:2});
}
},
error:function(data){
layer.close(ii)
layer.msg('服务器错误', {icon:2});
}
});
}
function refresh_plugins(){
var confirm = layer.confirm('是否确定从宝塔官方获取最新插件列表?', {
btn: ['确定','取消']
}, function(){
layer.close(confirm)
var ii = layer.msg('正在获取插件列表,请稍候...', {icon: 16, shade:0.1, time: 0});
$.ajax({
type : 'GET',
url : '/admin/refresh_plugins?os=Windows',
dataType : 'json',
success : function(data) {
layer.close(ii)
if(data.code == 0){
layer.alert(data.msg, {icon:1}, function(){layer.closeAll();searchSubmit();});
}else{
layer.alert(data.msg, {icon:2});
}
},
error:function(data){
layer.close(ii)
layer.msg('服务器错误', {icon:2});
}
});
}, function(){
layer.close(confirm)
});
}
function download_plugins(){
var confirm = layer.confirm('批量下载当前分类下未下载的插件包', {
btn: ['确定','取消']
}, function(){
layer.close(confirm)
$.downloadCount = 0;
$.preDownloadCount = $.preDownload.length;
download_item();
}, function(){
layer.close(confirm)
});
}
function download_item(){
if($.preDownload.length == 0){
layer.alert('成功下载'+$.downloadCount+'个插件包!', {icon:1}, function(){layer.closeAll();searchSubmit();});
return;
}
$.downloadCount++;
var plugin = $.preDownload[0];
var ii = layer.msg('['+$.downloadCount+'/'+$.preDownloadCount+']正在下载'+plugin.name+'-'+plugin.version, {icon: 16, shade:0.1, time: 0});
$.ajax({
type : 'POST',
url : '/admin/download_plugin',
data: { name:plugin.name, version:plugin.version, os:'Windows'},
dataType : 'json',
success : function(data) {
layer.close(ii)
if(data.code == 0){
$.preDownload.shift();
download_item();
}else{
layer.alert(data.msg, {icon:2});
}
},
error:function(data){
layer.close(ii)
layer.msg('服务器错误', {icon:2});
}
});
}
function searchByType(type){
$("input[name=keyword]").val('');
$("select[name=type]").val(type);
searchSubmit()
}
$(document).ready(function(){
updateToolbar();
const defaultPageSize = 20;
$("#listTable").bootstrapTable({
url: '/admin/plugins_data?os=Windows',
pageNumber: 1,
pageSize: 15,
sidePagination: 'client',
classes: 'table table-striped table-hover table-bottom-border',
columns: [
{
field: 'name',
title: '软件标识',
formatter: function(value, row, index) {
return '<b>'+value+'</b>';
}
},
{
field: 'title',
title: '软件名称'
},
{
field: 'type',
title: '软件分类',
formatter: function(value, row, index) {
return '<a href="javascript:searchByType('+value+')" title="查看该分类下的插件">'+row.typename+'</a>';
}
},
{
field: 'desc',
title: '说明',
},
{
field: 'price',
title: '价格',
formatter: function(value, row, index) {
return value > 0 ? '<span style="color:#fc6d26">¥'+value+'</span>' : '免费';
}
},
{
field: 'author',
title: '开发商'
},
{
field: 'versions',
title: '版本与状态',
formatter: function(value, row, index) {
var html = '';
if(row.type == 5){
html += '<a href="javascript:" class="btn btn-xs btn-success" disabled>无需下载</a>';
}else{
$.each(value, function(index,item){
if(item.status)
html += '<a href="javascript:download_version(\''+row.name+'\',\''+item.version+'\','+item.status+')" class="btn btn-xs btn-success">'+item.version+'</a>&nbsp;';
else
html += '<a href="javascript:download_version(\''+row.name+'\',\''+item.version+'\','+item.status+')" class="btn btn-xs btn-danger">'+item.version+'</a>&nbsp;';
})
}
return html
}
},
],
onLoadSuccess: function(data){
$.preDownload = [];
var type = $("select[name=type] option:selected").text();
if(type != '全部插件' && type != '运行环境' && type != '第三方应用'){
$("#batch_down").show();
if(data.length > 0){
$.each(data, function(index, plugin){
if(plugin.versions.length > 0){
$.each(plugin.versions, function(index, version){
if(!version.status){
$.preDownload.push({name:plugin.name, version:version.version})
}
});
}
});
}
}else{
$("#batch_down").hide();
}
}
})
})
</script>
{/block}

View File

@ -1,67 +1,67 @@
{extend name="admin/layout" /}
{block name="title"}使用记录{/block}
{block name="main"}
<style>
</style>
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 col-md-10 center-block" style="float: none;">
<div id="searchToolbar">
<form onsubmit="return searchSubmit()" method="GET" class="form-inline">
<div class="form-group">
<label>搜索</label>
<input type="text" class="form-control" name="ip" placeholder="服务器IP">
</div>
<div class="form-group">
<button class="btn btn-primary" type="submit"><i class="fa fa-search"></i>搜索</button>&nbsp;
<a href="javascript:searchClear()" class="btn btn-default"><i class="fa fa-repeat"></i>重置</a>&nbsp;
</div>
</form>
</div>
<table id="listTable">
</table>
</div>
</div>
<script src="//cdn.staticfile.org/layer/3.5.1/layer.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/bootstrap-table.min.js"></script>
<script src="//cdn.staticfile.org/bootstrap-table/1.20.2/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script>
<script src="/static/js/custom.js"></script>
<script>
$(document).ready(function(){
updateToolbar();
const defaultPageSize = 15;
const pageNumber = typeof window.$_GET['pageNumber'] != 'undefined' ? parseInt(window.$_GET['pageNumber']) : 1;
const pageSize = typeof window.$_GET['pageSize'] != 'undefined' ? parseInt(window.$_GET['pageSize']) : defaultPageSize;
$("#listTable").bootstrapTable({
url: '/admin/record_data',
pageNumber: pageNumber,
pageSize: pageSize,
classes: 'table table-striped table-hover table-bottom-border',
columns: [
{
field: 'id',
title: 'ID',
formatter: function(value, row, index) {
return '<b>'+value+'</b>';
}
},
{
field: 'ip',
title: '服务器IP'
},
{
field: 'addtime',
title: '首次安装时间',
},
{
field: 'usetime',
title: '最后使用时间'
},
],
})
})
</script>
{extend name="admin/layout" /}
{block name="title"}使用记录{/block}
{block name="main"}
<style>
</style>
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 col-md-10 center-block" style="float: none;">
<div id="searchToolbar">
<form onsubmit="return searchSubmit()" method="GET" class="form-inline">
<div class="form-group">
<label>搜索</label>
<input type="text" class="form-control" name="ip" placeholder="服务器IP">
</div>
<div class="form-group">
<button class="btn btn-primary" type="submit"><i class="fa fa-search"></i> 搜索</button>&nbsp;
<a href="javascript:searchClear()" class="btn btn-default"><i class="fa fa-repeat"></i> 重置</a>&nbsp;
</div>
</form>
</div>
<table id="listTable">
</table>
</div>
</div>
<script src="{$cdnpublic}layer/3.5.1/layer.js"></script>
<script src="{$cdnpublic}bootstrap-table/1.19.1/bootstrap-table.min.js"></script>
<script src="{$cdnpublic}bootstrap-table/1.19.1/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script>
<script src="/static/js/custom.js"></script>
<script>
$(document).ready(function(){
updateToolbar();
const defaultPageSize = 15;
const pageNumber = typeof window.$_GET['pageNumber'] != 'undefined' ? parseInt(window.$_GET['pageNumber']) : 1;
const pageSize = typeof window.$_GET['pageSize'] != 'undefined' ? parseInt(window.$_GET['pageSize']) : defaultPageSize;
$("#listTable").bootstrapTable({
url: '/admin/record_data',
pageNumber: pageNumber,
pageSize: pageSize,
classes: 'table table-striped table-hover table-bottom-border',
columns: [
{
field: 'id',
title: 'ID',
formatter: function(value, row, index) {
return '<b>'+value+'</b>';
}
},
{
field: 'ip',
title: '服务器IP'
},
{
field: 'addtime',
title: '首次安装时间',
},
{
field: 'usetime',
title: '最后使用时间'
},
],
})
})
</script>
{/block}

View File

@ -1,211 +1,571 @@
{extend name="admin/layout" /}
{block name="title"}系统设置{/block}
{block name="main"}
<div class="container" style="padding-top:70px;">
<div class="col-xs-12 col-sm-10 col-lg-8 center-block" style="float: none;">
{if $mod=='sys'}
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">系统基本设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<div class="form-group">
<label>是否开启白名单模式:</label><br/>
<select class="form-control" name="whitelist" default="{:config_get('whitelist')}"><option value="0">关闭</option><option value="1">开启</option></select>
<font color="green">开启白名单模式后,只有在<a href="/admin/list/type/white" target="_blank">白名单列表</a>中的服务器IP才能使用此云端</font>
</div>
<div class="form-group">
<label>安装脚本展示页面开关:</label>
<select class="form-control" name="download_page" default="{:config_get('download_page')}"><option value="0">关闭</option><option value="1">开启</option></select>
<font color="green">页面地址:<a href="/download" target="_blank">/download</a>,开启后可以公开访问,否则只能管理员访问</font>
</div>
<div class="form-group">
<label>宝塔面板最新版本号:</label>
<input type="text" name="new_version" value="{:config_get('new_version')}" class="form-control"/>
<font color="green">用于一键更新脚本获取最新版本号,以及检测更新接口。并确保已在/public/install/update/放置对应版本更新包</font>
</div>
<div class="form-group">
<label>宝塔面板最新版更新日志:</label>
<textarea class="form-control" name="update_msg" rows="5" placeholder="支持HTML代码">{:config_get('update_msg')}</textarea>
<font color="green">用于检测更新接口返回</font>
</div>
<div class="form-group">
<label>宝塔面板最新版更新日期:</label>
<input type="date" name="update_date" value="{:config_get('update_date')}" class="form-control"/>
<font color="green">用于检测更新接口返回</font>
</div>
<div class="form-group text-center">
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">宝塔面板接口设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<p>以下宝塔面板请使用官方最新脚本安装并绑定账号,用于获取最新插件列表及插件包</p>
<p><a href="/static/file/kaixin.zip">下载专用插件</a>,在面板【软件商店】->【第三方应用】,点击【导入插件】,导入该专用插件,<b>然后重启一次面板</b></p>
<div class="form-group">
<label>宝塔面板URL</label><br/>
<input type="text" name="bt_url" value="{:config_get('bt_url')}" class="form-control"/>
<font color="green">填写规则如:<u>http://192.168.1.1:8888</u> ,不要带其他后缀</font>
</div>
<div class="form-group">
<label>宝塔面板接口密钥:</label>
<input type="text" name="bt_key" value="{:config_get('bt_key')}" class="form-control"/>
</div>
<div class="form-group text-center">
<button type="button" class="btn btn-info btn-block" id="testbturl">测试连接</button>
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
{elseif $mod=='task'}
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">定时任务说明</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<div class="alert alert-info">使用以下命令可以从宝塔官方获取最新的插件列表并批量下载插件包(增量更新)。<br/>你也可以将此命令添加到crontab以使此云端的插件保持最新建议1天执行1次。</div>
<div class="alert alert-warning">上次运行时间:{$runtime|raw}</div>
<div class="list-group-item">php {:app()->getRootPath()}think updateall</div><br/>
</form>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">定时任务设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<div class="form-group">
<label>批量下载插件范围:</label><br/>
<select class="form-control" name="updateall_type" default="{:config_get('updateall_type')}"><option value="0">仅免费插件</option><option value="1">免费插件+专业版插件</option><option value="2">免费插件+专业版插件+企业版插件</option></select><font color="green">(批量下载不包含所有第三方插件,第三方插件需要去手动下载。)</font>
</div>
<div class="form-group text-center">
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
{elseif $mod=='account'}
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">管理账号设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveAccount(this)" method="post" class="form" role="form">
<div class="form-group">
<label>用户名:</label><br/>
<input type="text" name="username" value="{:config_get('admin_username')}" class="form-control" required/>
</div>
<div class="form-group">
<label>旧密码:</label>
<input type="password" name="oldpwd" value="" class="form-control" placeholder="请输入当前的管理员密码"/>
</div>
<div class="form-group">
<label>新密码:</label>
<input type="password" name="newpwd" value="" class="form-control" placeholder="不修改请留空"/>
</div>
<div class="form-group">
<label>重输密码:</label>
<input type="password" name="newpwd2" value="" class="form-control" placeholder="不修改请留空"/>
</div>
<div class="form-group text-center">
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
{/if}
<script src="//cdn.staticfile.org/layer/3.5.1/layer.js"></script>
<script>
$(document).ready(function(){
var items = $("select[default]");
for (i = 0; i < items.length; i++) {
$(items[i]).val($(items[i]).attr("default")||0);
}
$("#testbturl").click(function(){
var bt_url = $("input[name=bt_url]").val();
var bt_key = $("input[name=bt_key]").val();
if(bt_url == ''){
layer.alert('宝塔面板URL不能为空');return;
}
if(bt_url.indexOf('http://')==-1 && bt_url.indexOf('https://')==-1){
layer.alert('宝塔面板URL不正确');return;
}
if(bt_key == ''){
layer.alert('宝塔面板接口密钥不能为空');return;
}
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '/admin/testbturl',
data : {bt_url:bt_url, bt_key:bt_key},
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.msg(data.msg, {icon: 1, time:1000})
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
})
})
function saveSetting(obj){
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '/admin/set',
data : $(obj).serialize(),
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert('设置保存成功!', {
icon: 1,
closeBtn: false
}, function(){
window.location.reload()
});
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
return false;
}
function saveAccount(obj){
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '/admin/setaccount',
data : $(obj).serialize(),
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert('管理账号保存成功!请重新登录。', {
icon: 1,
closeBtn: false
}, function(){
window.location.reload()
});
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
return false;
}
</script>
{extend name="admin/layout" /}
{block name="title"}系统设置{/block}
{block name="main"}
<div class="container" style="padding-top:70px;">
{if $mod=='sys'}
<div class="col-sm-12 col-md-6 center-block">
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">系统基本设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<div class="form-group">
<label>是否开启白名单模式:</label><br/>
<select class="form-control" name="whitelist" default="{:config_get('whitelist')}"><option value="0">关闭</option><option value="1">开启</option></select>
<font color="green">开启白名单模式后,只有在<a href="/admin/list/type/white" target="_blank">白名单列表</a>中的服务器IP才能使用此云端</font>
</div>
<div class="form-group">
<label>安装脚本展示页面开关:</label>
<select class="form-control" name="download_page" default="{:config_get('download_page')}"><option value="0">关闭</option><option value="1">开启</option></select>
<font color="green">页面地址:<a href="/download" target="_blank">/download</a>,开启后可以公开访问,否则只能管理员访问</font>
</div>
<div class="form-group text-center">
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">Linux面板版本设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<div class="form-group">
<label>宝塔Linux面板最新版本号</label>
<input type="text" name="new_version" value="{:config_get('new_version')}" class="form-control"/>
<font color="green">用于一键更新脚本获取最新版本号,以及检测更新接口。并确保已在/public/install/update/放置对应版本更新包</font>
</div>
<div class="form-group">
<label>宝塔Linux面板更新日志</label>
<textarea class="form-control" name="update_msg" rows="5" placeholder="支持HTML代码">{:config_get('update_msg')}</textarea>
<font color="green">用于检测更新接口返回</font>
</div>
<div class="form-group">
<label>宝塔Linux面板更新日期</label>
<input type="date" name="update_date" value="{:config_get('update_date')}" class="form-control"/>
<font color="green">用于检测更新接口返回</font>
</div>
<div class="form-group text-center">
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">Windows面板版本设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<div class="form-group">
<label>宝塔Windows面板最新版本号</label>
<input type="text" name="new_version_win" value="{:config_get('new_version_win')}" class="form-control"/>
<font color="green">用于一键更新脚本获取最新版本号,以及检测更新接口。并确保已在/public/win/panel/放置对应版本更新包</font>
</div>
<div class="form-group">
<label>宝塔Windows面板更新日志</label>
<textarea class="form-control" name="update_msg_win" rows="5" placeholder="支持HTML代码">{:config_get('update_msg_win')}</textarea>
<font color="green">用于检测更新接口返回</font>
</div>
<div class="form-group">
<label>宝塔Windows面板更新日期</label>
<input type="date" name="update_date_win" value="{:config_get('update_date_win')}" class="form-control"/>
<font color="green">用于检测更新接口返回</font>
</div>
<div class="form-group text-center">
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
</div>
<div class="col-sm-12 col-md-6 center-block">
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">云监控版本设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<div class="form-group">
<label>宝塔云监控最新版本号:</label>
<input type="text" name="new_version_btm" value="{:config_get('new_version_btm')}" class="form-control"/>
<font color="green">用于一键更新脚本获取最新版本号,以及检测更新接口。并确保已在/public/install/src/放置对应版本更新包</font>
</div>
<div class="form-group">
<label>宝塔云监控更新日志:</label>
<textarea class="form-control" name="update_msg_btm" rows="3" placeholder="支持HTML代码">{:config_get('update_msg_btm')}</textarea>
</div>
<div class="form-group">
<label>宝塔云监控更新日期:</label>
<input type="date" name="update_date_btm" value="{:config_get('update_date_btm')}" class="form-control"/>
</div>
<div class="form-group text-center">
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">aaPanel面板版本设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<div class="form-group">
<label>aaPanel面板最新版本号</label>
<input type="text" name="new_version_en" value="{:config_get('new_version_en')}" class="form-control"/>
<font color="green">用于一键更新脚本获取最新版本号,以及检测更新接口。并确保已在/public/install/update/放置对应版本更新包</font>
</div>
<div class="form-group">
<label>aaPanel面板更新日志</label>
<textarea class="form-control" name="update_msg_en" rows="5" placeholder="支持HTML代码">{:config_get('update_msg_en')}</textarea>
<font color="green">用于检测更新接口返回</font>
</div>
<div class="form-group">
<label>aaPanel面板更新日期</label>
<input type="date" name="update_date_en" value="{:config_get('update_date_en')}" class="form-control"/>
<font color="green">用于检测更新接口返回</font>
</div>
<div class="form-group text-center">
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
</div>
{elseif $mod=='api'}
<div class="col-sm-12 col-md-6 center-block">
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">宝塔Linux面板接口设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<div class="form-group">
<label>对接方式:</label><br/>
<select class="form-control" name="bt_type" default="{:config_get('bt_type')}"><option value="0">对接宝塔面板接口</option><option value="1">对接其他第三方云端</option></select>
</div><hr/>
<div id="bt_type_0" style="{if config_get('bt_type')==1}display:none;{/if}">
<p>以下宝塔面板请使用官方最新脚本安装并绑定账号,用于获取插件列表及插件包</p>
<p><a href="/static/file/kaixin.zip">下载专用插件(Linux)</a>,在面板【软件商店】->【第三方应用】,点击【导入插件】,导入该专用插件。</p>
<div class="form-group">
<label>宝塔面板URL</label><br/>
<input type="text" name="bt_url" value="{:config_get('bt_url')}" class="form-control"/>
<font color="green">填写规则如:<u>http://192.168.1.1:8888</u> ,不要带其他后缀</font>
</div>
<div class="form-group">
<label>宝塔面板接口密钥:</label>
<input type="text" name="bt_key" value="{:config_get('bt_key')}" class="form-control"/>
</div>
</div>
<div id="bt_type_1" style="{if !config_get('bt_type')}display:none;{/if}">
<div class="form-group">
<label>第三方云端首页URL</label><br/>
<input type="text" name="bt_surl" value="{:config_get('bt_surl')}" class="form-control"/>
<font color="green">填写规则如:<u>http://www.example.com/</u> ,必须以/结尾</font>
</div>
</div>
<div class="form-group text-center">
<button type="button" class="btn btn-info btn-block" id="testbturl">测试连接</button>
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
</div>
<div class="col-sm-12 col-md-6 center-block">
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">宝塔Windows面板接口设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<div class="form-group">
<label>对接方式:</label><br/>
<select class="form-control" name="wbt_type" default="{:config_get('wbt_type')}"><option value="0">对接宝塔面板接口</option><option value="1">对接其他第三方云端</option></select>
</div><hr/>
<div id="wbt_type_0" style="{if config_get('wbt_type')==1}display:none;{/if}">
<p>以下宝塔面板请使用官方最新脚本安装并绑定账号,用于获取插件列表及插件包</p>
<p><a href="/static/file/win/kaixin.zip">下载专用插件(Win)</a>,上传到 /BtSoft/panel/plugin/ 解压,即可在软件商店已安装列表看到。</p>
<div class="form-group">
<label>宝塔面板URL</label><br/>
<input type="text" name="wbt_url" value="{:config_get('wbt_url')}" class="form-control"/>
<font color="green">填写规则如:<u>http://192.168.1.1:8888</u> ,不要带其他后缀</font>
</div>
<div class="form-group">
<label>宝塔面板接口密钥:</label>
<input type="text" name="wbt_key" value="{:config_get('wbt_key')}" class="form-control"/>
</div>
</div>
<div id="wbt_type_1" style="{if !config_get('wbt_type')}display:none;{/if}">
<div class="form-group">
<label>第三方云端首页URL</label><br/>
<input type="text" name="wbt_surl" value="{:config_get('wbt_surl')}" class="form-control"/>
<font color="green">填写规则如:<u>http://www.example.com/</u> ,必须以/结尾</font>
</div>
</div>
<div class="form-group text-center">
<button type="button" class="btn btn-info btn-block" id="testbturl2">测试连接</button>
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
</div>
<div class="col-sm-12 col-md-6 center-block">
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">aaPanel面板接口设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<div class="form-group">
<label>对接方式:</label><br/>
<select class="form-control" name="enbt_type" default="{:config_get('enbt_type')}"><option value="0">对接aaPanel面板接口</option><option value="1">对接其他第三方云端</option></select>
</div><hr/>
<div id="enbt_type_0" style="{if config_get('enbt_type')==1}display:none;{/if}">
<p>以下aaPanel面板请使用官方最新脚本安装并绑定账号用于获取插件列表及插件包</p>
<p><a href="/static/file/en/kaixin.zip">下载专用插件(aaPanel)</a>,在面板【软件商店】->【第三方应用】,点击【导入插件】,导入该专用插件。</p>
<div class="form-group">
<label>aaPanel面板URL</label><br/>
<input type="text" name="enbt_url" value="{:config_get('enbt_url')}" class="form-control"/>
<font color="green">填写规则如:<u>http://192.168.1.1:8888</u> ,不要带其他后缀</font>
</div>
<div class="form-group">
<label>aaPanel面板接口密钥</label>
<input type="text" name="enbt_key" value="{:config_get('enbt_key')}" class="form-control"/>
</div>
</div>
<div id="enbt_type_1" style="{if !config_get('enbt_type')}display:none;{/if}">
<div class="form-group">
<label>第三方云端首页URL</label><br/>
<input type="text" name="enbt_surl" value="{:config_get('enbt_surl')}" class="form-control"/>
<font color="green">填写规则如:<u>http://www.example.com/</u> ,必须以/结尾</font>
</div>
</div>
<div class="form-group text-center">
<button type="button" class="btn btn-info btn-block" id="testbturl3">测试连接</button>
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
</div>
<script>
$("select[name='bt_type']").change(function(){
if($(this).val() == 1){
$("#bt_type_0").hide();
$("#bt_type_1").show();
}else{
$("#bt_type_0").show();
$("#bt_type_1").hide();
}
});
$("select[name='wbt_type']").change(function(){
if($(this).val() == 1){
$("#wbt_type_0").hide();
$("#wbt_type_1").show();
}else{
$("#wbt_type_0").show();
$("#wbt_type_1").hide();
}
});
$("select[name='enbt_type']").change(function(){
if($(this).val() == 1){
$("#enbt_type_0").hide();
$("#enbt_type_1").show();
}else{
$("#enbt_type_0").show();
$("#enbt_type_1").hide();
}
});
</script>
{elseif $mod=='task'}
<div class="col-sm-12 col-md-6 center-block">
<div class="panel panel-success">
<div class="panel-heading"><h3 class="panel-title">自动更新插件说明</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<div class="alert alert-info">使用以下命令可以从宝塔官方获取最新的插件列表并批量下载插件包(增量更新)。<br/>你也可以将此命令添加到crontab以使此云端的插件保持最新建议1天执行1次。</div>
{if $is_user_www}<div class="alert alert-danger">计划任务执行用户必须选择www用户</div>{/if}
<div class="alert alert-warning">上次运行时间:{$runtime|raw}</div>
<div class="list-group-item">php {:app()->getRootPath()}think updateall</div><br/>
</form>
</div>
</div>
</div>
<div class="col-sm-12 col-md-6 center-block">
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">自动更新插件设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form" role="form">
<div class="form-group">
<label>Linux面板批量下载插件范围</label><br/>
<select class="form-control" name="updateall_type" default="{:config_get('updateall_type')}"><option value="0">仅免费插件</option><option value="1">免费插件+专业版插件</option><option value="2">免费插件+专业版插件+企业版插件</option></select><font color="green">(批量下载不包含所有第三方插件,第三方插件需要去手动下载。)</font>
</div>
<div class="form-group">
<label>Windows面板批量下载插件范围</label><br/>
<select class="form-control" name="updateall_type_win" default="{:config_get('updateall_type_win')}"><option value="0">仅免费插件</option><option value="1">免费插件+专业版插件</option><option value="2">免费插件+专业版插件+企业版插件</option></select><font color="green">(批量下载不包含所有第三方插件,第三方插件需要去手动下载。)</font>
</div>
<div class="form-group">
<label>aaPanel面板批量下载插件范围</label><br/>
<select class="form-control" name="updateall_type_en" default="{:config_get('updateall_type_en')}"><option value="0">仅免费插件</option><option value="1">免费插件+专业版插件</option><option value="2">免费插件+专业版插件+企业版插件</option></select><font color="green">(批量下载不包含所有第三方插件,第三方插件需要去手动下载。)</font>
</div>
<div class="form-group text-center">
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
</form>
</div>
</div>
</div>
{elseif $mod=='account'}
<div class="col-xs-12 col-sm-8 col-lg-6 center-block" style="float: none;">
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">管理账号设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveAccount(this)" method="post" class="form" role="form">
<div class="form-group">
<label>用户名:</label><br/>
<input type="text" name="username" value="{:config_get('admin_username')}" class="form-control" required/>
</div>
<div class="form-group">
<label>旧密码:</label>
<input type="password" name="oldpwd" value="" class="form-control" placeholder="请输入当前的管理员密码"/>
</div>
<div class="form-group">
<label>新密码:</label>
<input type="password" name="newpwd" value="" class="form-control" placeholder="不修改请留空"/>
</div>
<div class="form-group">
<label>重输密码:</label>
<input type="password" name="newpwd2" value="" class="form-control" placeholder="不修改请留空"/>
</div>
<div class="form-group text-center">
<input type="submit" name="submit" value="保存" class="btn btn-success btn-block"/>
</div>
<a href="javascript:cleancache()" class="btn btn-default btn-sm btn-block">清理缓存</a>
</form>
</div>
</div>
{elseif $mod=='tools'}
<div class="col-sm-12 col-md-10 col-lg-8 center-block" style="float: none;">
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">批量替换工具</h3></div>
<div class="panel-body">
<form onsubmit="return saveAccount(this)" method="post" class="form" role="form">
<div class="alert alert-info" style="word-break:break-all;">使用以下命令可以将bt安装包、更新包和脚本文件里面的<code>http://www.example.com</code>批量替换成当前网址<code>{:request()->root(true)}</code>,每次更新版本后只需要执行一次即可。</div>
<div class="list-group-item" style="word-break:break-all;">cd {:app()->getRootPath()}app/script && chmod +x convert.sh && ./convert.sh {:app()->getRootPath()} {:request()->root(true)}</div><br/>
</form>
</div>
</div>
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">清理旧版本插件工具</h3></div>
<div class="panel-body">
<form onsubmit="return saveAccount(this)" method="post" class="form" role="form">
<div class="alert alert-info" style="word-break:break-all;">使用以下命令可清理旧版本的插件文件,以释放空间占用。</div>
<div class="list-group-item" style="word-break:break-all;">php {:app()->getRootPath()}think clean</div><br/>
</form>
</div>
</div>
{/if}
<script src="{$cdnpublic}layer/3.5.1/layer.js"></script>
<script>
$(document).ready(function(){
var items = $("select[default]");
for (i = 0; i < items.length; i++) {
$(items[i]).val($(items[i]).attr("default")||0);
}
$("#testbturl").click(function(){
var bt_type = $("select[name=bt_type]").val();
if(bt_type == '1'){
var bt_surl = $("input[name=bt_surl]").val();
if(bt_surl == ''){
layer.alert('第三方云端URL不能为空');return;
}
if(bt_surl.indexOf('http://')==-1 && bt_surl.indexOf('https://')==-1){
layer.alert('第三方云端URL不正确');return;
}
var postdata = {bt_type:bt_type, bt_surl:bt_surl};
}else{
var bt_url = $("input[name=bt_url]").val();
var bt_key = $("input[name=bt_key]").val();
if(bt_url == ''){
layer.alert('宝塔面板URL不能为空');return;
}
if(bt_url.indexOf('http://')==-1 && bt_url.indexOf('https://')==-1){
layer.alert('宝塔面板URL不正确');return;
}
if(bt_key == ''){
layer.alert('宝塔面板接口密钥不能为空');return;
}
var postdata = {os:'linux', bt_type:bt_type, bt_url:bt_url, bt_key:bt_key};
}
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '/admin/testbturl',
data : postdata,
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.msg(data.msg, {icon: 1, time:1000})
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
})
$("#testbturl2").click(function(){
var wbt_type = $("select[name=wbt_type]").val();
if(wbt_type == '1'){
var wbt_surl = $("input[name=wbt_surl]").val();
if(wbt_surl == ''){
layer.alert('第三方云端URL不能为空');return;
}
if(wbt_surl.indexOf('http://')==-1 && wbt_surl.indexOf('https://')==-1){
layer.alert('第三方云端URL不正确');return;
}
var postdata = {bt_type:wbt_type, bt_surl:wbt_surl};
}else{
var wbt_url = $("input[name=wbt_url]").val();
var wbt_key = $("input[name=wbt_key]").val();
if(wbt_url == ''){
layer.alert('宝塔面板URL不能为空');return;
}
if(wbt_url.indexOf('http://')==-1 && wbt_url.indexOf('https://')==-1){
layer.alert('宝塔面板URL不正确');return;
}
if(wbt_key == ''){
layer.alert('宝塔面板接口密钥不能为空');return;
}
var postdata = {os:'win', bt_type:wbt_type, bt_url:wbt_url, bt_key:wbt_key};
}
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '/admin/testbturl',
data : postdata,
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.msg(data.msg, {icon: 1, time:1000})
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
})
$("#testbturl3").click(function(){
var enbt_type = $("select[name=enbt_type]").val();
if(enbt_type == '1'){
var enbt_surl = $("input[name=enbt_surl]").val();
if(enbt_surl == ''){
layer.alert('第三方云端URL不能为空');return;
}
if(enbt_surl.indexOf('http://')==-1 && enbt_surl.indexOf('https://')==-1){
layer.alert('第三方云端URL不正确');return;
}
var postdata = {bt_type:enbt_type, bt_surl:enbt_surl};
}else{
var enbt_url = $("input[name=enbt_url]").val();
var enbt_key = $("input[name=enbt_key]").val();
if(enbt_url == ''){
layer.alert('宝塔面板URL不能为空');return;
}
if(enbt_url.indexOf('http://')==-1 && enbt_url.indexOf('https://')==-1){
layer.alert('宝塔面板URL不正确');return;
}
if(enbt_key == ''){
layer.alert('宝塔面板接口密钥不能为空');return;
}
var postdata = {os:'en', bt_type:enbt_type, bt_url:enbt_url, bt_key:enbt_key};
}
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '/admin/testbturl',
data : postdata,
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.msg(data.msg, {icon: 1, time:1000})
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
})
})
function saveSetting(obj){
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '/admin/set',
data : $(obj).serialize(),
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert('设置保存成功!', {
icon: 1,
closeBtn: false
}, function(){
window.location.reload()
});
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
return false;
}
function saveAccount(obj){
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '/admin/setaccount',
data : $(obj).serialize(),
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert('管理账号保存成功!请重新登录。', {
icon: 1,
closeBtn: false
}, function(){
window.location.reload()
});
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
return false;
}
function cleancache(){
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'GET',
url : '/admin/cleancache',
dataType : 'json',
success : function(data) {
layer.close(ii);
layer.msg('清理缓存成功', {icon: 1});
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
}
</script>
{/block}

86
app/view/admin/ssl.html Normal file
View File

@ -0,0 +1,86 @@
{extend name="admin/layout" /}
{block name="title"}自签名SSL证书生成{/block}
{block name="main"}
<style>
.control-label[is-required]:before {
content: "*";
color: #f56c6c;
margin-right: 4px;
}
</style>
<div class="container" style="padding-top:70px;">
<div class="col-sm-12 col-md-10 col-lg-8 center-block" style="float: none;">
<div class="panel panel-primary">
<div class="panel-heading"><h3 class="panel-title">自签名SSL证书生成</h3></div>
<div class="panel-body">
{if $isca}
<div class="alert alert-warning" style="word-break:break-all;">下载CA证书并导入可解决浏览器不安全提醒。<br/>Windows<a href="/ssl/baota_root.pfx">baota_root.pfx</a>密码为空Mac/Linux<a href="/ssl/baota_root.crt">baota_root.crt</a></div>
<form onsubmit="return makeSSL(this)" method="post" class="form" role="form">
<div class="form-group">
<label is-required="true" class="control-label">域名列表:</label>
<textarea class="form-control" name="domain_list" rows="6" placeholder="每行一个域名/IP支持通配符" required></textarea>
</div>
<div class="form-group">
<label class="control-label">通用名称:</label>
<input type="text" name="common_name" value="" placeholder="留空则为域名列表第一个域名" class="form-control"/>
</div>
<div class="form-group">
<label is-required="true" class="control-label">有效天数:</label>
<input type="number" name="validity" value="3650" class="form-control" required/>
</div>
<div class="form-group text-center">
<input type="submit" name="submit" value="生成自签名证书" class="btn btn-success btn-block"/>
</div>
<div class="form-group row" id="result" style="display:none;">
<div class="col-md-6">
<label class="control-label">SSL证书</label>
<textarea class="form-control" name="ssl_cert" rows="5" onclick="copy(this)" title="点击复制"></textarea>
</div>
<div class="col-md-6">
<label class="control-label">SSL证书私钥</label>
<textarea class="form-control" name="ssl_key" rows="5" onclick="copy(this)" title="点击复制"></textarea>
</div>
</div>
</form>
{else}
<div class="alert alert-danger" role="alert">你还没有生成CA证书无法生成SSL证书</div>
<div class="alert alert-info" style="word-break:break-all;">执行以下命令生成自签名CA证书。然后可通过接口或当前页面生成SSL证书用于面板访问。</div>
<div class="list-group-item" style="word-break:break-all;">cd {:app()->getRootPath()}app/script && chmod +x cacert.sh && ./cacert.sh</div><br/>
{/if}
</div>
</div>
<script src="{$cdnpublic}layer/3.5.1/layer.js"></script>
<script>
function makeSSL(obj){
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '/admin/ssl',
data : $(obj).serialize(),
dataType : 'json',
success : function(data) {
layer.close(ii);
if(data.code == 0){
$("textarea[name='ssl_cert']").val(data.cert);
$("textarea[name='ssl_key']").val(data.key);
$("#result").show();
layer.msg('SSL证书生成成功', {icon:1, time:800});
}else{
layer.alert(data.msg, {icon: 2})
}
},
error:function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
return false;
}
function copy(obj){
if($(obj).val() == '') return;
$(obj).select();
document.execCommand("Copy");
layer.msg('复制成功', {icon:1, time:500});
}
</script>
{/block}

View File

@ -1,60 +1,60 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>温馨提示</title>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
<meta name="renderer" content="webkit"/>
<style type="text/css">
*{box-sizing:border-box;margin:0;padding:0;font-family:Lantinghei SC,Open Sans,Arial,Hiragino Sans GB,Microsoft YaHei,"微软雅黑",STHeiti,WenQuanYi Micro Hei,SimSun,sans-serif;-webkit-font-smoothing:antialiased}
body{padding:70px 0;background:#edf1f4;font-weight:400;font-size:1pc;-webkit-text-size-adjust:none;color:#333}
a{outline:0;color:#3498db;text-decoration:none;cursor:pointer}
.system-message{margin:20px 5%;padding:40px 20px;background:#fff;box-shadow:1px 1px 1px hsla(0,0%,39%,.1);text-align:center}
.system-message h1{margin:0;margin-bottom:9pt;color:#444;font-weight:400;font-size:40px}
.system-message .jump,.system-message .image{margin:20px 0;padding:0;padding:10px 0;font-weight:400}
.system-message .jump{font-size:14px}
.system-message .jump a{color:#333}
.system-message p{font-size:9pt;line-height:20px}
.system-message .btn{display:inline-block;margin-right:10px;width:138px;height:2pc;border:1px solid #44a0e8;border-radius:30px;color:#44a0e8;text-align:center;font-size:1pc;line-height:2pc;margin-bottom:5px;}
.success .btn{border-color:#69bf4e;color:#69bf4e}
.error .btn{border-color:#ff8992;color:#ff8992}
.info .btn{border-color:#3498db;color:#3498db}
.copyright p{width:100%;color:#919191;text-align:center;font-size:10px}
.system-message .btn-grey{border-color:#bbb;color:#bbb}
.clearfix:after{clear:both;display:block;visibility:hidden;height:0;content:"."}
@media (max-width:768px){body {padding:20px 0;}}
@media (max-width:480px){.system-message h1{font-size:30px;}}
</style>
</head>
<body>
<div class="system-message {$code}">
<div class="image">
<img src="/static/images/{$code}.svg" alt="" width="150" />
</div>
<h1>{$msg}</h1>
{if $url}
<p class="jump">
页面将在 <span id="wait">{$wait}</span> 秒后自动跳转
</p>
{/if}
<p class="clearfix">
<a href="javascript:history.go(-1);" class="btn btn-grey">返回上一页</a>
{if $url}
<a href="{$url}" class="btn btn-primary">立即跳转</a>
{/if}
</p>
</div>
<script type="text/javascript">
(function () {
var wait = document.getElementById('wait');
var interval = setInterval(function () {
var time = --wait.innerHTML;
if (time <= 0) {
location.href = "{$url}";
clearInterval(interval);
}
}, 1000);
})();
</script>
</body>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>温馨提示</title>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
<meta name="renderer" content="webkit"/>
<style type="text/css">
*{box-sizing:border-box;margin:0;padding:0;font-family:Lantinghei SC,Open Sans,Arial,Hiragino Sans GB,Microsoft YaHei,"微软雅黑",STHeiti,WenQuanYi Micro Hei,SimSun,sans-serif;-webkit-font-smoothing:antialiased}
body{padding:70px 0;background:#edf1f4;font-weight:400;font-size:1pc;-webkit-text-size-adjust:none;color:#333}
a{outline:0;color:#3498db;text-decoration:none;cursor:pointer}
.system-message{margin:20px 5%;padding:40px 20px;background:#fff;box-shadow:1px 1px 1px hsla(0,0%,39%,.1);text-align:center}
.system-message h1{margin:0;margin-bottom:9pt;color:#444;font-weight:400;font-size:40px}
.system-message .jump,.system-message .image{margin:20px 0;padding:0;padding:10px 0;font-weight:400}
.system-message .jump{font-size:14px}
.system-message .jump a{color:#333}
.system-message p{font-size:9pt;line-height:20px}
.system-message .btn{display:inline-block;margin-right:10px;width:138px;height:2pc;border:1px solid #44a0e8;border-radius:30px;color:#44a0e8;text-align:center;font-size:1pc;line-height:2pc;margin-bottom:5px;}
.success .btn{border-color:#69bf4e;color:#69bf4e}
.error .btn{border-color:#ff8992;color:#ff8992}
.info .btn{border-color:#3498db;color:#3498db}
.copyright p{width:100%;color:#919191;text-align:center;font-size:10px}
.system-message .btn-grey{border-color:#bbb;color:#bbb}
.clearfix:after{clear:both;display:block;visibility:hidden;height:0;content:"."}
@media (max-width:768px){body {padding:20px 0;}}
@media (max-width:480px){.system-message h1{font-size:30px;}}
</style>
</head>
<body>
<div class="system-message {$code}">
<div class="image">
<img src="/static/images/{$code}.svg" alt="" width="150" />
</div>
<h1>{$msg}</h1>
{if $url}
<p class="jump">
页面将在 <span id="wait">{$wait}</span> 秒后自动跳转
</p>
{/if}
<p class="clearfix">
<a href="javascript:history.go(-1);" class="btn btn-grey">返回上一页</a>
{if $url}
<a href="{$url}" class="btn btn-primary">立即跳转</a>
{/if}
</p>
</div>
<script type="text/javascript">
(function () {
var wait = document.getElementById('wait');
var interval = setInterval(function () {
var time = --wait.innerHTML;
if (time <= 0) {
location.href = "{$url}";
clearInterval(interval);
}
}, 1000);
})();
</script>
</body>
</html>

View File

@ -1,159 +1,306 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="renderer" content="webkit" />
<meta name="force-rendering" content="webkit" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>宝塔面板安装脚本</title>
<link rel="stylesheet" type="text/css" href="/static/css/sanren.css" />
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
<link rel="stylesheet" type="text/css" href="/static/css/download.css" />
</head>
<body>
<div class="down-main">
<div class="d1">
<div class="wrap">
<div class="i1t textcenter">
<h1 class="disflex flex_center flex_lrcenter textcenter">宝塔面板安装脚本<img class="ml10" src="/static/images/i1ico_03.png"></h1>
<div class="text20 mt_25 wap_mt15 textcenter cl8">
<p>2分钟装好面板一键管理服务器</p>
<p>集成LAMP/LNMP环境安装网站、FTP、数据库、文件管理、软件安装等功能</p>
</div>
<div class="mt_30 i1ta wap_mt15">
<a href="https://demo.bt.cn" target="_block" rel="noreferrer">查看演示</a>
<a href="javascript:;" id="goInstallLinux">
<img class="middle mr10" src="/static/images/i1aico_03.png">
立即免费安装
</a>
</div>
</div>
</div>
</div>
<div class="d2" id="instal-linux">
<div class="wrap">
<div class="wrap-title linux-title">
<div class="text">使用此云端的宝塔面板(版本:{:config_get('new_version')}</div>
</div>
<div class="desc">
使用 SSH 连接工具,如
<a class="link" href="https://www.putty.org/" target="_blank" rel="noreferrer">PUTTY</a>
连接到您的 Linux 服务器后,
<a class="link" href="https://www.bt.cn/bbs/thread-50002-1-1.html" target="_blank" rel="noreferrer">挂载磁盘</a>
,根据系统执行相应命令开始安装:
</div>
<div class="install-code">
<span class="osname">一键安装脚本</span>
<div class="code-cont">
<div class="command" title="点击复制一键安装脚本">wget -O install.sh {$siteurl}/install/install_6.0.sh && sh install.sh</div>
<span class="ico-copy" title="点击复制一键安装脚本" data-clipboard-text="wget -O install.sh {$siteurl}/install/install_6.0.sh && sh install.sh">复制</span>
</div>
</div>
<div class="install-code">
<span class="osname">一键更新脚本</span>
<div class="code-cont">
<div class="command" title="点击复制一键更新脚本">curl {$siteurl}/install/update6.sh|bash</div>
<span class="ico-copy" title="点击复制一键更新脚本" data-clipboard-text="curl {$siteurl}/install/update6.sh|bash">复制</span>
</div>
</div>
<div class="tips" style="color: orangered; font-weight: 700">
<p>注意必须为没装过其它环境如Apache/Nginx/php/MySQL的新系统,推荐使用centos 7.X的系统安装宝塔面板</p>
<p style="text-indent: 3em">推荐使用Chrome、火狐、edge浏览器国产浏览器请使用极速模式访问面板登录地址</p>
<p style="text-indent: 3em">如果使用过官方版或其他第三方云端的版本,使用一键更新脚本即可切换到此云端</p>
</div>
</div>
</div>
<div class="d4" id="instal-cloud">
<div class="wrap">
<div class="wrap-title">
<div class="text">更新日志</div>
</div>
<div class="desc">
<p>宝塔Linux面板更新到{:config_get('new_version')}</p>
</div>
</div>
</div>
<div class="animate-bg"></div>
</div>
<div class="foot">
<div class="wrap">
<div class="fb textcenter">
<div class="fb1 textcenter">
<a href="http://www.bt.cn/new/agreement_open.html" target="_blank" rel="noreferrer">《开源许可协议》</a>
<i></i>
<a href="http://www.bt.cn/new/agreement_user.html" target="_blank" rel="noreferrer">《用户协议》</a>
<i></i>
<a href="http://www.bt.cn/new/agreement_privacy.html" target="_blank" rel="noreferrer">《隐私声明》</a>
</div>
<div class="fb2 mt_15">
<p>
Copyright © 2022 宝塔面板安装脚本
</p>
</div>
</div>
</div>
</div>
<script src="//cdn.staticfile.org/jquery/3.6.0/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script src="//cdn.staticfile.org/layer/3.5.1/layer.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" src="//cdn.staticfile.org/clipboard.js/1.7.1/clipboard.min.js"></script>
<script type="text/javascript" src="/static/js/dx.js"></script>
<script>
$(function () {
var userId = '';
// 复制安装命令
var clipboard = new Clipboard('.ico-copy', {
text: function (element) {
return $(element).prev().text();
},
});
clipboard
.on('success', function (e) {
layer.msg(e.trigger.title + '成功', { icon: 1 });
})
.on('error', function (e) {
layer.msg('复制失败请手动选中文本Ctrl+c复制内容', { icon: 2 });
});
$('.install-code .command').click(function () {
$(this).next('.ico-copy').click();
});
$('#goInstallLinux').click(function () {
scrollTop('#instal-linux');
});
$('#goOnlineInstall').click(function () {
scrollTop('#online-instal');
});
$('#goInstallCloud').click(function () {
scrollTop('#instal-cloud');
});
function GetRequest() {
var url = location.search;
//获取url中"?"符后的字串
var theRequest = new Object();
if (url.indexOf('?') != -1) {
var str = url.substr(1);
}
return str;
}
if (GetRequest() == 'bt') {
scrollTop('#instal-linux');
}
// 滚动到指定位置
function scrollTop(el) {
var headHeight = 0;
$('html, body').animate({ scrollTop: $(el).offset().top - headHeight }, { duration: 200, easing: 'swing' });
}
});
</script>
</body>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="renderer" content="webkit" />
<meta name="force-rendering" content="webkit" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>宝塔面板安装脚本</title>
<link rel="stylesheet" type="text/css" href="/static/css/sanren.css" />
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
<link rel="stylesheet" type="text/css" href="/static/css/download.css" />
</head>
<body>
<div class="down-main">
<div class="d1">
<div class="wrap">
<div class="i1t textcenter">
<h1 class="disflex flex_center flex_lrcenter textcenter">宝塔面板安装脚本<img class="ml10" src="/static/images/i1ico_03.png"></h1>
<div class="text20 mt_25 wap_mt15 textcenter cl8">
<p>2分钟装好面板一键管理服务器</p>
<p>集成LAMP/LNMP环境安装网站、FTP、数据库、文件管理、软件安装等功能</p>
</div>
<div class="disflex flex_lrcenter mt_50 install-list">
<div class="install-box linux">
<div class="img">
<img src="/static/images/prd_1_03.png">
</div>
<div class="cont">
<div class="top">
<div class="title">Linux面板 {:config_get('new_version')}</div>
<div class="desc">
支持Centos、Ubuntu、Deepin、Debian、Fedora等Linux系统。
<a class="link" href="https://demo.bt.cn/login" target="_blank" style="margin-left: 5px; font-weight: 700" rel="noreferrer">查看演示</a>
</div>
<div class="mark">
<span>2分钟装好</span>
<span>阿里云推荐</span>
<span>腾讯云推荐</span>
</div>
</div>
<div class="bottom">
<a class="btn" href="javascript:;" id="goInstallLinux">查看安装脚本</a>
</div>
</div>
</div>
{if config_get('new_version_win')}<div class="install-box windows">
<div class="img">
<img src="/static/images/prd_2_03.png">
</div>
<div class="cont">
<div class="top">
<div class="title">Windows面板 {:config_get('new_version_win')}</div>
<div class="desc">支持Windows Server 2008 R2/2012/2016/2019/202264位系统</div>
<div class="mark">
<span>操作简单</span>
<span>使用方便</span>
</div>
</div>
<div class="bottom">
<a class="btn" href="javascript:;" id="goInstallWindows">查看安装方法</a>
</div>
</div>
</div>{/if}
</div>
{if config_get('new_version_btm') || config_get('new_version_en')}<div class="disflex flex_lrcenter mt_30 install-list">
{if config_get('new_version_win')}<div class="install-box monitor">
<div class="img">
<img src="/static/images/bt_monitor.png" style="height: 96px;"/>
</div>
<div class="cont">
<div class="top">
<div class="title">云安全监控</div>
<div class="desc">多机跨平台安全管理和监控</div>
</div>
<div class="bottom">
<a class="btn" href="javascript:;" id="goInstallMonitor">查看安装脚本</a>
</div>
</div>
</div>{/if}
{if config_get('new_version_en')}<div class="install-box monitor">
<div class="img">
<img src="/static/images/aapanel.png" style="height: 96px;"/>
</div>
<div class="cont">
<div class="top">
<div class="title">aaPanel {:config_get('new_version_en')}</div>
<div class="desc">宝塔面板国际版</div>
</div>
<div class="bottom">
<a class="btn" href="javascript:;" id="goInstallAaPanel">查看安装脚本</a>
</div>
</div>
</div>{/if}
</div>{/if}
</div>
</div>
</div>
<div class="d2" id="instal-linux">
<div class="wrap">
<div class="wrap-title linux-title">
<div class="text">Linux面板{:config_get('new_version')}安装脚本</div>
</div>
<div class="desc">
使用 SSH 连接工具,如
<a class="link" href="https://www.putty.org/" target="_blank" rel="noreferrer">PUTTY</a>
连接到您的 Linux 服务器后,
<a class="link" href="https://www.bt.cn/bbs/thread-50002-1-1.html" target="_blank" rel="noreferrer">挂载磁盘</a>
,根据系统执行相应命令开始安装:
</div>
<div class="install-code">
<span class="osname">Centos安装脚本</span>
<div class="code-cont">
<div class="command" title="点击复制Centos安装脚本">yum install -y wget && wget -O install.sh {$siteurl}/install/install_panel.sh && sh install.sh</div>
<span class="ico-copy" title="点击复制Centos安装脚本" data-clipboard-text="yum install -y wget && wget -O install.sh {$siteurl}/install/install_panel.sh && sh install.sh">复制</span>
</div>
</div>
<div class="install-code">
<span class="osname">Ubuntu/Debian安装脚本</span>
<div class="code-cont">
<div class="command" title="点击复制Ubuntu/Debian安装脚本">wget -O install.sh {$siteurl}/install/install_panel.sh && bash install.sh</div>
<span class="ico-copy" title="点击复制Ubuntu/Debian安装脚本" data-clipboard-text="wget -O install.sh {$siteurl}/install/install_panel.sh && bash install.sh">复制</span>
</div>
</div>
<div class="install-code">
<span class="osname">一键更新脚本</span>
<div class="code-cont">
<div class="command" title="点击复制一键更新脚本">curl {$siteurl}/install/update6.sh|bash</div>
<span class="ico-copy" title="点击复制一键更新脚本" data-clipboard-text="curl {$siteurl}/install/update6.sh|bash">复制</span>
</div>
</div>
<div class="tips" style="color: orangered; font-weight: 700">
<p>注意必须为没装过其它环境如Apache/Nginx/php/MySQL的新系统,推荐使用centos 7.X的系统安装宝塔面板</p>
<p style="text-indent: 3em">推荐使用Chrome、火狐、edge浏览器国产浏览器请使用极速模式访问面板登录地址</p>
<p style="text-indent: 3em">如果使用过官方版或其他第三方云端的版本,使用一键更新脚本即可切换到此云端</p>
</div>
</div>
</div>
{if config_get('new_version_win')}
<div class="d4" id="instal-windows" style="background-color: #edf6ef;">
<div class="wrap">
<div class="wrap-title">
<div class="text">Windows面板{:config_get('new_version_win')}安装方法</div>
</div>
<div class="desc">
<p>1、使用<a class="link" href="https://download.bt.cn/win/panel/go/BtSoft.exe" target="_blank" rel="noreferrer">官方安装程序</a>进行安装,安装完先不要进入面板。</p>
<p>2、在命令提示符cmd输入以下一键更新命令然后重启面板。</p>
<p>3、打开面板后绑定账号时输入任意账号密码即可。</p>
</div>
<div class="install-code">
<div class="code-cont">
<div class="command" title="点击复制一键更新命令">wget {$siteurl}/win/install/panel_update.py -O C:/update.py && &quot;C:\Program Files\python\python.exe&quot; C:/update.py {:config_get('new_version_win')}</div>
<span class="ico-copy" title="点击复制一键更新命令" data-clipboard-text="wget {$siteurl}/win/install/panel_update.py -O C:/update.py && &quot;C:\Program Files\python\python.exe&quot; C:/update.py {:config_get('new_version_win')}">复制</span>
</div>
</div>
<div class="tips" style="color: orangered; font-weight: 700">
<p>注意支持Windows Server 2012/Windows 8及以上系统且未安装其它环境</p>
</div>
</div>
</div>{/if}
{if config_get('new_version_btm')}
<div class="d4" id="instal-monitor">
<div class="wrap">
<div class="wrap-title">
<div class="text" style="margin-right: 12px;">云安全监控{:config_get('new_version_btm')}安装脚本</div>
</div>
<div class="desc">
使用 SSH 连接工具,如
<a class="link" href="https://www.putty.org/" target="_blank" rel="noreferrer">PUTTY</a>
连接到您的 Linux 服务器后,根据系统执行相应命令开始安装:
</div>
<div class="install-code">
<span class="osname">云安全监控安装脚本</span>
<div class="code-cont">
<div class="command" title="点击复制安装脚本">curl -sS {$siteurl}/install/install_btmonitor.sh -o /tmp/install_btmonitor.sh && bash /tmp/install_btmonitor.sh</div>
<span class="ico-copy" title="点击复制安装脚本" data-clipboard-text="curl -sS {$siteurl}/install/install_btmonitor.sh -o /tmp/install_btmonitor.sh && bash /tmp/install_btmonitor.sh">复制</span>
</div>
</div>
<div class="install-code">
<span class="osname">云安全监控更新脚本</span>
<div class="code-cont">
<div class="command" title="点击复制更新脚本">curl {$siteurl}/install/update_btmonitor.sh|bash</div>
<span class="ico-copy" title="点击复制更新脚本" data-clipboard-text="curl {$siteurl}/install/update_btmonitor.sh|bash">复制</span>
</div>
</div>
<div class="tips" style="color: orangered; font-weight: 700">
<p>注意安装完成后推荐使用Chrome、火狐、edge浏览器国产浏览器极速模式访问登录系统</p>
</div>
</div>
</div>{/if}
{if config_get('new_version_en')}
<div class="d4" id="instal-aapanel" style="background-color: #edf6ef;">
<div class="wrap">
<div class="wrap-title">
<div class="text">aaPanel {:config_get('new_version_en')} install script</div>
</div>
<div class="desc">
<p>It is recommended that you use Ubuntu22.04 to install aaPanel</p>
</div>
<div class="install-code">
<span class="osname">Instarll script</span>
<div class="code-cont">
<div class="command" title="Copy successfully, please paste it to the server installation">URL={$siteurl}/install/install_7.0_en.sh && if [ -f /usr/bin/curl ];then curl -ksSO $URL ;else wget -O install_7.0_en.sh $URL;fi;bash install_7.0_en.sh</div>
<span class="ico-copy" title="Copy successfully, please paste it to the server installation" data-clipboard-text="URL={$siteurl}/install/install_7.0_en.sh && if [ -f /usr/bin/curl ];then curl -ksSO $URL ;else wget -O install_7.0_en.sh $URL;fi;bash install_7.0_en.sh">复制</span>
</div>
</div>
<div class="install-code">
<span class="osname">Upgrade script</span>
<div class="code-cont">
<div class="command" title="Copy successfully, please paste it to the server installation">curl {$siteurl}/install/update_7.x_en.sh|bash</div>
<span class="ico-copy" title="Copy successfully, please paste it to the server installation" data-clipboard-text="curl {$siteurl}/install/update_7.x_en.sh|bash">复制</span>
</div>
</div>
</div>
</div>{/if}
<div class="animate-bg"></div>
</div>
<div class="foot">
<div class="wrap">
<div class="fb textcenter">
<div class="fb1 textcenter">
<a href="http://www.bt.cn/new/agreement_open.html" target="_blank" rel="noreferrer">《开源许可协议》</a>
<i></i>
<a href="http://www.bt.cn/new/agreement_user.html" target="_blank" rel="noreferrer">《用户协议》</a>
<i></i>
<a href="http://www.bt.cn/new/agreement_privacy.html" target="_blank" rel="noreferrer">《隐私声明》</a>
</div>
<div class="fb2 mt_15">
<p>
Copyright © {:date('Y')} 宝塔面板安装脚本
</p>
</div>
</div>
</div>
</div>
<script src="{$cdnpublic}jquery/3.6.0/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script src="{$cdnpublic}layer/3.5.1/layer.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" src="{$cdnpublic}clipboard.js/1.7.1/clipboard.min.js"></script>
<script type="text/javascript" src="/static/js/dx.js"></script>
<script>
$(function () {
var userId = '';
// 复制安装命令
var clipboard = new Clipboard('.ico-copy', {
text: function (element) {
return $(element).prev().text();
},
});
clipboard
.on('success', function (e) {
layer.msg(e.trigger.title + '成功', { icon: 1 });
})
.on('error', function (e) {
layer.msg('复制失败请手动选中文本Ctrl+c复制内容', { icon: 2 });
});
$('.install-code .command').click(function () {
$(this).next('.ico-copy').click();
});
$('#goInstallLinux').click(function () {
scrollTop('#instal-linux');
});
$('#goInstallWindows').click(function () {
scrollTop('#instal-windows');
});
$('#goInstallCloud').click(function () {
scrollTop('#instal-cloud');
});
$('#goInstallMonitor').click(function () {
scrollTop('#instal-monitor');
});
$('#goInstallAaPanel').click(function () {
scrollTop('#instal-aapanel');
});
function GetRequest() {
var url = location.search;
//获取url中"?"符后的字串
var theRequest = new Object();
if (url.indexOf('?') != -1) {
var str = url.substr(1);
}
return str;
}
if (GetRequest() == 'bt') {
scrollTop('#instal-linux');
}
// 滚动到指定位置
function scrollTop(el) {
var headHeight = 0;
$('html, body').animate({ scrollTop: $(el).offset().top - headHeight }, { duration: 200, easing: 'swing' });
}
});
</script>
</body>
</html>

268
app/view/install/index.html Normal file
View File

@ -0,0 +1,268 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>宝塔第三方云端 - 安装程序</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="renderer" content="webkit">
<style>
body {
background: #f1f6fd;
margin: 0;
padding: 0;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body, input, button {
font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, 'Microsoft Yahei', Arial, sans-serif;
font-size: 14px;
color: #7E96B3;
}
.container {
max-width: 480px;
margin: 0 auto;
padding: 20px;
text-align: center;
}
a {
color: #4e73df;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
h1 {
margin-top: 0;
margin-bottom: 10px;
}
h2 {
font-size: 28px;
font-weight: normal;
color: #3C5675;
margin-bottom: 0;
margin-top: 0;
}
form {
margin-top: 40px;
}
.form-group {
margin-bottom: 20px;
}
.form-group .form-field:first-child input {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.form-group .form-field:last-child input {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
.form-field input {
background: #fff;
margin: 0 0 2px;
border: 2px solid transparent;
transition: background 0.2s, border-color 0.2s, color 0.2s;
width: 100%;
padding: 15px 15px 15px 180px;
box-sizing: border-box;
}
.form-field input:focus {
border-color: #4e73df;
background: #fff;
color: #444;
outline: none;
}
.form-field label {
float: left;
width: 160px;
text-align: right;
margin-right: -160px;
position: relative;
margin-top: 15px;
font-size: 14px;
pointer-events: none;
opacity: 0.7;
}
button, .btn {
background: #3C5675;
color: #fff;
border: 0;
font-weight: bold;
border-radius: 4px;
cursor: pointer;
padding: 15px 30px;
-webkit-appearance: none;
}
button[disabled] {
opacity: 0.5;
}
.form-buttons {
height: 52px;
line-height: 52px;
}
.form-buttons .btn {
margin-right: 5px;
}
#error, .error, #success, .success, #warmtips, .warmtips {
background: #D83E3E;
color: #fff;
padding: 15px 20px;
border-radius: 4px;
margin-bottom: 20px;
}
#success {
background: #3C5675;
}
#error a, .error a {
color: white;
text-decoration: underline;
}
#warmtips {
background: #fff;
font-size: 14px;
color: #3C5675;
border: 2px solid #4e73df;
text-align: left;
}
</style>
</head>
<body>
<div class="container">
<h1>
<svg t="1660545699809" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4887" width="100px" height="100px">
<path d="M811.4 418.7C765.6 297.9 648.9 212 512.2 212S258.8 297.8 213 418.6C127.3 441.1 64 519.1 64 612c0 110.5 89.5 200 199.9 200h496.2C870.5 812 960 722.5 960 612c0-92.7-63.1-170.7-148.6-193.3z m36.3 281c-23.4 23.4-54.5 36.3-87.6 36.3H263.9c-33.1 0-64.2-12.9-87.6-36.3-23.4-23.4-36.3-54.6-36.3-87.7 0-28 9.1-54.3 26.2-76.3 16.7-21.3 40.2-36.8 66.1-43.7l37.9-9.9 13.9-36.6c8.6-22.8 20.6-44.1 35.7-63.4 14.9-19.2 32.6-35.9 52.4-49.9 41.1-28.9 89.5-44.2 140-44.2s98.9 15.3 140 44.2c19.9 14 37.5 30.8 52.4 49.9 15.1 19.3 27.1 40.7 35.7 63.4l13.8 36.5 37.8 10c54.3 14.5 92.1 63.8 92.1 120 0 33.1-12.9 64.3-36.3 87.7z" p-id="4888" fill="#4e73df"></path>
</svg>
</h1>
<h2>宝塔第三方云端 - 安装程序</h2>
<div>
<form method="post">
<div id="error" style="display:none"></div>
<div id="success" style="display:none"></div>
<div id="warmtips" style="display:none"><p>安装完成后,你还需要进行以下操作:</p><p>1、在后台使用批量替换工具执行命令一键替换压缩包与安装脚本中的域名。</p><p></p>2、在后台配置面板对接同步插件列表与插件包。</p></div>
<div class="form-group">
<div class="form-field">
<label>MySQL 数据库地址</label>
<input type="text" name="mysql_host" value="localhost" required="">
</div>
<div class="form-field">
<label>MySQL 数据库端口</label>
<input type="number" name="mysql_port" value="3306">
</div>
<div class="form-field">
<label>MySQL 用户名</label>
<input type="text" name="mysql_user" value="" required="">
</div>
<div class="form-field">
<label>MySQL 密码</label>
<input type="text" name="mysql_pwd" value="" required="">
</div>
<div class="form-field">
<label>MySQL 数据库名</label>
<input type="text" name="mysql_name" value="" required="">
</div>
<div class="form-field">
<label>MySQL 数据表前缀</label>
<input type="text" name="mysql_prefix" value="cloud_">
</div>
</div>
<div class="form-group">
<div class="form-field">
<label>管理员用户名</label>
<input type="text" name="admin_username" value="admin" required=""/>
</div>
<div class="form-field">
<label>管理员密码</label>
<input type="text" name="admin_password" value="123456" required="">
</div>
</div>
<div class="form-buttons">
<!--@formatter:off-->
<button type="submit" >点击安装</button>
<!--@formatter:on-->
</div>
</form>
</div>
</div>
<script src="//lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.4/jquery.min.js"></script>
<script>
$(function () {
$('form').on('submit', function (e) {
e.preventDefault();
var form = this;
var $error = $("#error");
var $success = $("#success");
var $button = $(this).find('button')
.text("安装中...")
.prop('disabled', true);
$.ajax({
url: "",
type: "POST",
dataType: "json",
data: $(this).serialize(),
success: function (ret) {
if (ret.code == 1) {
$error.hide();
$(".form-group", form).remove();
$button.remove();
$("#success").text(ret.msg).show();
$("#warmtips").show();
$buttons = $(".form-buttons", form);
$('<a class="btn" href="/admin" style="background:#4e73df">进入后台</a>').appendTo($buttons);
} else {
$error.show().text(ret.msg);
$button.prop('disabled', false).text("点击安装");
$("html,body").animate({
scrollTop: 0
}, 500);
}
},
error: function (xhr) {
$error.show().text(xhr.responseText);
$button.prop('disabled', false).text("点击安装");
$("html,body").animate({
scrollTop: 0
}, 500);
}
});
return false;
});
});
</script>
</body>
</html>

View File

@ -6,5 +6,8 @@ return [
// 指令定义
'commands' => [
'updateall' => 'app\command\UpdateAll',
'decrypt' => 'app\command\DecryptFile',
'clean' => 'app\command\Clean',
'cleanvitejs' => 'app\command\CleanViteJs',
],
];

0
data/config/.gitkeep Normal file
View File

File diff suppressed because one or more lines are too long

0
data/en/config/.gitkeep Normal file
View File

View File

View File

View File

View File

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 598 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 971 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Some files were not shown because too many files have changed in this diff Show More