2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 12:40:25 +08:00

特性(全局):一波更新

This commit is contained in:
耗子
2022-12-22 22:46:32 +08:00
parent 330d9bbb87
commit 45161c522c
44 changed files with 1032 additions and 506 deletions

View File

@@ -3,10 +3,18 @@
所有重要的更改都将在此文件中记录。
## [20221210] - 常规更新
## [20221222] - 常规更新
- 增强面板安全性
- 修复网站管理个别小Bug
- 优化计划任务日志格式
- 网站管理支持分页
- 文件管理的初步实现
- 样式微调
- 部分代码优化
- 框架版本更新
## [20221212] - 常规更新
- 修复创建网站可能重复添加listen端口的Bug
## [20221210] - 安全更新

View File

@@ -25,7 +25,7 @@ class Kernel extends ConsoleKernel
// 检查文件是否存在及所有者是否为root
if (!file_exists($file) || fileowner($file) != 0) {
file_put_contents('/www/server/cron/logs/'.$cron->id.'.log',
'耗子Linux面板检测到脚本文件异常为确保安全已终止运行如果你不知道发生了什么这通常意味着服务器已被入侵。',
PHP_EOL.'耗子Linux面板检测到脚本文件异常为确保安全已终止运行如果你不知道发生了什么这通常意味着服务器已被入侵。'.PHP_EOL,
FILE_APPEND);
continue;
}
@@ -37,10 +37,10 @@ class Kernel extends ConsoleKernel
$cron->save();
})->onSuccess(function () use ($cron) {
file_put_contents('/www/server/cron/logs/'.$cron->id.'.log',
Carbon::now()->toDateTimeString().' 任务执行成功', FILE_APPEND);
PHP_EOL.Carbon::now()->toDateTimeString().' 任务执行成功'.PHP_EOL, FILE_APPEND);
})->onFailure(function () use ($cron) {
file_put_contents('/www/server/cron/logs/'.$cron->id.'.log',
Carbon::now()->toDateTimeString().' 任务执行失败', FILE_APPEND);
PHP_EOL.Carbon::now()->toDateTimeString().' 任务执行失败'.PHP_EOL, FILE_APPEND);
});
}
}

View File

@@ -11,21 +11,494 @@ use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class FilesController extends Controller
{
/**
* 获取某个目录的文件列表
* 获取某个目录的文件(夹)列表
* @param Request $request
* @return JsonResponse
*/
public function getDirList(Request $request): JsonResponse
public function getList(Request $request): JsonResponse
{
$limit = $request->input('limit', 10);
try {
$credentials = $this->validate($request, [
'path' => ['required', 'regex:/^\/.*$/'],
'limit' => 'required|integer',
'page' => 'required|integer',
]);
$path = $credentials['path'];
$limit = $credentials['limit'];
$page = $credentials['page'];
} catch (ValidationException $e) {
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
}
if (!is_dir($path)) {
return response()->json(['code' => 1, 'msg' => '目录不存在']);
}
$files = scandir($path);
$fileData = [];
foreach ($files as $k => $v) {
if ($v == '.' || $v == '..') {
continue;
}
$fileData[$k]['name'] = $v;
$fileData[$k]['path'] = $path.'/'.$v;
$fileData[$k]['type'] = filetype($path.'/'.$v);
$fileData[$k]['size'] = filesize($path.'/'.$v);
$fileData[$k]['mtime'] = Carbon::createFromTimestamp(filemtime($path.'/'.$v))->toDateTimeString();
}
// 分页
$total = count($fileData);
$filesArr = array_slice($fileData, ($page - 1) * $limit, $limit);
$data['code'] = 0;
$data['msg'] = 'success';
$data['count'] = '';
$data['data'] = '';
$data['count'] = $total;
$data['data'] = $filesArr;
return response()->json($data);
}
/**
* 获取文件内容
* @param Request $request
* @return JsonResponse
*/
public function getFileContent(Request $request): JsonResponse
{
try {
$credentials = $this->validate($request, [
'file' => ['required', 'regex:/^\/.*$/'],
]);
$file = $credentials['file'];
} catch (ValidationException $e) {
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
}
if (!is_file($file)) {
return response()->json(['code' => 1, 'msg' => '文件不存在']);
}
$content = @file_get_contents($file);
$data['code'] = 0;
$data['msg'] = 'success';
$data['data'] = $content;
return response()->json($data);
}
/**
* 保存文件内容
* @param Request $request
* @return JsonResponse
*/
public function saveFileContent(Request $request): JsonResponse
{
try {
$credentials = $this->validate($request, [
'file' => ['required', 'regex:/^\/.*$/'],
'content' => 'required',
]);
$file = $credentials['file'];
$content = $credentials['content'];
} catch (ValidationException $e) {
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
}
if (!is_file($file)) {
return response()->json(['code' => 1, 'msg' => '文件不存在']);
}
$res = @file_put_contents($file, $content);
if ($res === false) {
return response()->json(['code' => 1, 'msg' => '保存失败']);
}
return response()->json(['code' => 0, 'msg' => 'success']);
}
/**
* 创建目录
* @param Request $request
* @return JsonResponse
*/
public function createDir(Request $request): JsonResponse
{
try {
$credentials = $this->validate($request, [
'path' => ['required', 'regex:/^\/.*$/'],
'name' => 'required',
]);
$path = $credentials['path'];
$name = $credentials['name'];
} catch (ValidationException $e) {
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
}
if (!is_dir($path)) {
return response()->json(['code' => 1, 'msg' => '目录不存在']);
}
$res = @mkdir($path.'/'.$name);
if ($res === false) {
return response()->json(['code' => 1, 'msg' => '创建失败']);
}
return response()->json(['code' => 0, 'msg' => 'success']);
}
/**
* 重命名文件或目录
* @param Request $request
* @return JsonResponse
*/
public function rename(Request $request): JsonResponse
{
try {
$credentials = $this->validate($request, [
'path' => ['required', 'regex:/^\/.*$/'],
'name' => 'required',
]);
$path = $credentials['path'];
$name = $credentials['name'];
} catch (ValidationException $e) {
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
}
if (!is_file($path) && !is_dir($path)) {
return response()->json(['code' => 1, 'msg' => '文件或目录不存在']);
}
$res = @rename($path, dirname($path).'/'.$name);
if ($res === false) {
return response()->json(['code' => 1, 'msg' => '重命名失败']);
}
return response()->json(['code' => 0, 'msg' => 'success']);
}
/**
* 上传文件
* @param Request $request
* @return JsonResponse
*/
public function uploadFile(Request $request): JsonResponse
{
try {
$credentials = $this->validate($request, [
'path' => ['required', 'regex:/^\/.*$/'],
'file' => 'required|file',
]);
$path = $credentials['path'];
$file = $credentials['file'];
} catch (ValidationException $e) {
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
}
if (!is_dir($path)) {
return response()->json(['code' => 1, 'msg' => '目录不存在']);
}
$res = @move_uploaded_file($file->getRealPath(), $path.'/'.$file->getClientOriginalName());
if ($res === false) {
return response()->json(['code' => 1, 'msg' => '上传失败']);
}
return response()->json(['code' => 0, 'msg' => 'success']);
}
/**
* 下载文件
* @param Request $request
* @return JsonResponse|BinaryFileResponse
*/
public function downloadFile(Request $request): BinaryFileResponse|JsonResponse
{
try {
$credentials = $this->validate($request, [
'path' => ['required', 'regex:/^\/.*$/'],
]);
$path = $credentials['path'];
} catch (ValidationException $e) {
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
}
if (!is_file($path)) {
return response()->json(['code' => 1, 'msg' => '文件不存在']);
}
return response()->download($path);
}
/**
* 创建文件
*/
public function createFile(Request $request): JsonResponse
{
try {
$credentials = $this->validate($request, [
'path' => ['required', 'regex:/^\/.*$/'],
'name' => 'required',
]);
$path = $credentials['path'];
$name = $credentials['name'];
} catch (ValidationException $e) {
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
}
if (!is_dir($path)) {
return response()->json(['code' => 1, 'msg' => '目录不存在']);
}
$res = @file_put_contents($path.'/'.$name, '');
if ($res === false) {
return response()->json(['code' => 1, 'msg' => '创建失败']);
}
return response()->json(['code' => 0, 'msg' => 'success']);
}
/**
* 设置权限
* @param Request $request
* @return JsonResponse
*/
public function chmod(Request $request): JsonResponse
{
try {
$credentials = $this->validate($request, [
'path' => ['required', 'regex:/^\/.*$/'],
'mode' => 'required',
]);
$path = $credentials['path'];
$mode = $credentials['mode'];
} catch (ValidationException $e) {
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
}
if (!is_file($path) && !is_dir($path)) {
return response()->json(['code' => 1, 'msg' => '文件或目录不存在']);
}
$res = @chmod($path, $mode);
if ($res === false) {
return response()->json(['code' => 1, 'msg' => '设置失败']);
}
return response()->json(['code' => 0, 'msg' => 'success']);
}
/**
* 设置所有者
*/
public function chown(Request $request): JsonResponse
{
try {
$credentials = $this->validate($request, [
'path' => ['required', 'regex:/^\/.*$/'],
'user' => 'required',
]);
$path = $credentials['path'];
$user = $credentials['user'];
} catch (ValidationException $e) {
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
}
if (!is_file($path) && !is_dir($path)) {
return response()->json(['code' => 1, 'msg' => '文件或目录不存在']);
}
$res = @chown($path, $user);
if ($res === false) {
return response()->json(['code' => 1, 'msg' => '设置失败']);
}
return response()->json(['code' => 0, 'msg' => 'success']);
}
/**
* 设置所有者组
* @param Request $request
* @return JsonResponse
*/
public function chgrp(Request $request): JsonResponse
{
try {
$credentials = $this->validate($request, [
'path' => ['required', 'regex:/^\/.*$/'],
'group' => 'required',
]);
$path = $credentials['path'];
$group = $credentials['group'];
} catch (ValidationException $e) {
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
}
if (!is_file($path) && !is_dir($path)) {
return response()->json(['code' => 1, 'msg' => '文件或目录不存在']);
}
$res = @chgrp($path, $group);
if ($res === false) {
return response()->json(['code' => 1, 'msg' => '设置失败']);
}
return response()->json(['code' => 0, 'msg' => 'success']);
}
/**
* 复制文件/目录
* @param Request $request
* @return JsonResponse
*/
public function copy(Request $request): JsonResponse
{
try {
$credentials = $this->validate($request, [
'path' => ['required', 'regex:/^\/.*$/'],
'dest' => ['required', 'regex:/^\/.*$/'],
]);
$path = $credentials['path'];
$dest = $credentials['dest'];
} catch (ValidationException $e) {
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
}
if (!is_file($path) && !is_dir($path)) {
return response()->json(['code' => 1, 'msg' => '文件或目录不存在']);
}
if (!is_dir($dest)) {
return response()->json(['code' => 1, 'msg' => '目标目录不存在']);
}
$res = @copy($path, $dest.'/'.basename($path));
if ($res === false) {
return response()->json(['code' => 1, 'msg' => '复制失败']);
}
return response()->json(['code' => 0, 'msg' => 'success']);
}
/**
* 移动文件/目录
* @param Request $request
* @return JsonResponse
*/
public function move(Request $request): JsonResponse
{
try {
$credentials = $this->validate($request, [
'path' => ['required', 'regex:/^\/.*$/'],
'dest' => ['required', 'regex:/^\/.*$/'],
]);
$path = $credentials['path'];
$dest = $credentials['dest'];
} catch (ValidationException $e) {
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
}
if (!is_file($path) && !is_dir($path)) {
return response()->json(['code' => 1, 'msg' => '文件或目录不存在']);
}
if (!is_dir($dest)) {
return response()->json(['code' => 1, 'msg' => '目标目录不存在']);
}
$res = @rename($path, $dest.'/'.basename($path));
if ($res === false) {
return response()->json(['code' => 1, 'msg' => '移动失败']);
}
return response()->json(['code' => 0, 'msg' => 'success']);
}
/**
* 删除文件/目录
* @param Request $request
* @return JsonResponse
*/
public function delete(Request $request): JsonResponse
{
try {
$credentials = $this->validate($request, [
'path' => ['required', 'regex:/^\/.*$/'],
]);
$path = $credentials['path'];
} catch (ValidationException $e) {
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
}
if (!is_file($path) && !is_dir($path)) {
return response()->json(['code' => 1, 'msg' => '文件或目录不存在']);
}
$res = @shell_exec('rm -rf '.escapeshellarg($path).' 2>&1');
if (!empty($res)) {
return response()->json(['code' => 1, 'msg' => '删除失败:'.$res]);
}
return response()->json(['code' => 0, 'msg' => 'success']);
}
/**
* 压缩文件/目录
* @param Request $request
* @return JsonResponse
*/
public function zip(Request $request): JsonResponse
{
try {
$credentials = $this->validate($request, [
'path' => ['required', 'regex:/^\/.*$/'],
]);
$path = $credentials['path'];
} catch (ValidationException $e) {
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
}
if (!is_file($path) && !is_dir($path)) {
return response()->json(['code' => 1, 'msg' => '文件或目录不存在']);
}
$zipPath = dirname($path).'/'.basename($path).'.zip';
$res = @shell_exec('zip -r '.escapeshellarg($zipPath).' '.escapeshellarg($path).' 2>&1');
if (!empty($res)) {
return response()->json(['code' => 1, 'msg' => '压缩失败:'.$res]);
}
return response()->json(['code' => 0, 'msg' => 'success']);
}
/**
* 解压文件/目录
* @param Request $request
* @return JsonResponse
*/
public function unzip(Request $request): JsonResponse
{
try {
$credentials = $this->validate($request, [
'path' => ['required', 'regex:/^\/.*$/'],
]);
$path = $credentials['path'];
} catch (ValidationException $e) {
return response()->json(['code' => 1, 'msg' => $e->getMessage()]);
}
if (!is_file($path)) {
return response()->json(['code' => 1, 'msg' => '文件不存在']);
}
$res = @shell_exec('unzip -o '.escapeshellarg($path).' -d '.escapeshellarg(dirname($path)).' 2>&1');
if (!empty($res)) {
return response()->json(['code' => 1, 'msg' => '解压失败:'.$res]);
}
return response()->json(['code' => 0, 'msg' => 'success']);
}
}

View File

@@ -20,11 +20,14 @@ class WebsitesController extends Controller
{
/**
* 获取面板网站
* @param Request $request
* @return JsonResponse
*/
public function getList(): JsonResponse
public function getList(Request $request): JsonResponse
{
$websiteList = Website::query()->get();
$limit = $request->input('limit', 10);
$websiteList = Website::query()->orderBy('id')->paginate($limit);
// 判空
if ($websiteList->isEmpty()) {
return response()->json([
@@ -42,7 +45,8 @@ class WebsitesController extends Controller
return response()->json([
'code' => 0,
'msg' => '获取成功',
'data' => $websiteList
'count' => $websiteList->total(),
'data' => $websiteList->items()
]);
}

View File

@@ -14,7 +14,7 @@ class AddAccessToken
// 获取请求头中的token
$token = $request->header('access_token') ?? $request->input('access_token');
// 将token放入请求中
$request->headers->set('Authorization', 'Bearer ' . $token);
$request->headers->set('Authorization', 'Bearer '.$token);
return $next($request);
}

647
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
<?php
return [
'name' => '耗子Linux面板',
'version' => '20221212',
'version' => '20221222',
'plugin_dir' => '/www/panel/plugins',
];

View File

@@ -1,30 +1,35 @@
layui.define(['jquery', 'layer', 'laypage'], function(exports) { //提示模块也可以依赖其它模块layui.define('layer', callback);
var $ = layui.jquery,
layer = layui.layer,
laypage = layui.laypage;
/**
* Name: Layui文件管理插件LayuiAdmin定制版
* Author: 李春林
* Author: 耗子
* Date: 2022-12-22
*/
layui.define(['jquery', 'layer', 'laypage', 'admin'], function (exports) { //提示模块也可以依赖其它模块layui.define('layer', callback);
var $ = layui.jquery, layer = layui.layer, laypage = layui.laypage, admin = layui.admin;
//外部接口
var fm = {
config:{'test':'test','thumb':{'nopic':'',width:100,height:100},icon_url:'ico/',btn_upload:true,btn_create:true}
,cache: {} //数据缓存
,index: layui.fm ? (layui.fm.index + 10000) : 0
config: {
'test': 'test',
'thumb': {'nopic': '', width: 100, height: 100},
icon_url: 'ico/',
btn_upload: true,
btn_create: true
}, cache: {} //数据缓存
, index: layui.fm ? (layui.fm.index + 10000) : 0
//设置全局项
,set: function(options){
, set: function (options) {
var that = this;
that.config = $.extend({}, that.config, options);
return that;
}
//事件监听
,on: function(events, callback){
, on: function (events, callback) {
return layui.onevent.call(this, 'file', events, callback);
}
,dirRoot:[{'path':'','name': '根目录'}]
,v:'1.0.1.2019.12.26'
}, dirRoot: [{'path': '/www/wwwroot', 'name': '网站目录'}], v: '20221222'
}
//操作当前实例
, thisFm = function() {
var that = this,
options = that.config,
id = options.id || options.index;
, thisFm = function () {
var that = this, options = that.config, id = options.id || options.index;
// console.log(id)
if (id) {
@@ -32,20 +37,19 @@ layui.define(['jquery', 'layer', 'laypage'], function(exports) { //提示:模
thisFm.config[id] = options; //记录当前实例配置项
}
return {
config: options,
reload: function(options) {
config: options, reload: function (options) {
that.reload.call(that, options);
}
}
}
//获取当前实例配置项
,getThisFmConfig = function(id){
, getThisFmConfig = function (id) {
var config = thisFm.config[id];
if(!config) hint.error('The ID option was not found in the fm instance');
if (!config) hint.error('The ID option was not found in the fm instance');
return config || null;
}
//构造器
,Class = function(options){
, Class = function (options) {
var that = this;
that.config = $.extend({}, that.config, fm.config, options);
//记录所有实例
@@ -55,9 +59,8 @@ layui.define(['jquery', 'layer', 'laypage'], function(exports) { //提示:模
that.render();
};
//渲染
Class.prototype.render = function(){
var that = this
,options = that.config;
Class.prototype.render = function () {
var that = this, options = that.config;
options.elem = $(options.elem);
options.where = options.where || {};
@@ -65,21 +68,16 @@ layui.define(['jquery', 'layer', 'laypage'], function(exports) { //提示:模
//请求参数的自定义格式
options.request = $.extend({
pageName: 'page'
,limitName: 'limit'
pageName: 'page', limitName: 'limit'
}, options.request)
//响应数据的自定义格式
options.response = $.extend({
statusName: 'code'
,statusCode: 0
,msgName: 'msg'
,dataName: 'data'
,countName: 'count'
statusName: 'code', statusCode: 0, msgName: 'msg', dataName: 'data', countName: 'count'
}, options.response);
//如果 page 传入 laypage 对象
if(typeof options.page === 'object'){
if (typeof options.page === 'object') {
options.limit = options.page.limit || options.limit;
options.limits = options.page.limits || options.limits;
that.page = options.page.curr = options.page.curr || 1;
@@ -87,37 +85,23 @@ layui.define(['jquery', 'layer', 'laypage'], function(exports) { //提示:模
delete options.page.jump;
}
if(!options.elem[0]) return that;
if (!options.elem[0]) return that;
//渲染主体
var _btn = ''
if(options.btn_create){
_btn +='<button type="button" class="layui-btn layui-btn-primary layui-btn-sm" id="new_dir">建文件夹</button>';
if (options.btn_create) {
_btn += '<button type="button" class="layui-btn layui-btn-primary layui-btn-sm" id="new_dir">建文件夹</button>';
}
if(options.btn_upload){
_btn +='<button type="button" class="layui-btn layui-btn-primary layui-btn-sm" id="uploadfile">上传文件</button>';
if (options.btn_upload) {
_btn += '<button type="button" class="layui-btn layui-btn-primary layui-btn-sm" id="uploadfile">上传文件</button>';
}
var _html = '<div class="layui-card" >' +
'<div class="layui-card-body">' +
'<div class="layui-btn-group tool_bar">' +
_btn+
'<button type="button" class="layui-btn layui-btn-primary layui-btn-sm" id="back"><i class="layui-icon layui-icon-left line"></i></button>' +
'</div>' +
'<div class="layui-inline path_bar" id="">' +
'<a ><i class="layui-icon layui-icon-more-vertical line" ></i>根目录</a>' +
'</div>' +
'</div><hr><div class="layui-card-body">' +
'<div class="file-body layui-form" style="">' +
'<ul class="file layui-row fm_body layui-col-space10" >' +
'</ul>' +
'</div>' +
'<hr><div ><div class="layui_page_'+options.id+'" id="layui_page_'+options.id+'"></div></div></div>';
var _html = '<div class="layui-card" >' + '<div class="layui-card-body">' + '<div class="layui-btn-group tool_bar">' + _btn + '<button type="button" class="layui-btn layui-btn-primary layui-btn-sm" id="back"><i class="layui-icon layui-icon-left line"></i></button>' + '</div>' + '<div class="layui-inline path_bar" id="">' + '<a ><i class="layui-icon layui-icon-more-vertical line" ></i>根目录</a>' + '</div>' + '</div><hr><div class="layui-card-body">' + '<div class="file-body layui-form" style="">' + '<ul class="file layui-row fm_body layui-col-space10" >' + '</ul>' + '</div>' + '<hr><div ><div class="layui_page_' + options.id + '" id="layui_page_' + options.id + '"></div></div></div>';
options.elem.html(_html);
options.index = that.index;
that.key = options.id || options.index;
//各级容器
that.layPage = options.elem.find('.layui_page_'+options.id);
that.layPage = options.elem.find('.layui_page_' + options.id);
that.layBody = options.elem.find('.fm_body');
that.layPathBar = options.elem.find('.path_bar');
that.layToolBar = options.elem.find('.tool_bar');
@@ -129,12 +113,8 @@ layui.define(['jquery', 'layer', 'laypage'], function(exports) { //提示:模
Class.prototype.page = 1;
//获得数据
Class.prototype.pullData = function(curr) {
var that = this,
options = that.config,
request = options.request,
response = options.response,
_status = false;
Class.prototype.pullData = function (curr) {
var that = this, options = that.config, request = options.request, response = options.response, _status = false;
that.startTime = new Date().getTime(); //渲染开始时间
if (options.url) { //Ajax请求
@@ -150,7 +130,7 @@ layui.define(['jquery', 'layer', 'laypage'], function(exports) { //提示:模
that.loading();
$.ajax({
admin.req({
type: options.method || 'get',
url: options.url,
contentType: options.contentType,
@@ -158,7 +138,7 @@ layui.define(['jquery', 'layer', 'laypage'], function(exports) { //提示:模
async: false,
dataType: 'json',
headers: options.headers || {},
success: function(res) {
success: function (res) {
//如果有数据解析的回调,则获得其返回的数据
if (typeof options.parseData === 'function') {
res = options.parseData(res) || res;
@@ -166,12 +146,9 @@ layui.define(['jquery', 'layer', 'laypage'], function(exports) { //提示:模
//检查数据格式是否符合规范
if (res[response.statusName] != response.statusCode) {
that.errorView(
res[response.msgName] ||
('返回的数据不符合规范,正确的成功状态码应为:"' + response.statusName + '": ' + response.statusCode)
);
that.errorView(res[response.msgName] || ('返回的数据不符合规范,正确的成功状态码应为:"' + response.statusName + '": ' + response.statusCode));
} else {
// console.log(res, curr, res[response.countName]);
console.log(res);
that.renderData(res, curr, res[response.countName]);
options.time = (new Date().getTime() - that.startTime) + ' ms'; //耗时(接口请求+视图渲染)
@@ -179,7 +156,7 @@ layui.define(['jquery', 'layer', 'laypage'], function(exports) { //提示:模
typeof options.done === 'function' && options.done(res, curr, res[response.countName]);
_status = true;
},
error: function(e, m) {
error: function (e, m) {
that.errorView('数据接口请求异常:' + m);
}
@@ -188,61 +165,54 @@ layui.define(['jquery', 'layer', 'laypage'], function(exports) { //提示:模
return _status;
};
//数据渲染
Class.prototype.renderData = function(res, curr, count){
var that = this
,options = that.config
,data = res[options.response.dataName] || []
Class.prototype.renderData = function (res, curr, count) {
var that = this, options = that.config, data = res[options.response.dataName] || []
//渲染数据
var _content = ''
layui.each(data,function(i,v){
let _img,_type;
layui.each(data, function (i, v) {
let _img, _type;
_type = v.type;
switch (v.type) {
case 'directory':
_img = '<div style="width:'+options.thumb['width']+'px;height:'+options.thumb['height']+'px;line-height:'+options.thumb['height']+'px"><img src="ico/dir.png" style="vertical-align:middle;"></div>';
case 'dir':
_img = '<div style="width:' + options.thumb['width'] + 'px;height:' + options.thumb['height'] + 'px;line-height:' + options.thumb['height'] + 'px"><img src="' + options.icon_url + 'dir.png" style="vertical-align:middle;"></div>';
_type = 'DIR';
break;
default:
if (v.type == 'png' || v.type == 'gif' || v.type == 'jpg' || v.type == 'image') {
_img = '<img src="' + v.thumb + '" width="'+options.thumb['width']+'" height="'+options.thumb['height']+'" onerror=\'this.src="'+options.thumb['nopic']+'"\' />';
_img = '<img src="' + v.thumb + '" width="' + options.thumb['width'] + '" height="' + options.thumb['height'] + '" onerror=\'this.src="' + options.thumb['nopic'] + '"\' />';
} else {
_img = '<div style="width:'+options.thumb['width']+'px;height:'+options.thumb['height']+'px;line-height:'+options.thumb['height']+'px"><img src="' + options.icon_url + v.type + '.png" onerror=\'this.src="'+options.thumb['nopic']+'"\' /></div>';
_img = '<div style="width:' + options.thumb['width'] + 'px;height:' + options.thumb['height'] + 'px;line-height:' + options.thumb['height'] + 'px"><img src="' + options.icon_url + v.type + '.png" onerror=\'this.src="' + options.thumb['nopic'] + '"\' /></div>';
}
break;
}
_content+='<li style="display:inline-block" data-type="'+_type+'" data-index="'+i+'">' +
'<div class="content" align="center">'+
_img +
'<p class="layui-elip" title="' + v.name + '">' + v.name + ' </p>' +
'</div>' +
'</li>';
_content += '<li style="display:inline-block" data-type="' + _type + '" data-index="' + i + '">' + '<div class="content" align="center">' + _img + '<p class="layui-elip" title="' + v.name + '">' + v.name + ' </p>' + '</div>' + '</li>';
});
options.elem.find('.file').html(_content);
fm.cache[options.id] = data; //记录数据
//显示隐藏分页栏
// console.log(that.layPage)
that.layPage[(count == 0 || (data.length === 0 && curr == 1)) ? 'addClass' : 'removeClass']('layui-hide');
if(data.length === 0){
if (data.length === 0) {
return that.errorView('空目录');
} else {
//that.layFixed.removeClass('layui-hide');
}
//同步分页状态
if(options.page){
if (options.page) {
// console.log(options,'layui_page_' + options.id)
options.page = $.extend({
elem: 'layui_page_' + options.id
,count: count
,limit: options.limit
,limits: options.limits || [10,20,30,40,50,60,70,80,90]
,groups: 3
,layout: ['prev', 'page', 'next', 'skip', 'count', 'limit']
,prev: '<i class="layui-icon">&#xe603;</i>'
,next: '<i class="layui-icon">&#xe602;</i>'
,jump: function(obj, first){
if(!first){
elem: 'layui_page_' + options.id,
count: count,
limit: options.limit,
limits: options.limits || [10, 20, 30, 40, 50, 60, 70, 80, 90],
groups: 3,
layout: ['prev', 'page', 'next', 'skip', 'count', 'limit'],
prev: '<i class="layui-icon">&#xe603;</i>',
next: '<i class="layui-icon">&#xe602;</i>',
jump: function (obj, first) {
if (!first) {
//分页本身并非需要做以下更新,下面参数的同步,主要是因为其它处理统一用到了它们
//而并非用的是 options.page 中的参数(以确保分页未开启的情况仍能正常使用)
that.page = obj.curr; //更新页码
@@ -257,135 +227,124 @@ layui.define(['jquery', 'layer', 'laypage'], function(exports) { //提示:模
}
};
//更新路径工具条
Class.prototype.updatePathBar = function(){
Class.prototype.updatePathBar = function () {
// console.log('updatePathBar',fm.dirRoot);
var that = this
,options = that.config;
var that = this, options = that.config;
//请求数据
let dir_cur = fm.dirRoot[fm.dirRoot.length -1];
options.where = {'path':dir_cur['path']}
let dir_cur = fm.dirRoot[fm.dirRoot.length - 1];
options.where = {'path': dir_cur['path']}
let _rs = that.pullData(1);
// console.log(_rs)
if(false == _rs) return;
if (false == _rs) return;
that.layPathBar.html('');
fm.dirRoot.map(function(item,index,arr){
let icon = index==0 ?'layui-icon-more-vertical':'layui-icon-right';
let html = '<i class="layui-icon '+icon+'"></i>'+
'<a data-path="' + item.path + '" data-name="' + item.name + '" >' + item.name + '</a>'
fm.dirRoot.map(function (item, index, arr) {
let icon = index == 0 ? 'layui-icon-more-vertical' : 'layui-icon-right';
let html = '<i class="layui-icon ' + icon + '"></i>' + '<a data-path="' + item.path + '" data-name="' + item.name + '" >' + item.name + '</a>'
that.layPathBar.append(html);
})
}
//事件处理
Class.prototype.events = function(){
var that = this
,options = that.config
,_BODY = $('body')
,dict = {}
,filter = options.elem.attr('lay-filter');
Class.prototype.events = function () {
var that = this, options = that.config, _BODY = $('body'), dict = {}, filter = options.elem.attr('lay-filter');
//文件事件
that.layBody.on('click', 'li', function(){ //单击行
that.layBody.on('click', 'li', function () { //单击行
setPicEvent.call(this, 'pic');
});
//文件夹事件
that.layBody.on('click', 'li[data-type=DIR]', function(){ //单击行
that.layBody.on('click', 'li[data-type=DIR]', function () { //单击行
var othis = $(this);
var data = fm.cache[options.id];
var data = fm.cache[options.id];
var index = othis.data('index');
data = data[index] || {};
//导航图标
fm.dirRoot.push({'path':data.path,'name': data.name});
fm.dirRoot.push({'path': data.path, 'name': data.name});
that.updatePathBar();
});
//返回上一级目录
that.layToolBar.on('click', '#back', function(){
that.layToolBar.on('click', '#back', function () {
var othis = $(this);
if(fm.dirRoot.length == 1) return layer.msg('已经是根目录');
if (fm.dirRoot.length == 1) return layer.msg('已经是根目录');
fm.dirRoot.length >1 && fm.dirRoot.pop()
fm.dirRoot.length > 1 && fm.dirRoot.pop()
that.updatePathBar();
// console.log('back');
});
//上传文件
that.layToolBar.on('click', '#uploadfile', function(){
that.layToolBar.on('click', '#uploadfile', function () {
var othis = $(this);
let eventType = 'uploadfile';
layui.event.call(this,
'file', eventType + '('+ filter +')'
,{obj:othis,path:fm.dirRoot[fm.dirRoot.length -1]['path']}
);
layui.event.call(this, 'file', eventType + '(' + filter + ')', {
obj: othis,
path: fm.dirRoot[fm.dirRoot.length - 1]['path']
});
// console.log('uploadfile');
});
//新建文件夹
that.layToolBar.on('click', '#new_dir', function(){
that.layToolBar.on('click', '#new_dir', function () {
var othis = $(this);
let eventType = 'new_dir';
layer.prompt({ title: '请输入新文件夹名字', formType: 0 }, function(name, index) {
layer.prompt({title: '请输入新文件夹名字', formType: 0}, function (name, index) {
layer.close(index);
//新建文件夹
layui.event.call(this,
'file', eventType + '('+ filter +')'
,{obj:othis,folder:name,path:fm.dirRoot[fm.dirRoot.length -1]['path']}
);
layui.event.call(this, 'file', eventType + '(' + filter + ')', {
obj: othis,
folder: name,
path: fm.dirRoot[fm.dirRoot.length - 1]['path']
});
});
});
//创建点击文件事件监听
var setPicEvent = function(eventType) {
var setPicEvent = function (eventType) {
var othis = $(this);
var data = fm.cache[options.id];
var data = fm.cache[options.id];
var index = othis.data('index');
if (othis.data('type')=='DIR') return; //不触发事件
if (othis.data('type') == 'DIR') return; //不触发事件
data = data[index] || {};
layui.event.call(this,
'file', eventType + '('+ filter +')'
,{obj:othis,data:data}
);
layui.event.call(this, 'file', eventType + '(' + filter + ')', {obj: othis, data: data});
};
};
//请求loading
Class.prototype.loading = function(hide){
var that = this
,options = that.config;
if(options.loading){
if(hide){
Class.prototype.loading = function (hide) {
var that = this, options = that.config;
if (options.loading) {
if (hide) {
that.layInit && that.layInit.remove();
delete that.layInit;
that.layBox.find(ELEM_INIT).remove();
} else {
that.layInit = $(['<div class="layui-table-init">'
,'<i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i>'
,'</div>'].join(''));
that.layInit = $(['<div class="layui-table-init">', '<i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i>', '</div>'].join(''));
that.layBox.append(that.layInit);
}
}
};
//异常提示
Class.prototype.errorView = function(html){
Class.prototype.errorView = function (html) {
var that = this
layer.msg(html);
};
//重载
Class.prototype.reload = function(options){
Class.prototype.reload = function (options) {
var that = this;
options = options || {};
delete that.haveInit;
if(options.data && options.data.constructor === Array) delete that.config.data;
if (options.data && options.data.constructor === Array) delete that.config.data;
that.config = $.extend(true, {}, that.config, options);
that.render();
};
//重载
fm.reload = function(id, options){
fm.reload = function (id, options) {
var config = getThisFmConfig(id); //获取当前实例配置项
if(!config) return;
if (!config) return;
var that = thisFm.that[id];
that.reload(options);
@@ -393,7 +352,7 @@ layui.define(['jquery', 'layer', 'laypage'], function(exports) { //提示:模
return thisFm.call(that);
};
//核心入口
fm.render = function(options){
fm.render = function (options) {
var inst = new Class(options);
return thisFm.call(inst);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -95,7 +95,7 @@
, {field: 'updated_at', title: '上次运行时间'}
, {
field: 'edit',
width: 170,
width: 180,
title: '操作',
templet: '#cron-table-edit',
fixed: 'right',

View File

@@ -1,2 +1,48 @@
<!--
Name: 文件管理
Author: 耗子
Date: 2022-12-22
-->
<title>文件管理</title>
<h1 style="text-align: center; padding-top: 20px;">文件管理正在开发中!</h1>
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-header">该功能尚未完成开发请等待推荐使用sftp临时代替</div>
<div class="layui-card-body">
<div class="layui-fluid">
<div id="files" lay-filter="files"></div>
</div>
</div>
</div>
</div>
<script>
layui.use(['layer', 'admin', 'view', 'form', 'file'], function () {
let $ = layui.jquery;
let admin = layui.admin;
let view = layui.view;
let form = layui.form;
let file = layui.file;
let path = '/www/wwwroot';// 当前路径
let router = layui.router();
if (router.search.path) {
path = window.atob(router.search.path);
}
file.render({
elem: '#files'
, method: 'get'
, id: 'files-list'
, btn_upload: true
, btn_create: true
, url: '/api/panel/file/getList'
, thumb: {'nopic': '/panel/style/ico/null.jpg', 'width': 100, 'height': 100}
, icon_url: '/panel/style/ico/'
, done: function (res, curr, count) {
// console.log(res,curr,count)
}
, page: {limit: 10}
, where: {path: '/www/wwwroot'}
});
});
</script>

View File

@@ -85,12 +85,9 @@ Date: 2022-11-28
, {field: 'php', title: 'PHP', width: 60}
, {field: 'ssl', title: 'SSL', width: 110, templet: '#website-ssl'}
, {field: 'note', title: '备注', edit: 'textarea'}
, {fixed: 'right', title: '操作', unresize: true, toolbar: '#website-control', width: 160}
, {fixed: 'right', title: '操作', unresize: true, toolbar: '#website-control', width: 180}
]]
/**
* TODO: 分页
*/
//, page: true
, page: true
});
// 头工具栏事件

View File

@@ -1,6 +1,7 @@
<?php
use App\Http\Controllers\Api\CronsController;
use App\Http\Controllers\Api\FilesController;
use App\Http\Controllers\Api\MonitorsController;
use App\Http\Controllers\Api\PluginsController;
use App\Http\Controllers\Api\SafesController;
@@ -8,11 +9,10 @@ use App\Http\Controllers\Api\SettingsController;
use App\Http\Controllers\Api\TasksController;
use App\Http\Controllers\Api\UsersController;
use App\Http\Controllers\Api\WebsitesController;
use App\Http\Controllers\Api\InfosController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\InfosController;
/*
|--------------------------------------------------------------------------
| API Routes
@@ -63,31 +63,44 @@ Route::prefix('panel')->group(function () {
Route::get('getInstalledDbAndPhp', [InfosController::class, 'getInstalledDbAndPhp']);
});
// 网站
// 网站管理
Route::middleware('auth:sanctum')->prefix('website')->group(function () {
// 获取网站列表
// 获取默认设置
Route::get('getDefaultSettings', [WebsitesController::class, 'getDefaultSettings']);
// 保存默认设置
Route::post('saveDefaultSettings', [WebsitesController::class, 'saveDefaultSettings']);
// 获取网站列表
Route::get('getList', [WebsitesController::class, 'getList']);
// 添加网站
Route::post('add', [WebsitesController::class, 'add']);
// 删除网站
Route::post('delete', [WebsitesController::class, 'delete']);
// 获取网站设置
Route::get('getSiteSettings', [WebsitesController::class, 'getSiteSettings']);
// 保存网站设置
Route::post('saveSiteSettings', [WebsitesController::class, 'saveSiteSettings']);
// 清空网站日志
Route::post('clearSiteLog', [WebsitesController::class, 'clearSiteLog']);
// 更新网站备注
Route::post('updateSiteNote', [WebsitesController::class, 'updateSiteNote']);
// 设置网站状态
Route::post('setSiteStatus', [WebsitesController::class, 'setSiteStatus']);
// 获取备份列表
Route::get('getBackupList', [WebsitesController::class, 'getBackupList']);
// 创建备份
Route::post('createBackup', [WebsitesController::class, 'createBackup']);
// 上传备份
Route::post('uploadBackup', [WebsitesController::class, 'uploadBackup']);
// 恢复备份
Route::post('restoreBackup', [WebsitesController::class, 'restoreBackup']);
// 删除备份
Route::post('deleteBackup', [WebsitesController::class, 'deleteBackup']);
// 重置网站配置
Route::post('resetSiteConfig', [WebsitesController::class, 'resetSiteConfig']);
// 签发SSL证书
Route::post('issueSsl', [WebsitesController::class, 'issueSsl']);
});
// 监控
// 资源监控
Route::middleware('auth:sanctum')->prefix('monitor')->group(function () {
// 获取监控数据
Route::get('getMonitorData', [MonitorsController::class, 'getMonitorData']);
@@ -100,7 +113,7 @@ Route::prefix('panel')->group(function () {
// 清空监控数据
Route::post('clearMonitorData', [MonitorsController::class, 'clearMonitorData']);
});
// 安全
// 系统安全
Route::middleware('auth:sanctum')->prefix('safe')->group(function () {
// 获取防火墙状态
Route::get('getFirewallStatus', [SafesController::class, 'getFirewallStatus']);
@@ -125,26 +138,40 @@ Route::prefix('panel')->group(function () {
// 删除防火墙规则
Route::post('deleteFirewallRule', [SafesController::class, 'deleteFirewallRule']);
});
// 插件
Route::middleware('auth:sanctum')->prefix('plugin')->group(function () {
// 获取插件列表
Route::get('getList', [PluginsController::class, 'getList']);
Route::post('install', [PluginsController::class, 'install']);
Route::post('uninstall', [PluginsController::class, 'uninstall']);
Route::post('update', [PluginsController::class, 'update']);
Route::post('setShowHome', [PluginsController::class, 'setShowHome']);
// 文件管理
Route::middleware('auth:sanctum')->prefix('file')->group(function () {
// 获取文件(夹)列表
Route::get('getList', [FilesController::class, 'getList']);
});
// 计划任务
Route::middleware('auth:sanctum')->prefix('cron')->group(function () {
// 获取计划任务列表
Route::get('getList', [CronsController::class, 'getList']);
// 添加计划任务
Route::post('add', [CronsController::class, 'add']);
// 编辑计划任务
Route::post('edit', [CronsController::class, 'edit']);
// 删除计划任务
Route::post('delete', [CronsController::class, 'delete']);
// 设置计划任务状态
Route::post('setStatus', [CronsController::class, 'setStatus']);
// 获取计划任务日志
Route::get('getLog', [CronsController::class, 'getLog']);
});
// 设置
// 插件中心
Route::middleware('auth:sanctum')->prefix('plugin')->group(function () {
// 获取插件列表
Route::get('getList', [PluginsController::class, 'getList']);
// 安装插件
Route::post('install', [PluginsController::class, 'install']);
// 卸载插件
Route::post('uninstall', [PluginsController::class, 'uninstall']);
// 更新插件
Route::post('update', [PluginsController::class, 'update']);
// 设置插件首页显示
Route::post('setShowHome', [PluginsController::class, 'setShowHome']);
});
// 面板设置
Route::middleware('auth:sanctum')->prefix('setting')->group(function () {
// 获取设置
Route::get('get', [SettingsController::class, 'get']);
@@ -152,4 +179,3 @@ Route::prefix('panel')->group(function () {
Route::post('save', [SettingsController::class, 'save']);
});
});