新增日志过滤

新增安装未知来源插件开关
新增插件纯净模式
修复分片合并未创建文件夹BUG
优化插件管理逻辑
pull/241/head
Karson 2020-09-24 15:15:24 +08:00
parent 9d5ccb91b4
commit acb42e8eb8
26 changed files with 323 additions and 291 deletions

View File

@ -6,7 +6,8 @@ class AdminLog
{ {
public function run(&$params) public function run(&$params)
{ {
if (request()->isPost()) { //只记录POST请求的日志
if (request()->isPost() && config('fastadmin.auto_record_log')) {
\app\admin\model\AdminLog::record(); \app\admin\model\AdminLog::record();
} }
} }

View File

@ -88,7 +88,7 @@ class Addon extends Command
'name' => $name, 'name' => $name,
'addon' => $name, 'addon' => $name,
'addonClassName' => ucfirst($name), 'addonClassName' => ucfirst($name),
'addonInstallMenu' => $createMenu ? "\$menu = " . var_export_short($createMenu, "\t") . ";\n\tMenu::create(\$menu);" : '', 'addonInstallMenu' => $createMenu ? "\$menu = " . var_export_short($createMenu) . ";\n\tMenu::create(\$menu);" : '',
'addonUninstallMenu' => $menuList ? 'Menu::delete("' . $name . '");' : '', 'addonUninstallMenu' => $menuList ? 'Menu::delete("' . $name . '");' : '',
'addonEnableMenu' => $menuList ? 'Menu::enable("' . $name . '");' : '', 'addonEnableMenu' => $menuList ? 'Menu::enable("' . $name . '");' : '',
'addonDisableMenu' => $menuList ? 'Menu::disable("' . $name . '");' : '', 'addonDisableMenu' => $menuList ? 'Menu::disable("' . $name . '");' : '',
@ -379,5 +379,5 @@ class Addon extends Command
{ {
return __DIR__ . '/Addon/stubs/' . $name . '.stub'; return __DIR__ . '/Addon/stubs/' . $name . '.stub';
} }
} }

View File

@ -245,7 +245,7 @@ class Install extends Command
$config[$value['name']] = $value['value']; $config[$value['name']] = $value['value'];
} }
$config['name'] = $siteName; $config['name'] = $siteName;
file_put_contents($configFile, '<?php' . "\n\nreturn " . var_export($config, true) . ";"); file_put_contents($configFile, '<?php' . "\n\nreturn " . var_export_short($config) . ";\n");
} }
$installLockFile = INSTALL_PATH . "install.lock"; $installLockFile = INSTALL_PATH . "install.lock";

View File

@ -9,7 +9,7 @@
<style> <style>
body { body {
background: #fff; background: #f1f6fd;
margin: 0; margin: 0;
padding: 0; padding: 0;
line-height: 1.5; line-height: 1.5;
@ -71,8 +71,8 @@
} }
.form-field input { .form-field input {
background: #EDF2F7; background: #fff;
margin: 0 0 1px; margin: 0 0 2px;
border: 2px solid transparent; border: 2px solid transparent;
transition: background 0.2s, border-color 0.2s, color 0.2s; transition: background 0.2s, border-color 0.2s, color 0.2s;
width: 100%; width: 100%;
@ -313,4 +313,4 @@
</div> </div>
</div> </div>
</body> </body>
</html> </html>

View File

@ -116,6 +116,8 @@ class Addon extends Backend
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) { if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
$this->error(__('Addon name incorrect')); $this->error(__('Addon name incorrect'));
} }
$info = [];
try { try {
$uid = $this->request->post("uid"); $uid = $this->request->post("uid");
$token = $this->request->post("token"); $token = $this->request->post("token");
@ -127,16 +129,13 @@ class Addon extends Backend
'version' => $version, 'version' => $version,
'faversion' => $faversion 'faversion' => $faversion
]; ];
Service::install($name, $force, $extend); $info = Service::install($name, $force, $extend);
$info = get_addon_info($name);
$info['config'] = get_addon_config($name) ? 1 : 0;
$info['state'] = 1;
$this->success(__('Install successful'), null, ['addon' => $info]);
} catch (AddonException $e) { } catch (AddonException $e) {
$this->result($e->getData(), $e->getCode(), __($e->getMessage())); $this->result($e->getData(), $e->getCode(), __($e->getMessage()));
} catch (Exception $e) { } catch (Exception $e) {
$this->error(__($e->getMessage()), $e->getCode()); $this->error(__($e->getMessage()), $e->getCode());
} }
$this->success(__('Install successful'), '', ['addon' => $info]);
} }
/** /**
@ -171,12 +170,12 @@ class Addon extends Backend
Db::execute("DROP TABLE IF EXISTS `{$table}`"); Db::execute("DROP TABLE IF EXISTS `{$table}`");
} }
} }
$this->success(__('Uninstall successful'));
} catch (AddonException $e) { } catch (AddonException $e) {
$this->result($e->getData(), $e->getCode(), __($e->getMessage())); $this->result($e->getData(), $e->getCode(), __($e->getMessage()));
} catch (Exception $e) { } catch (Exception $e) {
$this->error(__($e->getMessage())); $this->error(__($e->getMessage()));
} }
$this->success(__('Uninstall successful'));
} }
/** /**
@ -198,12 +197,12 @@ class Addon extends Backend
//调用启用、禁用的方法 //调用启用、禁用的方法
Service::$action($name, $force); Service::$action($name, $force);
Cache::rm('__menu__'); Cache::rm('__menu__');
$this->success(__('Operate successful'));
} catch (AddonException $e) { } catch (AddonException $e) {
$this->result($e->getData(), $e->getCode(), __($e->getMessage())); $this->result($e->getData(), $e->getCode(), __($e->getMessage()));
} catch (Exception $e) { } catch (Exception $e) {
$this->error(__($e->getMessage())); $this->error(__($e->getMessage()));
} }
$this->success(__('Operate successful'));
} }
/** /**
@ -213,75 +212,27 @@ class Addon extends Backend
{ {
Config::set('default_return_type', 'json'); Config::set('default_return_type', 'json');
$info = [];
$file = $this->request->file('file'); $file = $this->request->file('file');
$addonTmpDir = RUNTIME_PATH . 'addons' . DS; try {
if (!is_dir($addonTmpDir)) { $uid = $this->request->post("uid");
@mkdir($addonTmpDir, 0755, true); $token = $this->request->post("token");
} $faversion = $this->request->post("faversion");
$info = $file->rule('uniqid')->validate(['size' => 10240000, 'ext' => 'zip'])->move($addonTmpDir); if (!$uid || !$token) {
if ($info) { throw new Exception(__('Please login and try to install'));
$tmpName = substr($info->getFilename(), 0, stripos($info->getFilename(), '.'));
$tmpAddonDir = ADDON_PATH . $tmpName . DS;
$tmpFile = $addonTmpDir . $info->getSaveName();
try {
Service::unzip($tmpName);
unset($info);
@unlink($tmpFile);
$infoFile = $tmpAddonDir . 'info.ini';
if (!is_file($infoFile)) {
throw new Exception(__('Addon info file was not found'));
}
$config = Config::parse($infoFile, '', $tmpName);
$name = isset($config['name']) ? $config['name'] : '';
if (!$name) {
throw new Exception(__('Addon info file data incorrect'));
}
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
throw new Exception(__('Addon name incorrect'));
}
$newAddonDir = ADDON_PATH . $name . DS;
if (is_dir($newAddonDir)) {
throw new Exception(__('Addon already exists'));
}
//重命名插件文件夹
rename($tmpAddonDir, $newAddonDir);
try {
//默认禁用该插件
$info = get_addon_info($name);
if ($info['state']) {
$info['state'] = 0;
set_addon_info($name, $info);
}
//执行插件的安装方法
$class = get_addon_class($name);
if (class_exists($class)) {
$addon = new $class();
$addon->install();
}
//导入SQL
Service::importsql($name);
$info['config'] = get_addon_config($name) ? 1 : 0;
$this->success(__('Offline installed tips'), null, ['addon' => $info]);
} catch (Exception $e) {
@rmdirs($newAddonDir);
throw new Exception(__($e->getMessage()));
}
} catch (Exception $e) {
unset($info);
@unlink($tmpFile);
@rmdirs($tmpAddonDir);
$this->error(__($e->getMessage()));
} }
} else { $extend = [
// 上传失败获取错误信息 'uid' => $uid,
$this->error(__($file->getError())); 'token' => $token,
'faversion' => $faversion
];
$info = Service::local($file, $extend);
} catch (AddonException $e) {
$this->result($e->getData(), $e->getCode(), __($e->getMessage()));
} catch (Exception $e) {
$this->error(__($e->getMessage()));
} }
$this->success(__('Offline installed tips'), '', ['addon' => $info]);
} }
/** /**
@ -300,6 +251,8 @@ class Addon extends Backend
if (!is_dir($addonTmpDir)) { if (!is_dir($addonTmpDir)) {
@mkdir($addonTmpDir, 0755, true); @mkdir($addonTmpDir, 0755, true);
} }
$info = [];
try { try {
$uid = $this->request->post("uid"); $uid = $this->request->post("uid");
$token = $this->request->post("token"); $token = $this->request->post("token");
@ -312,14 +265,14 @@ class Addon extends Backend
'faversion' => $faversion 'faversion' => $faversion
]; ];
//调用更新的方法 //调用更新的方法
Service::upgrade($name, $extend); $info = Service::upgrade($name, $extend);
Cache::rm('__menu__'); Cache::rm('__menu__');
$this->success(__('Operate successful'));
} catch (AddonException $e) { } catch (AddonException $e) {
$this->result($e->getData(), $e->getCode(), __($e->getMessage())); $this->result($e->getData(), $e->getCode(), __($e->getMessage()));
} catch (Exception $e) { } catch (Exception $e) {
$this->error(__($e->getMessage())); $this->error(__($e->getMessage()));
} }
$this->success(__('Operate successful'), '', ['addon' => $info]);
} }
/** /**
@ -352,7 +305,7 @@ class Addon extends Backend
$addons = get_addon_list(); $addons = get_addon_list();
$list = []; $list = [];
foreach ($addons as $k => $v) { foreach ($addons as $k => $v) {
if ($search && stripos($v['name'], $search) === false && stripos($v['intro'], $search) === false) { if ($search && stripos($v['name'], $search) === false && stripos($v['title'], $search) === false && stripos($v['intro'], $search) === false) {
continue; continue;
} }
@ -393,6 +346,9 @@ class Addon extends Backend
public function get_table_list() public function get_table_list()
{ {
$name = $this->request->post("name"); $name = $this->request->post("name");
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
$this->error(__('Addon name incorrect'));
}
$tables = get_addon_tables($name); $tables = get_addon_tables($name);
$prefix = Config::get('database.prefix'); $prefix = Config::get('database.prefix');
foreach ($tables as $index => $table) { foreach ($tables as $index => $table) {

View File

@ -190,7 +190,7 @@ class Config extends Backend
} }
file_put_contents( file_put_contents(
APP_PATH . 'extra' . DS . 'site.php', APP_PATH . 'extra' . DS . 'site.php',
'<?php' . "\n\nreturn " . var_export($config, true) . ";" '<?php' . "\n\nreturn " . var_export_short($config) . ";\n"
); );
} }

View File

@ -21,19 +21,21 @@ return [
'Pay tips' => '扫码支付后如果仍然无法立即下载,请不要重复支付,请稍后再重试安装!', 'Pay tips' => '扫码支付后如果仍然无法立即下载,请不要重复支付,请稍后再重试安装!',
'Pay click tips' => '请点击这里在新窗口中进行支付!', 'Pay click tips' => '请点击这里在新窗口中进行支付!',
'Pay new window tips' => '请在新弹出的窗口中进行支付,支付完成后再重新点击安装按钮进行安装!', 'Pay new window tips' => '请在新弹出的窗口中进行支付,支付完成后再重新点击安装按钮进行安装!',
'Upgrade tips' => '确认升级<b>[%s]</b><p class="text-danger">升级后可能出现部分冗余数据记录,请根据需要移除即可!!!</p>如有重要数据请备份后再操作!', 'Upgrade tips' => '确认升级<b>《%s》</b><p class="text-danger">1、请务必做好代码和数据库备份备份备份<br>2、升级后如出现冗余数据请根据需要移除即可!<br>3、不建议在生产环境升级请在本地完成升级测试</p>如有重要数据请备份后再操作!',
'Offline installed tips' => '插件安装成功!清除浏览器缓存和框架缓存后生效!', 'Offline installed tips' => '安装成功!清除浏览器缓存和框架缓存后生效!',
'Online installed tips' => '插件安装成功!清除浏览器缓存和框架缓存后生效!', 'Online installed tips' => '安装成功!清除浏览器缓存和框架缓存后生效!',
'Not login tips' => '你当前未登录FastAdmin登录后将同步已购买的记录下载时无需二次付费', 'Not login tips' => '你当前未登录FastAdmin登录后将同步已购买的记录下载时无需二次付费',
'Please login and try to install' => '请登录后再进行离线安装!',
'Not installed tips' => '请安装后再访问插件前台页面!', 'Not installed tips' => '请安装后再访问插件前台页面!',
'Not enabled tips' => '插件已经禁用,请启用后再访问插件前台页面!', 'Not enabled tips' => '插件已经禁用,请启用后再访问插件前台页面!',
'New version tips' => '发现新版本:%s 点击查看更新日志', 'New version tips' => '发现新版本:%s 点击查看更新日志',
'Store now available tips' => '插件市场暂不可用,是否切换到本地插件?', 'Store now available tips' => '插件市场暂不可用,是否切换到本地插件?',
'Switch to the local' => '切换到本地插件', 'Switch to the local' => '切换到本地插件',
'try to reload' => '重新尝试加载', 'try to reload' => '重新尝试加载',
'Please disable addon first' => '请先禁用插件再进行升级', 'Please disable the add before trying to upgrade' => '请先禁用插件再进行升级',
'Please disable the add before trying to uninstall' => '请先禁用插件再进行卸载',
'Login now' => '立即登录', 'Login now' => '立即登录',
'Continue install' => '不登录,继续安装', 'Continue install' => '继续安装',
'View addon home page' => '查看插件介绍和帮助', 'View addon home page' => '查看插件介绍和帮助',
'View addon index page' => '查看插件前台首页', 'View addon index page' => '查看插件前台首页',
'View addon screenshots' => '点击查看插件截图', 'View addon screenshots' => '点击查看插件截图',
@ -85,10 +87,17 @@ return [
'Addon name incorrect' => '插件名称不正确', 'Addon name incorrect' => '插件名称不正确',
'Addon info file was not found' => '插件配置文件未找到', 'Addon info file was not found' => '插件配置文件未找到',
'Addon info file data incorrect' => '插件配置信息不正确', 'Addon info file data incorrect' => '插件配置信息不正确',
'Addon already exists' => '上传的插件已经存在', 'Addon already exists' => '插件已经存在',
'Addon package download failed' => '插件下载失败',
'Conflicting file found' => '发现冲突文件',
'Invalid addon package' => '未验证的插件',
'No permission to write temporary files' => '没有权限写入临时文件',
'The addon file does not exist' => '插件主启动程序不存在',
'The configuration file content is incorrect' => '配置文件不完整',
'Unable to open the zip file' => '无法打开ZIP文件', 'Unable to open the zip file' => '无法打开ZIP文件',
'Unable to extract the file' => '无法解压ZIP文件', 'Unable to extract the file' => '无法解压ZIP文件',
'Are you sure you want to unstall %s?' => '确认卸载插件《%s》?', 'Unable to open file \'%s\' for writing' => '文件(%s)没有写入权限',
'Are you sure you want to unstall %s?' => '确认卸载<b>《%s》</b>?',
'Delete all the addon file and cannot be recovered!' => '卸载将会删除所有插件文件且不可找回!!!', 'Delete all the addon file and cannot be recovered!' => '卸载将会删除所有插件文件且不可找回!!!',
'Delete all the addon database and cannot be recovered!' => '删除所有插件相关数据表且不可找回!!!', 'Delete all the addon database and cannot be recovered!' => '删除所有插件相关数据表且不可找回!!!',
'Please backup important data manually before uninstall!' => '如有重要数据请备份后再操作!!!', 'Please backup important data manually before uninstall!' => '如有重要数据请备份后再操作!!!',

View File

@ -18,6 +18,10 @@ class AdminLog extends Model
protected static $title = ''; protected static $title = '';
//自定义日志内容 //自定义日志内容
protected static $content = ''; protected static $content = '';
//忽略的链接正则列表
protected static $ignoreRegex = [
'/^(.*)\/(selectpage|index)$/i',
];
public static function setTitle($title) public static function setTitle($title)
{ {
@ -29,26 +33,41 @@ class AdminLog extends Model
self::$content = $content; self::$content = $content;
} }
public static function record($title = '') public static function setIgnoreRegex($regex = [])
{
$regex = is_array($regex) ? $regex : [$regex];
self::$ignoreRegex = array_merge(self::$ignoreRegex, $regex);
}
/**
* 记录日志
* @param string $title
* @param string $content
*/
public static function record($title = '', $content = '')
{ {
$auth = Auth::instance(); $auth = Auth::instance();
$admin_id = $auth->isLogin() ? $auth->id : 0; $admin_id = $auth->isLogin() ? $auth->id : 0;
$username = $auth->isLogin() ? $auth->username : __('Unknown'); $username = $auth->isLogin() ? $auth->username : __('Unknown');
$content = self::$content;
if (!$content) { $controllername = Loader::parseName(request()->controller());
$content = request()->param('', null, 'trim,strip_tags,htmlspecialchars'); $actionname = strtolower(request()->action());
foreach ($content as $k => $v) { $path = str_replace('.', '/', $controllername) . '/' . $actionname;
if (is_string($v) && strlen($v) > 200 || stripos($k, 'password') !== false) { if (self::$ignoreRegex) {
unset($content[$k]); foreach (self::$ignoreRegex as $index => $item) {
if (preg_match($item, $path)) {
return;
} }
} }
} }
$title = self::$title; $content = $content ? $content : self::$content;
if (!$content) {
$content = request()->param('', null, 'trim,strip_tags,htmlspecialchars');
$content = self::getPureContent($content);
}
$title = $title ? $title : self::$title;
if (!$title) { if (!$title) {
$title = []; $title = [];
$controllername = Loader::parseName(request()->controller());
$actionname = strtolower(request()->action());
$path = str_replace('.', '/', $controllername) . '/' . $actionname;
$breadcrumb = Auth::instance()->getBreadcrumb($path); $breadcrumb = Auth::instance()->getBreadcrumb($path);
foreach ($breadcrumb as $k => $v) { foreach ($breadcrumb as $k => $v) {
$title[] = $v['title']; $title[] = $v['title'];
@ -57,7 +76,7 @@ class AdminLog extends Model
} }
self::create([ self::create([
'title' => $title, 'title' => $title,
'content' => !is_scalar($content) ? json_encode($content) : $content, 'content' => !is_scalar($content) ? json_encode($content, JSON_UNESCAPED_UNICODE) : $content,
'url' => substr(request()->url(), 0, 1500), 'url' => substr(request()->url(), 0, 1500),
'admin_id' => $admin_id, 'admin_id' => $admin_id,
'username' => $username, 'username' => $username,
@ -66,6 +85,28 @@ class AdminLog extends Model
]); ]);
} }
/**
* 获取已屏蔽关键信息的数据
* @param $content
* @return false|string
*/
protected static function getPureContent($content)
{
if (!is_array($content)) {
return $content;
}
foreach ($content as $index => &$item) {
if (preg_match("/(password|salt|token)/i", $index)) {
$item = "***";
} else {
if (is_array($item)) {
$item = self::getPureContent($item);
}
}
}
return $content;
}
public function admin() public function admin()
{ {
return $this->belongsTo('Admin', 'admin_id')->setEagerlyType(0); return $this->belongsTo('Admin', 'admin_id')->setEagerlyType(0);

View File

@ -78,18 +78,18 @@
<div class="tab-pane fade active in" id="one"> <div class="tab-pane fade active in" id="one">
<div class="widget-body no-padding"> <div class="widget-body no-padding">
<div id="toolbar" class="toolbar"> <div id="toolbar" class="toolbar">
{:build_toolbar('refresh')} <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" data-force-refresh="false"><i class="fa fa-refresh"></i> </a>
<button type="button" id="faupload-addon" class="btn btn-danger faupload" data-url="addon/local" data-chunking="false" data-mimetype="zip" data-multiple="false"><i class="fa fa-upload"></i> {if $Think.config.fastadmin.api_url}
<button type="button" id="faupload-addon" class="btn btn-danger faupload btn-mini-xs" data-url="addon/local" data-chunking="false" data-mimetype="zip,fastaddon" data-multiple="false"><i class="fa fa-upload"></i>
{:__('Offline install')} {:__('Offline install')}
</button> </button>
{if $Think.config.fastadmin.api_url}
<div class="btn-group"> <div class="btn-group">
<a href="#" class="btn btn-info btn-switch active" data-type="all"><i class="fa fa-list"></i> {:__('All')}</a> <a href="#" class="btn btn-info btn-switch active btn-mini-xs" data-type="all"><i class="fa fa-list"></i> {:__('All')}</a>
<a href="#" class="btn btn-info btn-switch" data-type="free"><i class="fa fa-gift"></i> {:__('Free')}</a> <a href="#" class="btn btn-info btn-switch btn-mini-xs" data-type="free"><i class="fa fa-gift"></i> {:__('Free')}</a>
<a href="#" class="btn btn-info btn-switch" data-type="price"><i class="fa fa-rmb"></i> {:__('Paying')}</a> <a href="#" class="btn btn-info btn-switch btn-mini-xs" data-type="price"><i class="fa fa-rmb"></i> {:__('Paying')}</a>
<a href="#" class="btn btn-info btn-switch" data-type="local" data-url="addon/downloaded"><i class="fa fa-laptop"></i> {:__('Local addon')}</a> <a href="#" class="btn btn-info btn-switch btn-mini-xs" data-type="local" data-url="addon/downloaded"><i class="fa fa-laptop"></i> {:__('Local addon')}</a>
</div> </div>
<a class="btn btn-primary btn-userinfo" href="javascript:;"><i class="fa fa-user"></i> {:__('Userinfo')}</a> <a class="btn btn-primary btn-userinfo btn-mini-xs" href="javascript:;"><i class="fa fa-user"></i> {:__('Userinfo')}</a>
{/if} {/if}
</div> </div>
<table id="table" class="table table-striped table-bordered table-hover" width="100%"> <table id="table" class="table table-striped table-bordered table-hover" width="100%">
@ -208,7 +208,7 @@
</script> </script>
<script id="uninstalltpl" type="text/html"> <script id="uninstalltpl" type="text/html">
<div class=""> <div class="">
<div class=""><%=__("Are you sure you want to unstall %s?", addon['title'])%> <div class=""><%=#__("Are you sure you want to unstall %s?", addon['title'])%>
<p class="text-danger">{:__('Delete all the addon file and cannot be recovered!')} </p> <p class="text-danger">{:__('Delete all the addon file and cannot be recovered!')} </p>
{if config('app_debug')} {if config('app_debug')}
<p class="text-danger"><input type="checkbox" name="droptables" id="droptables" data-name="<%=addon['name']%>"/> {:__('Delete all the addon database and cannot be recovered!')} </p> <p class="text-danger"><input type="checkbox" name="droptables" id="droptables" data-name="<%=addon['name']%>"/> {:__('Delete all the addon database and cannot be recovered!')} </p>

View File

@ -1,17 +1,22 @@
<table class="table table-striped"> <style>
.table-adminlog tr td {
word-break: break-all;
}
</style>
<table class="table table-striped table-adminlog">
<thead> <thead>
<tr> <tr>
<th>{:__('Title')}</th> <th width="100">{:__('Title')}</th>
<th>{:__('Content')}</th> <th>{:__('Content')}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{volist name="row" id="vo" } {volist name="row" id="vo" }
<tr> <tr>
<td>{:__($key)}</td> <td>{:__($key)}</td>
<td>{if $key=='createtime'}{$vo|datetime}{else/}{$vo|htmlentities}{/if}</td> <td>{if $key=='createtime'}{$vo|datetime}{else/}{$vo|htmlentities}{/if}</td>
</tr> </tr>
{/volist} {/volist}
</tbody> </tbody>
</table> </table>
<div class="hide layer-footer"> <div class="hide layer-footer">

View File

@ -9,7 +9,7 @@
<div class="tab-pane fade active in" id="one"> <div class="tab-pane fade active in" id="one">
<div class="widget-body no-padding"> <div class="widget-body no-padding">
<div id="toolbar" class="toolbar"> <div id="toolbar" class="toolbar">
<a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a> <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" data-force-refresh="false"><i class="fa fa-refresh"></i> </a>
<a href="javascript:;" class="btn btn-success btn-add {:$auth->check('auth/rule/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a> <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('auth/rule/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
<a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled {:$auth->check('auth/rule/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-pencil"></i> {:__('Edit')}</a> <a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled {:$auth->check('auth/rule/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-pencil"></i> {:__('Edit')}</a>
<a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('auth/rule/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a> <a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('auth/rule/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
@ -22,9 +22,9 @@
</div> </div>
<a href="javascript:;" class="btn btn-danger btn-toggle-all"><i class="fa fa-plus"></i> {:__('Toggle all')}</a> <a href="javascript:;" class="btn btn-danger btn-toggle-all"><i class="fa fa-plus"></i> {:__('Toggle all')}</a>
</div> </div>
<table id="table" class="table table-bordered table-hover" <table id="table" class="table table-bordered table-hover"
data-operate-edit="{:$auth->check('auth/rule/edit')}" data-operate-edit="{:$auth->check('auth/rule/edit')}"
data-operate-del="{:$auth->check('auth/rule/del')}" data-operate-del="{:$auth->check('auth/rule/del')}"
width="100%"> width="100%">
</table> </table>
</div> </div>

View File

@ -2,6 +2,8 @@
// 公共助手函数 // 公共助手函数
use Symfony\Component\VarExporter\VarExporter;
if (!function_exists('__')) { if (!function_exists('__')) {
/** /**
@ -264,29 +266,12 @@ if (!function_exists('var_export_short')) {
/** /**
* 返回打印数组结构 * 返回打印数组结构
* @param string $var 数组 * @param string $var 数组
* @param string $indent 缩进字符
* @return string * @return string
*/ */
function var_export_short($var, $indent = "") function var_export_short($var)
{ {
switch (gettype($var)) { return VarExporter::export($var);
case "string":
return '"' . addcslashes($var, "\\\$\"\r\n\t\v\f") . '"';
case "array":
$indexed = array_keys($var) === range(0, count($var) - 1);
$r = [];
foreach ($var as $key => $value) {
$r[] = "$indent "
. ($indexed ? "" : var_export_short($key) . " => ")
. var_export_short($value, "$indent ");
}
return "[\n" . implode(",\n", $r) . "\n" . $indent . "]";
case "boolean":
return $var ? "TRUE" : "FALSE";
default:
return var_export($var, true);
}
} }
} }

View File

@ -277,7 +277,7 @@ class Backend extends Controller
$bind = []; $bind = [];
$name = ''; $name = '';
$aliasName = ''; $aliasName = '';
if (!empty($this->model)) { if (!empty($this->model) && $this->relationSearch) {
$name = $this->model->getTable(); $name = $this->model->getTable();
$alias[$name] = Loader::parseName(basename(str_replace('\\', '/', get_class($this->model)))); $alias[$name] = Loader::parseName(basename(str_replace('\\', '/', get_class($this->model))));
$aliasName = $alias[$name] . '.'; $aliasName = $alias[$name] . '.';

View File

@ -4,6 +4,8 @@ namespace app\common\library;
use app\admin\model\AuthRule; use app\admin\model\AuthRule;
use fast\Tree; use fast\Tree;
use think\addons\Service;
use think\Db;
use think\Exception; use think\Exception;
use think\exception\PDOException; use think\exception\PDOException;
@ -15,32 +17,16 @@ class Menu
* @param array $menu * @param array $menu
* @param mixed $parent 父类的name或pid * @param mixed $parent 父类的name或pid
*/ */
public static function create($menu, $parent = 0) public static function create($menu = [], $parent = 0)
{ {
if (!is_numeric($parent)) { $old = [];
$parentRule = AuthRule::getByName($parent); self::menuUpdate($menu, $old, $parent);
$pid = $parentRule ? $parentRule['id'] : 0;
} else {
$pid = $parent;
}
$allow = array_flip(['file', 'name', 'title', 'icon', 'condition', 'remark', 'ismenu', 'weigh']);
foreach ($menu as $k => $v) {
$hasChild = isset($v['sublist']) && $v['sublist'] ? true : false;
$data = array_intersect_key($v, $allow); //菜单刷新处理
$info = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
$data['ismenu'] = isset($data['ismenu']) ? $data['ismenu'] : ($hasChild ? 1 : 0); preg_match('/addons\\\\([a-z0-9]+)\\\\/i', $info['class'], $matches);
$data['icon'] = isset($data['icon']) ? $data['icon'] : ($hasChild ? 'fa fa-list' : 'fa fa-circle-o'); if ($matches && isset($matches[1])) {
$data['pid'] = $pid; Menu::refresh($matches[1], $menu);
$data['status'] = 'normal';
try {
$menu = AuthRule::create($data);
if ($hasChild) {
self::create($v['sublist'], $menu->id);
}
} catch (PDOException $e) {
throw new Exception($e->getMessage());
}
} }
} }
@ -97,13 +83,70 @@ class Menu
*/ */
public static function upgrade($name, $menu) public static function upgrade($name, $menu)
{ {
$old = AuthRule::where('name', 'like', "{$name}%")->select(); $ids = self::getAuthRuleIdsByName($name);
$old = AuthRule::where('id', 'in', $ids)->select();
$old = collection($old)->toArray(); $old = collection($old)->toArray();
$old = array_column($old, null, 'name'); $old = array_column($old, null, 'name');
self::menuUpdate($menu, $old);
Db::startTrans();
try {
self::menuUpdate($menu, $old);
$ids = [];
foreach ($old as $index => $item) {
if (!isset($item['keep'])) {
$ids[] = $item['id'];
}
}
if ($ids) {
//旧版本的菜单需要做删除处理
$config = Service::config($name);
$menus = isset($config['menus']) ? $config['menus'] : [];
$where = ['id' => ['in', $ids]];
if ($menus) {
//必须是旧版本中的菜单,可排除用户自主创建的菜单
$where['name'] = ['in', $menus];
}
AuthRule::where($where)->delete();
}
Db::commit();
} catch (PDOException $e) {
Db::rollback();
return false;
}
Menu::refresh($name, $menu);
return true; return true;
} }
/**
* 刷新插件菜单配置缓存
* @param string $name
* @param array $menu
*/
public static function refresh($name, $menu = [])
{
if (!$menu) {
// $menu为空时表示首次安装首次安装需刷新插件菜单标识缓存
$menuIds = Menu::getAuthRuleIdsByName($name);
$menus = Db::name("auth_rule")->where('id', 'in', $menuIds)->column('name');
} else {
// 刷新新的菜单缓存
$getMenus = function ($menu) use (&$getMenus) {
$result = [];
foreach ($menu as $index => $item) {
$result[] = $item['name'];
$result = array_merge($result, isset($item['sublist']) && is_array($item['sublist']) ? $getMenus($item['sublist']) : []);
}
return $result;
};
$menus = $getMenus($menu);
}
//刷新新的插件核心菜单缓存
Service::config($name, ['menus' => $menus]);
}
/** /**
* 导出指定名称的菜单规则 * 导出指定名称的菜单规则
* @param string $name * @param string $name
@ -131,7 +174,7 @@ class Menu
* @param int $parent * @param int $parent
* @throws Exception * @throws Exception
*/ */
private static function menuUpdate($newMenu, $oldMenu, $parent = 0) private static function menuUpdate($newMenu, &$oldMenu, $parent = 0)
{ {
if (!is_numeric($parent)) { if (!is_numeric($parent)) {
$parentRule = AuthRule::getByName($parent); $parentRule = AuthRule::getByName($parent);
@ -147,17 +190,16 @@ class Menu
$data['icon'] = isset($data['icon']) ? $data['icon'] : ($hasChild ? 'fa fa-list' : 'fa fa-circle-o'); $data['icon'] = isset($data['icon']) ? $data['icon'] : ($hasChild ? 'fa fa-list' : 'fa fa-circle-o');
$data['pid'] = $pid; $data['pid'] = $pid;
$data['status'] = 'normal'; $data['status'] = 'normal';
try { if (!isset($oldMenu[$data['name']])) {
if (!isset($oldMenu[$data['name']])) { $menu = AuthRule::create($data);
$menu = AuthRule::create($data); } else {
} else { $menu = $oldMenu[$data['name']];
$menu = $oldMenu[$data['name']]; //更新旧菜单
} AuthRule::update($data, ['id' => $menu['id']]);
if ($hasChild) { $oldMenu[$data['name']]['keep'] = true;
self::menuUpdate($v['sublist'], $oldMenu, $menu['id']); }
} if ($hasChild) {
} catch (PDOException $e) { self::menuUpdate($v['sublist'], $oldMenu, $menu['id']);
throw new Exception($e->getMessage());
} }
} }
} }

View File

@ -311,6 +311,9 @@ class Upload
$sourceFile = $this->file->getRealPath() ?: $this->file->getPathname(); $sourceFile = $this->file->getRealPath() ?: $this->file->getPathname();
$info = $this->file->getInfo(); $info = $this->file->getInfo();
$this->file = null; $this->file = null;
if (!is_dir($destDir)) {
@mkdir($destDir, 0755, true);
}
rename($sourceFile, $destFile); rename($sourceFile, $destFile);
$file = new File($destFile); $file = new File($destFile);
$file->setSaveName($fileName)->setUploadInfo($info); $file->setSaveName($fileName)->setUploadInfo($info);

View File

@ -279,6 +279,14 @@ return [
'adminskin' => '', 'adminskin' => '',
//后台是否启用面包屑 //后台是否启用面包屑
'breadcrumb' => false, 'breadcrumb' => false,
//是否允许未知来源的插件压缩包
'unknownsources' => true,
//插件启用禁用时是否备份对应的全局文件
'backup_global_files' => true,
//是否开启后台自动日志记录
'auto_record_log' => true,
//插件纯净模式插件启用后是否删除插件目录的application、public和assets文件夹
'addon_pure_mode' => true,
//允许跨域的域名,多个以,分隔 //允许跨域的域名,多个以,分隔
'cors_request_domain' => 'localhost,127.0.0.1', 'cors_request_domain' => 'localhost,127.0.0.1',
//版本号 //版本号

View File

@ -1,14 +1,8 @@
<?php <?php
return array ( return [
'autoload' => false, 'autoload' => false,
'hooks' => 'hooks' => [],
array ( 'route' => [],
), 'priority' => [],
'route' => ];
array (
),
'priority' =>
array (
),
);

View File

@ -1,38 +1,35 @@
<?php <?php
return array ( return [
'name' => '我的网站', 'name' => '我的网站',
'beian' => '', 'beian' => '',
'cdnurl' => '', 'cdnurl' => '',
'version' => '1.0.1', 'version' => '1.0.1',
'timezone' => 'Asia/Shanghai', 'timezone' => 'Asia/Shanghai',
'forbiddenip' => '', 'forbiddenip' => '',
'languages' => 'languages' => [
array ( 'backend' => 'zh-cn',
'backend' => 'zh-cn', 'frontend' => 'zh-cn',
'frontend' => 'zh-cn', ],
), 'fixedpage' => 'dashboard',
'fixedpage' => 'dashboard', 'categorytype' => [
'categorytype' => 'default' => 'Default',
array ( 'page' => 'Page',
'default' => 'Default', 'article' => 'Article',
'page' => 'Page', 'test' => 'Test',
'article' => 'Article', ],
'test' => 'Test', 'configgroup' => [
), 'basic' => 'Basic',
'configgroup' => 'email' => 'Email',
array ( 'dictionary' => 'Dictionary',
'basic' => 'Basic', 'user' => 'User',
'email' => 'Email', 'example' => 'Example',
'dictionary' => 'Dictionary', ],
'user' => 'User', 'mail_type' => '1',
'example' => 'Example', 'mail_smtp_host' => 'smtp.qq.com',
), 'mail_smtp_port' => '465',
'mail_type' => '1', 'mail_smtp_user' => '10000',
'mail_smtp_host' => 'smtp.qq.com', 'mail_smtp_pass' => 'password',
'mail_smtp_port' => '465', 'mail_verify_type' => '2',
'mail_smtp_user' => '10000', 'mail_from' => '10000@qq.com',
'mail_smtp_pass' => 'password', ];
'mail_verify_type' => '2',
'mail_from' => '10000@qq.com',
);

View File

@ -19,10 +19,12 @@
"topthink/framework": "~5.0.24", "topthink/framework": "~5.0.24",
"topthink/think-captcha": "^1.0", "topthink/think-captcha": "^1.0",
"phpmailer/phpmailer": "~6.1.6", "phpmailer/phpmailer": "~6.1.6",
"karsonzhang/fastadmin-addons": "~1.2.0", "karsonzhang/fastadmin-addons": "~1.2.4",
"overtrue/pinyin": "~3.0", "overtrue/pinyin": "~3.0",
"phpoffice/phpspreadsheet": "^1.2", "phpoffice/phpspreadsheet": "^1.2",
"overtrue/wechat": "4.2.11", "overtrue/wechat": "4.2.11",
"nelexa/zip": "^3.3",
"symfony/var-exporter ": "^4.4.13",
"ext-json": "*", "ext-json": "*",
"ext-curl": "*" "ext-curl": "*"
}, },

View File

@ -1008,7 +1008,7 @@ table.table-nowrap thead > tr > th {
.fixed-table-toolbar .toolbar a.btn-import, .fixed-table-toolbar .toolbar a.btn-import,
.fixed-table-toolbar .toolbar a.btn-more, .fixed-table-toolbar .toolbar a.btn-more,
.fixed-table-toolbar .toolbar a.btn-recyclebin, .fixed-table-toolbar .toolbar a.btn-recyclebin,
.fixed-table-toolbar .toolbar a.btn-mini-xs { .fixed-table-toolbar .toolbar .btn-mini-xs {
font-size: 0; font-size: 0;
} }
.fixed-table-toolbar .toolbar a.btn-refresh .fa, .fixed-table-toolbar .toolbar a.btn-refresh .fa,
@ -1018,7 +1018,7 @@ table.table-nowrap thead > tr > th {
.fixed-table-toolbar .toolbar a.btn-import .fa, .fixed-table-toolbar .toolbar a.btn-import .fa,
.fixed-table-toolbar .toolbar a.btn-more .fa, .fixed-table-toolbar .toolbar a.btn-more .fa,
.fixed-table-toolbar .toolbar a.btn-recyclebin .fa, .fixed-table-toolbar .toolbar a.btn-recyclebin .fa,
.fixed-table-toolbar .toolbar a.btn-mini-xs .fa { .fixed-table-toolbar .toolbar .btn-mini-xs .fa {
font-size: initial; font-size: initial;
} }
.fixed-table-toolbar .search { .fixed-table-toolbar .search {

File diff suppressed because one or more lines are too long

View File

@ -1,38 +1,3 @@
define([], function () { define([], function () {
require.config({
paths: {
'jquery-colorpicker': '../addons/cms/js/jquery.colorpicker.min',
'jquery-autocomplete': '../addons/cms/js/jquery.autocomplete',
'jquery-tagsinput': '../addons/cms/js/jquery.tagsinput',
'clipboard': '../addons/cms/js/clipboard.min',
},
shim: {
'jquery-colorpicker': {
deps: ['jquery'],
exports: '$.fn.extend'
},
'jquery-autocomplete': {
deps: ['jquery'],
exports: '$.fn.extend'
},
'jquery-tagsinput': {
deps: ['jquery', 'jquery-autocomplete', 'css!../addons/cms/css/jquery.tagsinput.min.css'],
exports: '$.fn.extend'
}
}
});
require.config({
paths: {
'async': '../addons/example/js/async',
'BMap': ['//api.map.baidu.com/api?v=2.0&ak=mXijumfojHnAaN2VxpBGoqHM'],
},
shim: {
'BMap': {
deps: ['jquery'],
exports: 'BMap'
}
}
});
}); });

View File

@ -31,6 +31,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
btn: [__('Switch to the local'), __('Try to reload')] btn: [__('Switch to the local'), __('Try to reload')]
}, function (index) { }, function (index) {
layer.close(index); layer.close(index);
$(".panel .nav-tabs").hide();
$(".toolbar > *:not(:first)").hide();
$(".btn-switch[data-type='local']").trigger("click"); $(".btn-switch[data-type='local']").trigger("click");
}, function (index) { }, function (index) {
layer.close(index); layer.close(index);
@ -62,6 +64,15 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
Template.helper("Moment", Moment); Template.helper("Moment", Moment);
Template.helper("addons", Config['addons']); Template.helper("addons", Config['addons']);
$("#faupload-addon").data("params", function () {
var userinfo = Controller.api.userinfo.get();
return {
uid: userinfo ? userinfo.id : '',
token: userinfo ? userinfo.token : '',
version: Config.faversion
};
});
// 初始化表格 // 初始化表格
table.bootstrapTable({ table.bootstrapTable({
url: $.fn.bootstrapTable.defaults.extend.index_url, url: $.fn.bootstrapTable.defaults.extend.index_url,
@ -168,6 +179,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
Config['addons'][data.addon.name] = data.addon; Config['addons'][data.addon.name] = data.addon;
Toastr.success(ret.msg); Toastr.success(ret.msg);
operate(data.addon.name, 'enable', false); operate(data.addon.name, 'enable', false);
return false;
}); });
}); });
@ -220,12 +232,13 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
// 会员信息 // 会员信息
$(document).on("click", ".btn-userinfo", function () { $(document).on("click", ".btn-userinfo", function () {
var that = this; var that = this;
var area = [$(window).width() > 800 ? '500px' : '95%', $(window).height() > 600 ? '400px' : '95%'];
var userinfo = Controller.api.userinfo.get(); var userinfo = Controller.api.userinfo.get();
if (!userinfo) { if (!userinfo) {
Layer.open({ Layer.open({
content: Template("logintpl", {}), content: Template("logintpl", {}),
zIndex: 99, zIndex: 99,
area: [$(window).width() > 800 ? '500px' : '95%', $(window).height() > 600 ? '400px' : '95%'], area: area,
title: __('Login FastAdmin'), title: __('Login FastAdmin'),
resize: false, resize: false,
btn: [__('Login'), __('Register')], btn: [__('Login'), __('Register')],
@ -315,8 +328,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
title: __('Warning'), title: __('Warning'),
icon: 1 icon: 1
}); });
$('.btn-refresh').trigger('click'); Controller.api.refresh(table, name);
Fast.api.refreshmenu();
}, function (data, ret) { }, function (data, ret) {
//如果是需要购买的插件则弹出二维码提示 //如果是需要购买的插件则弹出二维码提示
if (ret && ret.code === -1) { if (ret && ret.code === -1) {
@ -377,8 +389,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
}, function (data, ret) { }, function (data, ret) {
delete Config['addons'][name]; delete Config['addons'][name];
Layer.closeAll(); Layer.closeAll();
$('.btn-refresh').trigger('click'); Controller.api.refresh(table, name);
Fast.api.refreshmenu();
}, function (data, ret) { }, function (data, ret) {
if (ret && ret.code === -3) { if (ret && ret.code === -3) {
//插件目录发现影响全局的文件 //插件目录发现影响全局的文件
@ -411,8 +422,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
var addon = Config['addons'][name]; var addon = Config['addons'][name];
addon.state = action === 'enable' ? 1 : 0; addon.state = action === 'enable' ? 1 : 0;
Layer.closeAll(); Layer.closeAll();
$('.btn-refresh').trigger('click'); Controller.api.refresh(table, name);
Fast.api.refreshmenu();
}, function (data, ret) { }, function (data, ret) {
if (ret && ret.code === -3) { if (ret && ret.code === -3) {
//插件目录发现影响全局的文件 //插件目录发现影响全局的文件
@ -445,10 +455,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
url: 'addon/upgrade', url: 'addon/upgrade',
data: {name: name, uid: uid, token: token, version: version, faversion: Config.faversion} data: {name: name, uid: uid, token: token, version: version, faversion: Config.faversion}
}, function (data, ret) { }, function (data, ret) {
Config['addons'][name].version = version; Config['addons'][name] = data.addon;
Layer.closeAll(); Layer.closeAll();
$('.btn-refresh').trigger('click'); Controller.api.refresh(table, name);
Fast.api.refreshmenu();
}, function (data, ret) { }, function (data, ret) {
Layer.alert(ret.msg); Layer.alert(ret.msg);
return false; return false;
@ -483,7 +492,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
$(document).on("click", ".btn-uninstall", function () { $(document).on("click", ".btn-uninstall", function () {
var name = $(this).closest(".operate").data('name'); var name = $(this).closest(".operate").data('name');
if (Config['addons'][name].state == 1) { if (Config['addons'][name].state == 1) {
Layer.alert(__('Please disable addon first'), {icon: 7}); Layer.alert(__('Please disable the add before trying to uninstall'), {icon: 7});
return false; return false;
} }
Template.helper("__", __); Template.helper("__", __);
@ -509,7 +518,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
$(document).on("click", ".btn-upgrade", function () { $(document).on("click", ".btn-upgrade", function () {
var name = $(this).closest(".operate").data('name'); var name = $(this).closest(".operate").data('name');
if (Config['addons'][name].state == 1) { if (Config['addons'][name].state == 1) {
Layer.alert(__('Please disable addon first'), {icon: 7}); Layer.alert(__('Please disable the add before trying to upgrade'), {icon: 7});
return false; return false;
} }
var version = $(this).data("version"); var version = $(this).data("version");
@ -602,6 +611,20 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function
localStorage.removeItem("fastadmin_userinfo"); localStorage.removeItem("fastadmin_userinfo");
} }
} }
},
refresh: function (table, name) {
//刷新左侧边栏
Fast.api.refreshmenu();
//刷新行数据
if ($(".operate[data-name='" + name + "']").length > 0) {
var index = $(".operate[data-name='" + name + "']").closest("tr[data-index]").data("index");
var row = Table.api.getrowbyindex(table, index);
row.addon = typeof Config['addons'][name] !== 'undefined' ? Config['addons'][name] : undefined;
table.bootstrapTable("updateRow", {index: index, row: row});
} else if ($(".btn-switch.active").data("type") == "local") {
$(".btn-refresh").trigger("click");
}
} }
} }
}; };

View File

@ -116,7 +116,8 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], functi
$(document).on('refresh', '.sidebar-menu', function () { $(document).on('refresh', '.sidebar-menu', function () {
Fast.api.ajax({ Fast.api.ajax({
url: 'index/index', url: 'index/index',
data: {action: 'refreshmenu'} data: {action: 'refreshmenu'},
loading: false
}, function (data) { }, function (data) {
$(".sidebar-menu li:not([data-rel='external'])").remove(); $(".sidebar-menu li:not([data-rel='external'])").remove();
$(".sidebar-menu").prepend(data.menulist); $(".sidebar-menu").prepend(data.menulist);

File diff suppressed because one or more lines are too long

View File

@ -1243,7 +1243,7 @@ table.table-nowrap {
} }
.toolbar { .toolbar {
a.btn-refresh, a.btn-del, a.btn-add, a.btn-edit, a.btn-import, a.btn-more, a.btn-recyclebin, a.btn-mini-xs { a.btn-refresh, a.btn-del, a.btn-add, a.btn-edit, a.btn-import, a.btn-more, a.btn-recyclebin, .btn-mini-xs {
font-size: 0; font-size: 0;
.fa { .fa {