Commit 0f8aa333 by lepdou

首页环境切换 & app主页显示app基本信息 & 样式优化

parent 22756eb0
......@@ -27,12 +27,16 @@ public class AdminServiceAPI {
public static String APP_API = "/apps";
public List<AppDTO> getApps(Env env) {
public List<AppDTO> findApps(Env env) {
AppDTO[] appDTOs =
restTemplate.getForObject(getAdminServiceHost(env) + APP_API, AppDTO[].class);
return Arrays.asList(appDTOs);
}
public AppDTO loadApp(Env env, String appId){
return restTemplate.getForObject(getAdminServiceHost(env) + APP_API + "/" + appId, AppDTO.class);
}
public AppDTO save(Env env, AppDTO app) {
return restTemplate.postForEntity(getAdminServiceHost(env) + APP_API, app, AppDTO.class)
.getBody();
......
......@@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.ctrip.apollo.core.dto.AppDTO;
import com.ctrip.apollo.core.enums.Env;
import com.ctrip.apollo.core.exception.BadRequestException;
import com.ctrip.apollo.core.utils.StringUtils;
import com.ctrip.apollo.portal.entity.ClusterNavTree;
......@@ -24,9 +25,12 @@ public class AppController {
private AppService appService;
@RequestMapping("")
public List<AppDTO> findAllApp(){
return appService.findAll();
@RequestMapping("/env/{env}")
public List<AppDTO> findAllApp(@PathVariable String env){
if (StringUtils.isEmpty(env)){
throw new BadRequestException("env can not be empty");
}
return appService.findAll(Env.valueOf(env));
}
@RequestMapping("/{appId}/navtree")
......@@ -47,6 +51,14 @@ public class AppController {
return ResponseEntity.ok().build();
}
@RequestMapping(value = "/{appId}", method = RequestMethod.GET)
public AppDTO load(@PathVariable String appId){
if (StringUtils.isEmpty(appId)){
throw new BadRequestException("app id can not be empty.");
}
return appService.load(appId);
}
private boolean isInvalidApp(AppDTO app) {
return StringUtils.isContainEmpty(app.getName(), app.getAppId(), app.getOwnerEmail(), app.getOwnerName());
}
......
package com.ctrip.apollo.portal.controller;
import com.ctrip.apollo.core.enums.Env;
import com.ctrip.apollo.portal.PortalSettings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/envs")
public class EnvController {
@Autowired
private PortalSettings portalSettings;
@RequestMapping(value = "", method = RequestMethod.GET)
public List<Env> envs(){
return portalSettings.getEnvs();
}
}
......@@ -5,12 +5,17 @@ import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpStatusCodeException;
import com.ctrip.apollo.common.utils.ExceptionUtils;
import com.ctrip.apollo.core.dto.AppDTO;
import com.ctrip.apollo.core.enums.Env;
import com.ctrip.apollo.core.exception.BadRequestException;
import com.ctrip.apollo.core.exception.ServiceException;
import com.ctrip.apollo.core.utils.StringUtils;
import com.ctrip.apollo.portal.PortalSettings;
import com.ctrip.apollo.portal.api.AdminServiceAPI;
import com.ctrip.apollo.portal.entity.ClusterNavTree;
......@@ -29,10 +34,33 @@ public class AppService {
@Autowired
private AdminServiceAPI.AppAPI appAPI;
public List<AppDTO> findAll() {
// TODO: 16/4/21 先从 portalSettings第一个环境去apps,后续可以优化
Env env = portalSettings.getEnvs().get(0);
return appAPI.getApps(env);
public List<AppDTO> findAll(Env env) {
return appAPI.findApps(env);
}
public AppDTO load(String appId) {
//轮询环境直到能找到此app的信息
AppDTO app = null;
for (Env env : portalSettings.getEnvs()) {
try {
app = appAPI.loadApp(env, appId);
break;
} catch (HttpClientErrorException e) {
//not exist maybe because create app fail.
if (e.getStatusCode() == HttpStatus.NOT_FOUND) {
logger.warn("app:{} in {} not exist", appId, env);
} else {
logger.error("load app info({}) from env:{} error.", appId, env);
throw new ServiceException("can not load app from all envs");
}
}
}
if (app == null){
throw new BadRequestException(String.format("invalid app id %s", appId));
}
return app;
}
public ClusterNavTree buildClusterNavTree(String appId) {
......
......@@ -15,4 +15,4 @@ ctrip:
apollo:
portal:
env: local
env: local,dev
......@@ -13,7 +13,11 @@
<body ng-controller="IndexController">
<!--<div ng-include="'views/common/nav.html'"></div>-->
<div class="site-notice">当前站点支持
&nbsp;<a ng-repeat="env in envs" ng-class="{selected:selectedEnv == env}" ng-click="switchEnv(env)"><em>{{env}}
</em></a>&nbsp;
环境点击切换
</div>
<header class="site-header jumbotron">
<div class="container">
......@@ -21,21 +25,25 @@
<div class="col-xs-12"><h1>Apollo</h1>
<p>携程统一配置中心<br>
<span class="package-amount">共收录了 <strong>{{appsCount}}</strong> 个项目</span>
<a class="btn btn-success btn-lg" href="views/create-app.html">创建项目</a>
<a class="btn btn-success" href="views/create-app.html">创建项目</a>
</p>
<form class="" role="search">
<div class="form-group"><input type="text" class="form-control search clearable"
placeholder="搜索App, 例如:900088" ng-model="searchKey" ng-change="search()"></div>
placeholder="搜索App, 例如:apollo" ng-model="searchKey"
ng-change="search()"></div>
</form>
</div>
</div>
</div>
</header>
<div class="container-fluid apollo-container">
<div class="list-group apps">
<a class="package list-group-item" target="_blank" href="/views/app.html?#/appid={{app.appId}}" ng-repeat="app in apps ">
<a class="package list-group-item" target="_blank" href="/views/app.html?#/appid={{app.appId}}"
ng-repeat="app in apps ">
<div class="row">
<div class="col-md-3"><h4 class="apps-name">{{app.appId}}</h4></div>
<div class="col-md-7 hidden-xs">
......@@ -49,9 +57,6 @@
</p>
</div>
<!--<div class="package-extra-info col-md-9 col-md-offset-3 col-xs-12"><span><i-->
<!--class="glyphicon glyphicon-fire"></i> 92793</span>-->
<!--</div>-->
</div>
</a>
</div>
......@@ -73,6 +78,7 @@
<script type="application/javascript" src="scripts/app.js"></script>
<script type="application/javascript" src="scripts/services/AppService.js"></script>
<script type="application/javascript" src="scripts/services/EnvService.js"></script>
<script type="application/javascript" src="scripts/controller/IndexController.js"></script>
</body>
</html>
index_module.controller('IndexController', ['$scope', '$window', 'toastr', 'AppService',
function ($scope, $window, toastr, AppService) {
index_module.controller('IndexController', ['$scope', '$window', 'toastr', 'AppService', 'EnvService',
function ($scope, $window, toastr, AppService, EnvService) {
var apps = [];
AppService.find_all_app().then(function (result) {
apps = result;
$scope.apps = apps;
$scope.appsCount = apps.length;
});
$scope.envs = [];
$scope.selectedEnv = '';
EnvService.find_all_envs().then(function (result) {
$scope.envs = result;
//default select first env
$scope.switchEnv($scope.envs[0]);
}, function (result) {
toastr.error(result.status + result.data.message, "load env error");
});
$scope.search = function () {
var key = $scope.searchKey;
if (key == '') {
$scope.apps = apps;
return;
}
var result = [];
apps.forEach(function (item) {
if (item.appId.indexOf(key) >= 0 || item.name.indexOf(key) >= 0) {
result.push(item);
}
});
var apps = [];
$scope.switchEnv = function (env) {
$scope.selectedEnv = env;
loadApps(env);
};
function loadApps(env){
AppService.find_all_app(env).then(function (result) {
apps = result;
$scope.apps = apps;
$scope.appsCount = apps.length;
$scope.selectedEnv = env;
}, function (result) {
toastr.error(result.status + result.data.message, "load apps error");
});
};
$scope.apps = result;
};
$scope.search = function () {
var key = $scope.searchKey;
if (key == '') {
$scope.apps = apps;
return;
}
var result = [];
apps.forEach(function (item) {
if (item.appId.indexOf(key) >= 0 || item.name.indexOf(key) >= 0) {
result.push(item);
}
});
$scope.apps = result;
};
}]);
......@@ -61,6 +61,14 @@ application_module.controller("AppConfigController",
toastr.error(result.status + result.data.message, "加载导航出错");
});
/////////// app info ////////////
AppService.load($scope.pageContext.appId).then(function (result) {
$scope.appInfo = result;
},function (result) {
toastr.error(result.status + result.data.message, "加载App信息出错");
});
/////////// namespace ////////////
var namespace_view_type = {
......@@ -90,7 +98,6 @@ application_module.controller("AppConfigController",
item.viewType = namespace_view_type.TABLE;
}
item.isTextEditing = false;
})
}
......@@ -160,6 +167,12 @@ application_module.controller("AppConfigController",
);
};
$scope.isItemsViewOpened = true;
$scope.toggleItemView = function (isOpened) {
$scope.isItemsViewOpened = isOpened;
};
//文本编辑框状态切换
$scope.toggleTextEditStatus = function (namespace) {
namespace.isTextEditing = !namespace.isTextEditing;
......
......@@ -3,7 +3,7 @@ appService.service('AppService', ['$resource', '$q', function ($resource, $q) {
find_all_app:{
method: 'GET',
isArray: true,
url:'/apps'
url:'/apps/env/:env'
},
load_navtree:{
methode: 'GET',
......@@ -20,11 +20,11 @@ appService.service('AppService', ['$resource', '$q', function ($resource, $q) {
}
});
return {
find_all_app: function () {
find_all_app: function (env) {
var d = $q.defer();
app_resource.find_all_app({
},
function (result) {
env: env
}, function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
......
appService.service('EnvService', ['$resource', '$q', function ($resource, $q) {
var env_resource = $resource('/envs', {}, {
find_all_envs:{
method: 'GET',
isArray: true,
url:'/envs'
}
});
return {
find_all_envs: function () {
var d = $q.defer();
env_resource.find_all_envs({
},
function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
}
}
}]);
html, body {
height: 100%
}
body {
color: #797979;
background: #fff;
padding: 0px !important;
margin: 0px !important;
font-size: 13px;
min-height: 650px;
/*min-height: 650px;*/
background: #f1f2f7;
font-family: 'Open Sans', sans-serif;
}
a{
a {
cursor: pointer;
}
.apollo-container{
min-height: 550px;
.apollo-container {
min-height: 90%;
}
.navbar-default {
background-color: #ffffff;
border: none;
}
.footer {
height: 100px;
width: 100%;
background: #F6F6F6;
padding-top: 40px;
height: 75px;
margin-top: -75px;
padding-top: 25px;
}
/*panel*/
.panel{
.panel {
border: 1px solid #ddd;
}
.panel-heading {
height: 45px;
font-size: 14px;
background-color: #f5f5f5;
padding: 5px 20px;
}
table th {
text-align: center;
}
/*首页*/
.site-header{
.site-notice {
padding: 5px 0;
text-align: center;
background-color: #208d4e;
}
.site-notice {
color: #eee;
}
.site-notice a {
color: #ffffff;
}
.site-notice a:hover {
text-underline: none;
}
.site-notice .selected {
color: #000000;
}
.site-header {
position: relative;
text-align: center;
background-color: #27AE60;
......@@ -48,7 +72,7 @@ table th {
margin-bottom: 0;
}
.site-header .search{
.site-header .search {
border: 2px solid #27AE60;
-webkit-box-shadow: none;
box-shadow: none;
......@@ -59,23 +83,55 @@ table th {
text-align: center;
}
.list-group{
.site-header h1 {
font-size: 56px;
text-shadow: -5px 5px 0 rgba(0, 0, 0, 0.1);
}
.site-header span {
font-size: 14px;
}
.list-group {
margin-top: 20px;
}
.apps .apps-description{
.apps .apps-description {
color: gray;
font-family: "Apple Color Emoji";
font-size: 16px;
}
.app {
background-color: #f5f5f5;
padding-bottom: 75px;
overflow: hidden;
}
.app td, th {
display: table-cell;
vertical-align: inherit;
}
.project-info {
width: 100%;
}
.panel-heading {
border-color: #eff2f7;
font-size: 16px;
font-weight: 300;
}
.project-info td {
word-wrap: break-word;
word-break: break-all;
border-bottom: 1px dotted gray;
padding: 4px 6px;
}
#config-info {
min-height: 500px;
background-color: #ffffff;
/*background-color: #ffffff;*/
}
#config-info .nav {
......@@ -103,20 +159,30 @@ table th {
display: none;
}
.config-item-container {
padding-top: 2px;
}
.config-item-container .config-items {
height: 500px;
overflow: scroll;
}
#editor {
position: relative;
width: 500px;
height: 400px;
textarea:disabled {
color: #5b6e84;
}
.namespace-view-table{
.namespace-view-table {
max-height: 700px;
overflow: scroll;
/*overflow-y: scroll;*/
}
.namespace-view-table table {
table-layout: fixed;
}
.namespace-view-table td {
word-wrap: break-word;
}
.history-view {
......
......@@ -23,12 +23,44 @@
<!--具体配置信息-->
<div class="row config-info-container">
<!--tag导航-->
<div class="col-md-2">
<div class="col-md-3">
<div id="treeview"></div>
</div>
<!--app info-->
<section class="panel">
<header class="panel-heading">
应用信息
<span class="tools pull-right">
<a href="javascript:;" class="icon-chevron-down"></a>
</span>
</header>
<div class="panel-body">
<table class="project-info">
<tbody>
<tr>
<th>应用ID:</th>
<td>{{appInfo.appId}}</td>
</tr>
<tr>
<th>应用名:</th>
<td>{{appInfo.name}}</td>
</tr>
<tr>
<th>Owner:</th>
<td>{{appInfo.ownerName}}</td>
</tr>
<tr>
<th>Owner Email:</th>
<td>{{appInfo.ownerEmail}}</td>
</tr>
</tbody>
</table>
<div class="col-md-10 config-item-container">
</div>
</section>
</div>
<div class="col-md-9 config-item-container">
<a ng-click="toggleItemView(true)">全部展开</a> | <a ng-click="toggleItemView(false)">全部收缩</a>
<div ng-repeat="namespace in namespaces">
<div class="panel">
<header class="panel-heading">
......@@ -100,20 +132,20 @@
</div>
</div>
</header>
<div ng-show="namespace.viewType == 'text'">
<textarea class="form-control" rows="30" ng-model="namespace.text"
ng-disabled="!namespace.isTextEditing">
<!--text view-->
<textarea class="form-control" rows="30" ng-show="isItemsViewOpened && namespace.viewType == 'text'"
ng-disabled="!namespace.isTextEditing">
{{namespace.text}}
</textarea>
</div>
<!--table view-->
<div class="namespace-view-table">
<table class="table table-bordered text-center table-hover"
ng-show="namespace.viewType == 'table'">
ng-show="isItemsViewOpened && namespace.viewType == 'table'">
<thead>
<tr>
<th>
<th >
Key
</th>
<th>
......@@ -130,27 +162,27 @@
</th>
</tr>
</thead>
<tbody ng-repeat="config in namespace.items">
<tbody>
<tr ng-class="{warning:config.modified}" ng-if="config.item.key">
<td>
<tr ng-repeat="config in namespace.items" ng-class="{warning:config.modified}" ng-if="config.item.key">
<td width="25%">
{{config.item.key}}
</td>
<td>
<td width="30%">
<button data-placement="top" title="查看旧值"
class="glyphicon glyphicon-eye-open"
aria-hidden="true" data-toggle="modal" data-target="#oldValueModal"
ng-show="config.modified"
ng-click="queryOldValue(config.item.key, config.oldValue)"></button>
{{config.item.value}}
</td>
<td>
</td >
<td width="20%">
{{config.item.comment}}
</td>
<td>
<td width="10%">
{{config.item.lastModifiedBy}}
</td>
<td>
<td width="15%">
{{config.item.lastModifiedTime | date: 'yyyy-MM-dd HH:mm:ss'}}
</td>
......@@ -160,7 +192,7 @@
</div>
<!--历史修改视图-->
<div class="J_historyview history-view" ng-show="namespace.viewType == 'history'">
<div class="J_historyview history-view" ng-show="isItemsViewOpened && namespace.viewType == 'history'">
<div class="row">
<div class="col-md-11 col-md-offset-1 list" style="">
<div class="media">
......@@ -241,10 +273,13 @@
<h4 class="modal-title" id="myModalLabel2">Commit changes</h4>
</div>
<div class="modal-body">
<textarea rows="4" class="form-control" style="width:570px;" placeholder="input change log...." ng-model="commitComment"></textarea>
<textarea rows="4" class="form-control" style="width:570px;" placeholder="input change log...."
ng-model="commitComment"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal" ng-click="commitChange()">提交</button>
<button type="button" class="btn btn-primary" data-dismiss="modal" ng-click="commitChange()">
提交
</button>
</div>
</div>
</div>
......@@ -260,10 +295,12 @@
<h4 class="modal-title" id="myModalLabel3">发布</h4>
</div>
<div class="modal-body">
<textarea rows="4" class="form-control" style="width:570px;" ng-model = "releaseComment" placeholder="input release log...."></textarea>
<textarea rows="4" class="form-control" style="width:570px;" ng-model="releaseComment"
placeholder="input release log...."></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal" ng-click="release()">提交</button>
<button type="button" class="btn btn-primary" data-dismiss="modal" ng-click="release()">提交
</button>
</div>
</div>
</div>
......@@ -282,6 +319,9 @@
<!--lodash.js-->
<script src="../vendor/lodash.min.js" type="text/javascript"></script>
<!--nicescroll-->
<script src="../vendor/jquery.nicescroll.min.js"></script>
<!--angular-->
<script src="../vendor/angular/angular.min.js"></script>
<script src="../vendor/angular/angular-ui-router.min.js"></script>
......@@ -296,8 +336,6 @@
<!--biz script-->
<script type="application/javascript" src="../scripts/app.js"></script>
<!--directive-->
<!--service-->
<script type="application/javascript" src="../scripts/services/AppService.js"></script>
<script type="application/javascript" src="../scripts/services/ConfigService.js"></script>
......@@ -306,9 +344,23 @@
<script type="application/javascript" src="../scripts/controller/app/AppConfigController.js"></script>
<script type="application/javascript">
$(document).ready(function () {
$("html").niceScroll({
styler: "fb",
cursorcolor: "#e8403f",
cursorwidth: '6',
cursorborderradius: '10px',
background: '#404040',
spacebarenabled: false,
cursorborder: '',
zindex: '1000'
});
});
$(function () {
$('[data-toggle="tooltip"]').tooltip()
});
</script>
</body>
</html>
<div class="footer">
<hr>
<p class="text-center">
<span class="glyphicon glyphicon-copyright-mark" aria-hidden="true"></span>携程 框架研发部<br>
<a href="http://www.ctrip.com" target="_blank">www.ctrip.com</a>
</p>
</div>
......
......@@ -15,8 +15,6 @@
<button type="submit" class="btn btn-default">Go</button>
</form>
<ul class="nav navbar-nav navbar-right">
<li><a href="/views/create-app.html" data-toggle="tooltip" data-placement="bottom" title="创建项目"><span
class="glyphicon glyphicon-plus"></span> </a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">用户 <span class="caret"></span></a>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment