主题可在线安装以及更新(基于git)

pull/33/merge
ruibaby 2018-08-28 19:19:40 +08:00
parent 42ee51cfff
commit 8775511ed9
6 changed files with 360 additions and 74 deletions

View File

@ -64,10 +64,10 @@ public class ListPage<T> {
this.nowPage = nowPage; this.nowPage = nowPage;
this.totalCount = data.size(); this.totalCount = data.size();
this.totalPage = (totalCount + pageSize - 1) / pageSize; this.totalPage = (totalCount + pageSize - 1) / pageSize;
this.prePage = nowPage-1>1? nowPage-1:1; this.prePage = nowPage - 1 > 1 ? nowPage - 1 : 1;
this.nextPage = nowPage>=totalPage? totalPage: nowPage + 1; this.nextPage = nowPage >= totalPage ? totalPage : nowPage + 1;
this.hasPrevious = nowPage!=prePage; this.hasPrevious = nowPage != prePage;
this.hasNext = nowPage!=nextPage; this.hasNext = nowPage != nextPage;
} }
/** /**
@ -80,7 +80,7 @@ public class ListPage<T> {
if (fromIndex >= data.size()) { if (fromIndex >= data.size()) {
return Collections.emptyList(); return Collections.emptyList();
} }
if(fromIndex<0){ if (fromIndex < 0) {
return Collections.emptyList(); return Collections.emptyList();
} }
int toIndex = nowPage * pageSize; int toIndex = nowPage * pageSize;

View File

@ -26,4 +26,9 @@ public class Theme implements Serializable {
* *
*/ */
private boolean hasOptions; private boolean hasOptions;
/**
*
*/
private boolean hasUpdate;
} }

View File

@ -170,6 +170,12 @@ public class HaloUtils {
} else { } else {
theme.setHasOptions(false); theme.setHasOptions(false);
} }
File gitPath = new File(themesPath.getAbsolutePath(), file.getName() + "/.git");
if (gitPath.exists()) {
theme.setHasUpdate(true);
} else {
theme.setHasUpdate(false);
}
themes.add(theme); themes.add(theme);
} }
} }

View File

@ -14,6 +14,7 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileReader; import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.io.file.FileWriter; import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.util.RuntimeUtil;
import cn.hutool.core.util.ZipUtil; import cn.hutool.core.util.ZipUtil;
import cn.hutool.extra.servlet.ServletUtil; import cn.hutool.extra.servlet.ServletUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -29,6 +30,7 @@ import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.websocket.server.PathParam; import javax.websocket.server.PathParam;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.util.List; import java.util.List;
/** /**
@ -50,6 +52,8 @@ public class ThemeController extends BaseController {
@Autowired @Autowired
private LogsService logsService; private LogsService logsService;
private static final String NOT_FOUND_GIT = "-bash: git: command not found";
/** /**
* *
* *
@ -149,6 +153,71 @@ public class ThemeController extends BaseController {
return "redirect:/admin/themes"; return "redirect:/admin/themes";
} }
/**
*
*
* @return admin/widget/_theme-install
*/
@GetMapping(value = "/install")
public String install() {
return "admin/widget/_theme-install";
}
/**
* 线
*
* @param remoteAddr
* @param themeName
* @return JsonResult
*/
@PostMapping(value = "/clone")
@ResponseBody
public JsonResult cloneFromRemote(@RequestParam(value = "remoteAddr") String remoteAddr,
@RequestParam(value = "themeName") String themeName) {
if (StringUtils.isBlank(remoteAddr) || StringUtils.isBlank(themeName)) {
return new JsonResult(0, "请输入完整信息!");
}
try {
File basePath = new File(ResourceUtils.getURL("classpath:").getPath());
File themePath = new File(basePath.getAbsolutePath(), "templates/themes");
String cmdResult = RuntimeUtil.execForStr("git clone " + remoteAddr + " " + themePath.getAbsolutePath() + "/" + themeName);
if (NOT_FOUND_GIT.equals(cmdResult)) {
return new JsonResult(0, "没有安装Git");
}
HaloConst.THEMES.clear();
HaloConst.THEMES = HaloUtils.getThemes();
} catch (FileNotFoundException e) {
log.error("克隆主题失败:{}", e.getMessage());
return new JsonResult(0, "克隆主题失败:" + e.getMessage());
}
return new JsonResult(1, "安装成功!");
}
/**
*
*
* @param themeName
* @return JsonResult
*/
@GetMapping(value = "/pull")
@ResponseBody
public JsonResult pullFromRemote(@RequestParam(value = "themeName") String themeName) {
try {
File basePath = new File(ResourceUtils.getURL("classpath:").getPath());
File themePath = new File(basePath.getAbsolutePath(), "templates/themes");
String cmdResult = RuntimeUtil.execForStr("cd " + themePath.getAbsolutePath() + "/" + themeName + " && git pull");
if (NOT_FOUND_GIT.equals(cmdResult)) {
return new JsonResult(0, "没有安装Git");
}
HaloConst.THEMES.clear();
HaloConst.THEMES = HaloUtils.getThemes();
} catch (Exception e) {
log.error("更新主题失败:{}", e.getMessage());
return new JsonResult(0, "更新主题失败:" + e.getMessage());
}
return new JsonResult(1, "更新成功!");
}
/** /**
* *
* *

View File

@ -60,8 +60,8 @@
</style> </style>
<section class="content-header"> <section class="content-header">
<h1 style="display: inline-block;">主题管理</h1> <h1 style="display: inline-block;">主题管理</h1>
<a id="showForm" href="#"> <a id="showForm" href="#" onclick="openThemeInstall()">
<i class="fa fa-cloud-upload" aria-hidden="true"></i>上传 <i class="fa fa-cloud-upload" aria-hidden="true"></i>安装主题
</a> </a>
<ol class="breadcrumb"> <ol class="breadcrumb">
<li><a data-pjax="true" href="/admin"><i class="fa fa-dashboard"></i> 首页</a></li> <li><a data-pjax="true" href="/admin"><i class="fa fa-dashboard"></i> 首页</a></li>
@ -70,15 +70,6 @@
</ol> </ol>
</section> </section>
<section class="content container-fluid"> <section class="content container-fluid">
<div class="row" id="uploadForm">
<div class="col-md-12">
<div class="form-group">
<div class="file-loading">
<input id="uploadTheme" class="file-loading" type="file" name="file" multiple>
</div>
</div>
</div>
</div>
<div class="row"> <div class="row">
<#if themes?? && (themes?size>0)> <#if themes?? && (themes?size>0)>
<#list themes as theme> <#list themes as theme>
@ -89,9 +80,12 @@
</div> </div>
<div class="box-footer"> <div class="box-footer">
<span class="theme-title">${theme.themeName?if_exists?upper_case}</span> <span class="theme-title">${theme.themeName?if_exists?upper_case}</span>
<#if theme.hasOptions==true> <#if theme.hasOptions>
<button class="btn btn-primary btn-sm pull-right btn-theme-setting" onclick="openSetting('${theme.themeName?if_exists}')" style="display: none">设置</button> <button class="btn btn-primary btn-sm pull-right btn-theme-setting" onclick="openSetting('${theme.themeName?if_exists}')" style="display: none">设置</button>
</#if> </#if>
<#if theme.hasUpdate>
<button class="btn btn-warning btn-sm pull-right btn-theme-update" data-loading-text="更新中..." onclick="updateTheme('${theme.themeName?if_exists}',this)" style="display: none;margin-right: 3px">更新</button>
</#if>
<#if activeTheme != "${theme.themeName}"> <#if activeTheme != "${theme.themeName}">
<button class="btn btn-default btn-sm pull-right btn-theme-enable" onclick="setTheme('${theme.themeName?if_exists}')" style="display: none;margin-right: 3px">启用</button> <button class="btn btn-default btn-sm pull-right btn-theme-enable" onclick="setTheme('${theme.themeName?if_exists}')" style="display: none;margin-right: 3px">启用</button>
<#else> <#else>
@ -133,63 +127,26 @@
</div> </div>
</div> </div>
<script type="application/javascript"> <script type="application/javascript">
function loadFileInput() { /**
$('#uploadTheme').fileinput({ * 打开安装主题的窗口
language: 'zh', */
uploadUrl: '/admin/themes/upload', function openThemeInstall() {
allowedFileExtensions: ['zip','jpg'], layer.open({
maxFileCount: 1, type: 2,
enctype: 'multipart/form-data', title: '安装主题',
dropZoneTitle: '拖拽主题压缩包到这里 &hellip;<br>不支持多个主题同时上传', shadeClose: true,
showClose: false shade: 0.5,
}).on("fileuploaded",function (event,data,previewId,index) { maxmin: true,
var data = data.jqXHR.responseJSON; area: ['90%', '90%'],
if(data.code==1){ content: '/admin/themes/install',
$("#uploadForm").hide(400); scrollbar: false
$.toast({
text: data.msg,
heading: '提示',
icon: 'success',
showHideTransition: 'fade',
allowToastClose: true,
hideAfter: 1000,
stack: 1,
position: 'top-center',
textAlign: 'left',
loader: true,
loaderBg: '#ffffff',
afterHidden: function () {
window.location.reload();
}
});
}else{
$.toast({
text: data.msg,
heading: '提示',
icon: 'error',
showHideTransition: 'fade',
allowToastClose: true,
hideAfter: 1000,
stack: 1,
position: 'top-center',
textAlign: 'left',
loader: true,
loaderBg: '#ffffff'
});
}
}); });
} }
$(document).ready(function () {
loadFileInput(); /**
}); * 设置主题
<#if options.admin_pjax?default("true") == "true"> * @param site_theme 主题名
$(document).on('pjax:complete',function () { */
loadFileInput();
});
</#if>
$("#showForm").click(function(){
$("#uploadForm").slideToggle(400);
});
function setTheme(site_theme) { function setTheme(site_theme) {
$.ajax({ $.ajax({
type: 'get', type: 'get',
@ -233,6 +190,61 @@
} }
}); });
} }
/**
* 更新主题
*/
function updateTheme(theme,e) {
$(e).button('loading');
$.ajax({
type: 'get',
url: '/admin/themes/pull',
data: {
'themeName': theme
},
success: function (data) {
if(data.code==1){
$.toast({
text: data.msg,
heading: '提示',
icon: 'success',
showHideTransition: 'fade',
allowToastClose: true,
hideAfter: 1000,
stack: 1,
position: 'top-center',
textAlign: 'left',
loader: true,
loaderBg: '#ffffff',
afterHidden: function () {
window.location.reload();
}
});
}else{
$.toast({
text: data.msg,
heading: '提示',
icon: 'error',
showHideTransition: 'fade',
allowToastClose: true,
hideAfter: 2000,
stack: 1,
position: 'top-center',
textAlign: 'left',
loader: true,
loaderBg: '#ffffff'
});
$(e).button('reset');
}
}
});
}
/**
* 打开主题设置
*
* @param theme 主题名
*/
function openSetting(theme) { function openSetting(theme) {
layer.open({ layer.open({
type: 2, type: 2,
@ -253,11 +265,11 @@
}); });
$('.theme-body').mouseover(function () { $('.theme-body').mouseover(function () {
$(this).find(".theme-thumbnail").css("opacity","0.8"); $(this).find(".theme-thumbnail").css("opacity","0.8");
$(this).find(".btn-theme-setting,.btn-theme-enable").show(); $(this).find(".btn-theme-setting,.btn-theme-enable,.btn-theme-update").show();
}); });
$('.theme-body').mouseleave(function () { $('.theme-body').mouseleave(function () {
$(this).find(".theme-thumbnail").css("opacity","1"); $(this).find(".theme-thumbnail").css("opacity","1");
$(this).find(".btn-theme-setting,.btn-theme-enable").hide(); $(this).find(".btn-theme-setting,.btn-theme-enable,.btn-theme-update").hide();
}); });
function modelShow(url) { function modelShow(url) {
$('#url').val(url); $('#url').val(url);

View File

@ -0,0 +1,194 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<link rel="stylesheet" href="/static/plugins/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/plugins/toast/css/jquery.toast.min.css">
<link rel="stylesheet" href="/static/plugins/fileinput/fileinput.min.css">
<link rel="stylesheet" href="/static/css/AdminLTE.min.css">
<style type="text/css" rel="stylesheet">
.form-horizontal .control-label{
text-align: left;
}
.alert-info{
color: #31708f!important;
background-color: #d9edf7!important;
border-color: #bce8f1!important;
}
</style>
</head>
<body>
<div class="container-fluid">
<section class="content">
<div class="nav-tabs-custom">
<ul class="nav nav-tabs">
<li class="active">
<a href="#upload" data-toggle="tab">本地上传</a>
</li>
<li>
<a href="#clone" data-toggle="tab">远程拉取</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="upload">
<div class="row" id="uploadForm">
<div class="col-md-12">
<div class="form-group">
<div class="file-loading">
<input id="uploadTheme" class="file-loading" type="file" name="file" multiple>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="clone">
<form method="post" class="form-horizontal" id="pullForm">
<div class="box-body">
<div class="alert alert-info alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<strong>注意!</strong> 使用该功能必须安装Git否则无法使用。更多主题请点击<a href="https://gitee.com/babyrui" class="alert-link">https://gitee.com/babyrui</a>.
</div>
<div class="form-group">
<label for="remoteAddr" class="col-lg-2 col-sm-4 control-label">远程地址:</label>
<div class="col-lg-4 col-sm-8">
<input type="text" class="form-control" id="remoteAddr" name="remoteAddr">
</div>
</div>
<div class="form-group">
<label for="themeName" class="col-lg-2 col-sm-4 control-label">主题名称:</label>
<div class="col-lg-4 col-sm-8">
<input type="text" class="form-control" id="themeName" name="themeName">
</div>
</div>
</div>
<div class="box-footer">
<button type="button" data-loading-text="安装中..." class="btn btn-primary btn-sm" onclick="pullAction()" id="btnInstall">安装</button>
</div>
</form>
</div>
</div>
</div>
</section>
</div>
</body>
<script src="/static/plugins/jquery/jquery.min.js"></script>
<script src="/static/plugins/bootstrap/js/bootstrap.min.js"></script>
<script src="/static/plugins/fileinput/fileinput.min.js"></script>
<script src="/static/plugins/fileinput/zh.min.js"></script>
<script src="/static/plugins/toast/js/jquery.toast.min.js"></script>
<script src="/static/plugins/layer/layer.js"></script>
<script src="/static/js/app.js"></script>
<script>
$(document).ready(function () {
loadFileInput();
});
/**
* 初始化上传组件
*/
function loadFileInput() {
$('#uploadTheme').fileinput({
language: 'zh',
uploadUrl: '/admin/themes/upload',
allowedFileExtensions: ['zip'],
maxFileCount: 1,
enctype: 'multipart/form-data',
dropZoneTitle: '拖拽主题压缩包到这里 &hellip;<br>仅支持Zip格式',
showClose: false
}).on("fileuploaded",function (event,data,previewId,index) {
var data = data.jqXHR.responseJSON;
if(data.code==1){
$("#uploadForm").hide(400);
$.toast({
text: data.msg,
heading: '提示',
icon: 'success',
showHideTransition: 'fade',
allowToastClose: true,
hideAfter: 1000,
stack: 1,
position: 'top-center',
textAlign: 'left',
loader: true,
loaderBg: '#ffffff',
afterHidden: function () {
parent.location.reload();
}
});
}else{
$.toast({
text: data.msg,
heading: '提示',
icon: 'error',
showHideTransition: 'fade',
allowToastClose: true,
hideAfter: 1000,
stack: 1,
position: 'top-center',
textAlign: 'left',
loader: true,
loaderBg: '#ffffff'
});
}
});
}
/**
* 拉取主题
*/
function pullAction() {
var remoteAddr = $("#remoteAddr").val();
var themeName = $("#themeName").val();
if(remoteAddr==null || themeName==null){
$.toast({
text: "请输入完整信息!",
heading: '提示',
icon: 'error',
showHideTransition: 'fade',
allowToastClose: true,
hideAfter: 1000,
stack: 1,
position: 'top-center',
textAlign: 'left',
loader: true,
loaderBg: '#ffffff'
});
return;
}
$('#btnInstall').button('loading');
$.ajax({
type: 'post',
url: '/admin/themes/clone',
data: {
remoteAddr : remoteAddr,
themeName: themeName
},
success: function (data) {
if(data.code==1){
$.toast({
text: data.msg,
heading: '提示',
icon: 'success',
showHideTransition: 'fade',
allowToastClose: true,
hideAfter: 1000,
stack: 1,
position: 'top-center',
textAlign: 'left',
loader: true,
loaderBg: '#ffffff',
afterHidden: function () {
parent.location.reload();
}
});
}else {
showMsg(data.msg,"error",1000);
$('#btnInstall').button('reset');
}
}
});
}
</script>
</html>