Commit 26cb8582 authored by lepdou's avatar lepdou

portal v2

parent 11e8b799
...@@ -8,10 +8,12 @@ import org.springframework.web.bind.annotation.RequestMapping; ...@@ -8,10 +8,12 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.ctrip.apollo.biz.entity.Release; import com.ctrip.apollo.biz.entity.Release;
import com.ctrip.apollo.biz.service.ConfigService;
import com.ctrip.apollo.biz.service.ReleaseService; import com.ctrip.apollo.biz.service.ReleaseService;
import com.ctrip.apollo.biz.service.ViewService; import com.ctrip.apollo.biz.service.ViewService;
import com.ctrip.apollo.biz.utils.BeanUtils; import com.ctrip.apollo.biz.utils.BeanUtils;
import com.ctrip.apollo.core.dto.ReleaseDTO; import com.ctrip.apollo.core.dto.ReleaseDTO;
import com.ctrip.apollo.core.utils.StringUtils;
@RestController @RestController
public class ReleaseController { public class ReleaseController {
...@@ -22,6 +24,9 @@ public class ReleaseController { ...@@ -22,6 +24,9 @@ public class ReleaseController {
@Autowired @Autowired
private ReleaseService releaseService; private ReleaseService releaseService;
@Autowired
private ConfigService configService;
@RequestMapping("/release/{releaseId}") @RequestMapping("/release/{releaseId}")
public ReleaseDTO findOne(@PathVariable("releaseId") long releaseId) { public ReleaseDTO findOne(@PathVariable("releaseId") long releaseId) {
Release release = releaseService.findOne(releaseId); Release release = releaseService.findOne(releaseId);
...@@ -35,4 +40,16 @@ public class ReleaseController { ...@@ -35,4 +40,16 @@ public class ReleaseController {
List<Release> releases = viewSerivce.findReleases(appId, clusterName, namespaceName); List<Release> releases = viewSerivce.findReleases(appId, clusterName, namespaceName);
return BeanUtils.batchTransform(ReleaseDTO.class, releases); return BeanUtils.batchTransform(ReleaseDTO.class, releases);
} }
@RequestMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/latest")
public ReleaseDTO findLatestRelease(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName){
if (StringUtils.isContainEmpty(appId, clusterName, namespaceName)){
return null;
}
Release release = configService.findRelease(appId, clusterName, namespaceName);
return BeanUtils.transfrom(ReleaseDTO.class, release);
}
} }
...@@ -44,6 +44,9 @@ public class BeanUtils { ...@@ -44,6 +44,9 @@ public class BeanUtils {
* </pre> * </pre>
*/ */
public static <T> T transfrom(Class<T> clazz, Object src) { public static <T> T transfrom(Class<T> clazz, Object src) {
if (src == null){
return null;
}
T instance = null; T instance = null;
try { try {
instance = clazz.newInstance(); instance = clazz.newInstance();
......
...@@ -2,3 +2,4 @@ spring.datasource.url = jdbc:h2:mem:~/fxapolloconfigdb;mode=mysql ...@@ -2,3 +2,4 @@ spring.datasource.url = jdbc:h2:mem:~/fxapolloconfigdb;mode=mysql
spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.EJB3NamingStrategy spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.EJB3NamingStrategy
spring.h2.console.enabled = true spring.h2.console.enabled = true
spring.h2.console.settings.web-allow-others=true spring.h2.console.settings.web-allow-others=true
...@@ -31,6 +31,20 @@ public class StringUtils { ...@@ -31,6 +31,20 @@ public class StringUtils {
return str == null || str.length() == 0; return str == null || str.length() == 0;
} }
public static boolean isContainEmpty(String... args){
if (args == null){
return false;
}
for (String arg: args){
if (arg == null || "".equals(arg)){
return true;
}
}
return false;
}
/** /**
* <p> * <p>
* Checks if a String is whitespace, empty ("") or null. * Checks if a String is whitespace, empty ("") or null.
......
package com.ctrip.apollo.portal.api; package com.ctrip.apollo.portal.api;
import com.google.common.base.Strings;
import com.ctrip.apollo.Apollo; import com.ctrip.apollo.Apollo;
import com.ctrip.apollo.core.dto.AppDTO; import com.ctrip.apollo.core.dto.AppDTO;
import com.ctrip.apollo.core.dto.ClusterDTO; import com.ctrip.apollo.core.dto.ClusterDTO;
import com.ctrip.apollo.core.dto.ItemDTO; import com.ctrip.apollo.core.dto.ItemDTO;
import com.ctrip.apollo.core.dto.NamespaceDTO;
import com.ctrip.apollo.core.dto.ReleaseDTO; import com.ctrip.apollo.core.dto.ReleaseDTO;
import com.ctrip.apollo.core.utils.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List;
@Service @Service
public class AdminServiceAPI { public class AdminServiceAPI {
...@@ -24,46 +24,62 @@ public class AdminServiceAPI { ...@@ -24,46 +24,62 @@ public class AdminServiceAPI {
} }
} }
@Service @Service
public static class ConfigAPI extends API { public static class NamespaceAPI extends API {
public static String CONFIG_RELEASE_API = "/configs/release/";
public ReleaseDTO[] getConfigByReleaseId(Apollo.Env env, long releaseId) { public NamespaceDTO[] findGroupsByAppAndCluster(String appId, Apollo.Env env,
if (releaseId <= 0) { String clusterName) {
if (StringUtils.isContainEmpty(appId, clusterName)) {
return null; return null;
} }
return restTemplate.getForObject(getAdminServiceHost(env) + CONFIG_RELEASE_API + releaseId, return restTemplate.getForObject(
ReleaseDTO[].class); getAdminServiceHost(env) + String.format("apps/%s/clusters/%s/namespaces", appId, clusterName),
NamespaceDTO[].class);
}
} }
public ItemDTO[] getLatestConfigItemsByClusters(Apollo.Env env, List<Long> clusterIds) { @Service
if (clusterIds == null || clusterIds.size() == 0) { public static class ItemAPI extends API {
public ItemDTO[] findItems(String appId, Apollo.Env env, String clusterName, String namespace) {
if (StringUtils.isContainEmpty(appId, clusterName, namespace)) {
return null; return null;
} }
StringBuilder sb = new StringBuilder();
for (long clusterId : clusterIds) {
sb.append(clusterId).append(",");
}
return restTemplate.getForObject(getAdminServiceHost(env) + "/configs/latest?clusterIds=" return restTemplate.getForObject(getAdminServiceHost(env) + String
+ sb.substring(0, sb.length() - 1), ItemDTO[].class); .format("apps/%s/clusters/%s/namespaces/%s/items", appId,
clusterName, namespace),
ItemDTO[].class);
} }
} }
@Service @Service
public static class ClusterAPI extends API { public static class ClusterAPI extends API {
public static String CLUSTER_APP_API = "/cluster/app/"; public ClusterDTO[] findClustersByApp(String appId, Apollo.Env env) {
if (StringUtils.isContainEmpty(appId)) {
public ClusterDTO[] getClustersByApp(Apollo.Env env, String appId) {
if (Strings.isNullOrEmpty(appId)) {
return null; return null;
} }
return restTemplate.getForObject(getAdminServiceHost(env) + CLUSTER_APP_API + appId, return restTemplate.getForObject(getAdminServiceHost(env) + String.format("apps/%s/clusters", appId),
ClusterDTO[].class); ClusterDTO[].class);
} }
} }
@Service
public static class ReleaseAPI extends API{
public ReleaseDTO loadLatestRelease(String appId, Apollo.Env env, String clusterName, String namespace){
if (StringUtils.isContainEmpty(appId, clusterName, namespace)){
return null;
}
return restTemplate.getForObject(getAdminServiceHost(env) + String
.format("apps/%s/clusters/%s/namespaces/%s/releases/latest", appId,
clusterName, namespace), ReleaseDTO.class);
}
}
} }
package com.ctrip.apollo.portal.controller; package com.ctrip.apollo.portal.controller;
import com.ctrip.apollo.Apollo; import com.google.common.base.Strings;
import com.ctrip.apollo.portal.PortalSettings;
import com.ctrip.apollo.portal.entity.ClusterNavTree;
import com.ctrip.apollo.portal.service.AppService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController @RestController
@RequestMapping("/envs") @RequestMapping("/apps")
public class EnvController { public class AppController {
@Autowired @Autowired
private PortalSettings portalSettings; private AppService appService;
@RequestMapping("") @RequestMapping("/{appId}/navtree")
public List<Apollo.Env> envs(){ public ClusterNavTree nav(@PathVariable String appId) {
return portalSettings.getEnvs(); if (Strings.isNullOrEmpty(appId)) {
throw new IllegalArgumentException("app id can not be empty.");
}
return appService.buildClusterNavTree(appId);
} }
} }
package com.ctrip.apollo.portal.controller; package com.ctrip.apollo.portal.controller;
import com.google.common.base.Strings;
import com.ctrip.apollo.Apollo; import com.ctrip.apollo.Apollo;
import com.ctrip.apollo.core.dto.AppConfigVO; import com.ctrip.apollo.core.utils.StringUtils;
import com.ctrip.apollo.portal.constants.PortalConstants; import com.ctrip.apollo.portal.entity.NamespaceVO;
import com.ctrip.apollo.portal.exception.NotFoundException;
import com.ctrip.apollo.portal.service.ConfigService; import com.ctrip.apollo.portal.service.ConfigService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -13,35 +11,21 @@ import org.springframework.web.bind.annotation.PathVariable; ...@@ -13,35 +11,21 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController @RestController
@RequestMapping("/configs") @RequestMapping("")
public class ConfigController { public class ConfigController {
@Autowired @Autowired
private ConfigService configService; private ConfigService configService;
@RequestMapping("/{appId}/{env}/{versionId}") @RequestMapping("/apps/{appId}/env/{env}/clusters/{clusterName}/namespaces")
public AppConfigVO detail(@PathVariable String appId, @PathVariable String env, public List<NamespaceVO> findNamespaces(@PathVariable String appId, @PathVariable String env, @PathVariable String clusterName){
@PathVariable long versionId) { if (StringUtils.isContainEmpty(appId, env, clusterName)){
throw new IllegalArgumentException("app id and cluster name can not be empty");
if (Strings.isNullOrEmpty(appId) || Strings.isNullOrEmpty(env)) {
throw new IllegalArgumentException(
String.format("app id and env can not be empty. app id:%s , env:%s", appId, env));
} }
Apollo.Env e = Apollo.Env.valueOf(env); return configService.findNampspaces(appId, Apollo.Env.valueOf(env), clusterName);
if (versionId == PortalConstants.LASTEST_VERSION_ID) {
return configService.loadLatestConfig(e, appId);
// } else if (versionId > 0) {
//
// return configService.loadReleaseConfig(e, appId, versionId);
//
}
else {
throw new NotFoundException();
}
} }
} }
package com.ctrip.apollo.portal.entity;
import com.ctrip.apollo.Apollo;
import com.ctrip.apollo.core.dto.ClusterDTO;
import java.util.LinkedList;
import java.util.List;
public class ClusterNavTree {
private List<Node> nodes;
public void addNode(Node node){
if (nodes == null){
nodes = new LinkedList<>();
}
nodes.add(node);
}
public static class Node{
private Apollo.Env env;
private List<ClusterDTO> clusters;
public Node(Apollo.Env env){
this.env = env;
}
public Apollo.Env getEnv() {
return env;
}
public void setEnv(Apollo.Env env) {
this.env = env;
}
public List<ClusterDTO> getClusters() {
return clusters;
}
public void setClusters(List<ClusterDTO> clusters) {
this.clusters = clusters;
}
}
public List<Node> getNodes() {
return nodes;
}
public void setNodes(List<Node> nodes) {
this.nodes = nodes;
}
}
package com.ctrip.apollo.portal.entity;
import com.ctrip.apollo.core.dto.ItemDTO;
import com.ctrip.apollo.core.dto.NamespaceDTO;
import java.util.List;
public class NamespaceVO {
private NamespaceDTO namespace;
private int itemModifiedCnt;
private List<ItemVO> items;
public NamespaceDTO getNamespace() {
return namespace;
}
public void setNamespace(NamespaceDTO namespace) {
this.namespace = namespace;
}
public int getItemModifiedCnt() {
return itemModifiedCnt;
}
public void setItemModifiedCnt(int itemModifiedCnt) {
this.itemModifiedCnt = itemModifiedCnt;
}
public List<ItemVO> getItems() {
return items;
}
public void setItems(List<ItemVO> items) {
this.items = items;
}
public static class ItemVO{
private ItemDTO item;
private boolean isModified;
private String oldValue;
private String newValue;
public ItemDTO getItem() {
return item;
}
public void setItem(ItemDTO item) {
this.item = item;
}
public boolean isModified() {
return isModified;
}
public void setModified(boolean isModified) {
this.isModified = isModified;
}
public String getOldValue() {
return oldValue;
}
public void setOldValue(String oldValue) {
this.oldValue = oldValue;
}
public String getNewValue() {
return newValue;
}
public void setNewValue(String newValue) {
this.newValue = newValue;
}
}
}
package com.ctrip.apollo.portal.service;
import com.ctrip.apollo.Apollo;
import com.ctrip.apollo.portal.entity.ClusterNavTree;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AppService {
@Autowired
private ClusterService clusterService;
public ClusterNavTree buildClusterNavTree(String appId){
ClusterNavTree tree = new ClusterNavTree();
ClusterNavTree.Node localNode = new ClusterNavTree.Node(Apollo.Env.LOCAL);
localNode.setClusters(clusterService.findClusters(Apollo.Env.LOCAL, appId));
tree.addNode(localNode);
// ClusterNavTree.Node uatNode = new ClusterNavTree.Node(Apollo.Env.UAT);
// List<ClusterDTO> uatClusters = new LinkedList<>();
// uatClusters.add(defaultCluster);
// uatNode.setClusters(uatClusters);
// tree.addNode(uatNode);
return tree;
}
}
package com.ctrip.apollo.portal.service;
import com.ctrip.apollo.Apollo;
import com.ctrip.apollo.core.dto.ClusterDTO;
import com.ctrip.apollo.portal.api.AdminServiceAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
@Service
public class ClusterService {
@Autowired
private AdminServiceAPI.ClusterAPI clusterAPI;
public List<ClusterDTO> findClusters(Apollo.Env env, String appId){
return Arrays.asList(clusterAPI.findClustersByApp(appId, env));
}
}
package com.ctrip.apollo.portal.service; package com.ctrip.apollo.portal.service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.ctrip.apollo.Apollo.Env; import com.ctrip.apollo.Apollo;
import com.ctrip.apollo.core.ConfigConsts;
import com.ctrip.apollo.core.dto.AppConfigVO;
import com.ctrip.apollo.core.dto.ClusterDTO;
import com.ctrip.apollo.core.dto.ItemDTO; import com.ctrip.apollo.core.dto.ItemDTO;
import com.ctrip.apollo.core.dto.NamespaceDTO;
import com.ctrip.apollo.core.dto.ReleaseDTO; import com.ctrip.apollo.core.dto.ReleaseDTO;
import com.ctrip.apollo.portal.api.AdminServiceAPI; import com.ctrip.apollo.portal.api.AdminServiceAPI;
import com.ctrip.apollo.portal.constants.PortalConstants; import com.ctrip.apollo.portal.entity.NamespaceVO;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings; import java.io.IOException;
import com.google.common.collect.Maps; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@Service @Service
public class ConfigService { public class ConfigService {
...@@ -32,231 +28,82 @@ public class ConfigService { ...@@ -32,231 +28,82 @@ public class ConfigService {
private Logger logger = LoggerFactory.getLogger(ConfigService.class); private Logger logger = LoggerFactory.getLogger(ConfigService.class);
@Autowired @Autowired
private AdminServiceAPI.ConfigAPI configAPI; private AdminServiceAPI.NamespaceAPI groupAPI;
@Autowired
private AdminServiceAPI.ItemAPI itemAPI;
@Autowired @Autowired
private AdminServiceAPI.ClusterAPI clusterAPI; private AdminServiceAPI.ReleaseAPI releaseAPI;
private ObjectMapper objectMapper = new ObjectMapper(); private ObjectMapper objectMapper = new ObjectMapper();
// public AppConfigVO loadReleaseConfig(Env env, String appId, String cluster, String namespace) {
//
// if (Strings.isNullOrEmpty(appId) || Strings.isNullOrEmpty(cluster) || Strings.isNullOrEmpty(namespace)) {
// return null;
// }
//
// ReleaseDTO[] releaseSnapShots = configAPI.getConfigByReleaseId(env, releaseId);
// if (releaseSnapShots == null || releaseSnapShots.length == 0) {
// return null;
// }
//
// AppConfigVO appConfigVO = AppConfigVO.newInstance(appId, versionId);
//
// for (ReleaseDTO snapShot : releaseSnapShots) {
// // default cluster
// if (ConfigConsts.DEFAULT_CLUSTER_NAME.equals(snapShot.getClusterName())) {
//
// collectDefaultClusterConfigs(appId, snapShot, appConfigVO);
//
// } else {// cluster special configs
// collectSpecialClusterConfigs(appId, snapShot, appConfigVO);
// }
// }
// return appConfigVO;
// }
private void collectDefaultClusterConfigs(String appId, ReleaseDTO snapShot,
AppConfigVO appConfigVO) {
Map<String, List<ItemDTO>> groupedConfigs = public List<NamespaceVO> findNampspaces(String appId, Apollo.Env env, String clusterName) {
groupConfigsByApp(appId, snapShot.getConfigurations());
List<AppConfigVO.OverrideAppConfig> overrideAppConfigs = appConfigVO.getOverrideAppConfigs(); List<NamespaceDTO> namespaces = Arrays.asList(
groupAPI.findGroupsByAppAndCluster(appId, env, clusterName));
for (Map.Entry<String, List<ItemDTO>> entry : groupedConfigs.entrySet()) { if (namespaces == null || namespaces.size() == 0) {
String configAppId = entry.getKey(); return Collections.EMPTY_LIST;
List<ItemDTO> kvs = entry.getValue();
if (configAppId.equals(appId)) {
appConfigVO.setDefaultClusterConfigs(kvs);
} else {
AppConfigVO.OverrideAppConfig overrideAppConfig = new AppConfigVO.OverrideAppConfig();
overrideAppConfig.setAppId(configAppId);
overrideAppConfig.setConfigs(kvs);
overrideAppConfigs.add(overrideAppConfig);
}
} }
List<NamespaceVO> namespaceVOs = new LinkedList<>();
for (NamespaceDTO namespace : namespaces) {
namespaceVOs.add(parseNamespace(appId, env, clusterName, namespace));
} }
/** return namespaceVOs;
* appId -> List<KV>
*/
private Map<String, List<ItemDTO>> groupConfigsByApp(String selfAppId, String configJson) {
if (configJson == null || "".equals(configJson)) {
return Maps.newHashMap();
} }
Map<String, List<ItemDTO>> appIdMapKVs = new HashMap<>(); private NamespaceVO parseNamespace(String appId, Apollo.Env env, String clusterName, NamespaceDTO namespace) {
String key; NamespaceVO namespaceVO = new NamespaceVO();
Object value; namespaceVO.setNamespace(namespace);
Map<String, String> kvMaps = null;
try {
kvMaps = objectMapper.readValue(configJson, Map.class);
} catch (IOException e) {
logger.error("parse release snapshot json error. app id:{}", selfAppId);
return Maps.newHashMap();
}
for (Map.Entry<String, String> entry : kvMaps.entrySet()) { List<NamespaceVO.ItemVO> itemVos = new LinkedList<>();
key = entry.getKey();
value = entry.getValue();
String appId = getAppIdFromKey(key); String namespaceName = namespace.getNamespaceName();
List<ItemDTO> kvs = appIdMapKVs.get(appId);
if (kvs == null) {
kvs = new LinkedList<>();
appIdMapKVs.put(appId, kvs);
}
kvs.add(new ItemDTO(key, value.toString()));
}
return appIdMapKVs;
//latest release
ReleaseDTO release = releaseAPI.loadLatestRelease(appId, env, clusterName, namespaceName);
Map<String, String> releaseItems = new HashMap<>();
if (release != null) {
try {
releaseItems = objectMapper.readValue(release.getConfigurations(), Map.class);
} catch (IOException e) {
logger.error("parse release json error. appId:{},env:{},clusterName:{},namespace:{}", appId,
env, clusterName, namespaceName);
} }
private String getAppIdFromKey(String key) {
return key.substring(0, key.indexOf("."));
}
private void collectSpecialClusterConfigs(String appId, ReleaseDTO snapShot,
AppConfigVO appConfigVO) {
List<AppConfigVO.OverrideClusterConfig> overrideClusterConfigs =
appConfigVO.getOverrideClusterConfigs();
AppConfigVO.OverrideClusterConfig overrideClusterConfig =
new AppConfigVO.OverrideClusterConfig();
overrideClusterConfig.setClusterName(snapShot.getClusterName());
// todo step1: cluster special config can't override other app config
overrideClusterConfig.setConfigs(groupConfigsByApp(appId, snapShot.getConfigurations()).get(appId));
overrideClusterConfigs.add(overrideClusterConfig);
}
public AppConfigVO loadLatestConfig(Env env, String appId) {
if (Strings.isNullOrEmpty(appId)) {
return null;
}
ClusterDTO[] clusters = clusterAPI.getClustersByApp(env, appId);
if (clusters == null || clusters.length == 0) {
return null;
}
List<Long> clusterIds = new ArrayList<>(clusters.length);
for (ClusterDTO cluster : clusters) {
clusterIds.add(cluster.getId());
}
ItemDTO[] configItems = configAPI.getLatestConfigItemsByClusters(env, clusterIds);
return buildAPPConfigVO(appId, Arrays.asList(configItems));
}
private AppConfigVO buildAPPConfigVO(String appId, List<ItemDTO> configItems) {
if (configItems == null || configItems.size() == 0) {
return null;
} }
Map<String, List<ItemDTO>> groupedClusterConfigs = groupConfigByCluster(configItems); //not release config items
List<ItemDTO> items = Arrays.asList(itemAPI.findItems(appId, env, clusterName, namespaceName));
int modifiedItemCnt = 0;
for (ItemDTO itemDTO : items) {
AppConfigVO appConfigVO = AppConfigVO.newInstance(appId, PortalConstants.LASTEST_VERSION_ID); NamespaceVO.ItemVO itemVO = parseItemVO(itemDTO, releaseItems);
groupConfigByAppAndEnrichDTO(groupedClusterConfigs, appConfigVO);
return appConfigVO;
if (itemVO.isModified()) {
modifiedItemCnt++;
} }
private Map<String, List<ItemDTO>> groupConfigByCluster(List<ItemDTO> configItems) { itemVos.add(itemVO);
Map<String, List<ItemDTO>> groupedClusterConfigs = new HashMap<>();
// String clusterName;
// for (ItemDTO configItem : configItems) {
// clusterName = configItem.getClusterName();
// List<ItemDTO> clusterConfigs = groupedClusterConfigs.get(clusterName);
// if (clusterConfigs == null) {
// clusterConfigs = new LinkedList<>();
// groupedClusterConfigs.put(clusterName, clusterConfigs);
// }
// clusterConfigs.add(configItem);
// }
return groupedClusterConfigs;
} }
namespaceVO.setItemModifiedCnt(modifiedItemCnt);
namespaceVO.setItems(itemVos);
private void groupConfigByAppAndEnrichDTO(Map<String, List<ItemDTO>> groupedClusterConfigs, return namespaceVO;
AppConfigVO appConfigVO) {
String appId = appConfigVO.getAppId();
List<ItemDTO> defaultClusterConfigs = appConfigVO.getDefaultClusterConfigs();
List<AppConfigVO.OverrideAppConfig> overrideAppConfigs = appConfigVO.getOverrideAppConfigs();
List<AppConfigVO.OverrideClusterConfig> overrideClusterConfigs =
appConfigVO.getOverrideClusterConfigs();
String clusterName;
List<ItemDTO> clusterConfigs;
for (Map.Entry<String, List<ItemDTO>> entry : groupedClusterConfigs.entrySet()) {
clusterName = entry.getKey();
clusterConfigs = entry.getValue();
if (ConfigConsts.CLUSTER_NAME_DEFAULT.equals(clusterName)) {
// default cluster configs
collectDefaultClusterConfigs(appId, clusterConfigs, defaultClusterConfigs,
overrideAppConfigs);
} else {
// override cluster configs
collectSpecialClusterConfigs(clusterName, clusterConfigs, overrideClusterConfigs);
}
}
} }
private void collectDefaultClusterConfigs(String appId, List<ItemDTO> clusterConfigs, private NamespaceVO.ItemVO parseItemVO(ItemDTO itemDTO, Map<String, String> releaseItems) {
List<ItemDTO> defaultClusterConfigs, NamespaceVO.ItemVO itemVO = new NamespaceVO.ItemVO();
List<AppConfigVO.OverrideAppConfig> overrideAppConfigs) { itemVO.setItem(itemDTO);
String key = itemDTO.getKey();
Map<String, AppConfigVO.OverrideAppConfig> appIdMapOverrideAppConfig = null; String value = itemDTO.getValue();
String oldValue = releaseItems.get(key);
// for (ItemDTO config : clusterConfigs) { if (value.equals(oldValue)) {
// String targetAppId = config.getAppId(); itemVO.setModified(true);
// if (appId.equals(targetAppId)) {// app self's configs itemVO.setOldValue(oldValue);
// defaultClusterConfigs.add(config); itemVO.setNewValue(value);
// } else {// override other app configs
// if (appIdMapOverrideAppConfig == null) {
// appIdMapOverrideAppConfig = new HashMap<>();
// }
//
// AppConfigVO.OverrideAppConfig overrideAppConfig =
// appIdMapOverrideAppConfig.get(targetAppId);
//
// if (overrideAppConfig == null) {
// overrideAppConfig = new AppConfigVO.OverrideAppConfig();
// appIdMapOverrideAppConfig.put(targetAppId, overrideAppConfig);
// overrideAppConfigs.add(overrideAppConfig);
// }
//
// overrideAppConfig.setAppId(targetAppId);
// overrideAppConfig.addConfig(config);
// }
// }
} }
return itemVO;
private void collectSpecialClusterConfigs(String clusterName, List<ItemDTO> clusterConfigs,
List<AppConfigVO.OverrideClusterConfig> overrideClusterConfigs) {
AppConfigVO.OverrideClusterConfig overrideClusterConfig =
new AppConfigVO.OverrideClusterConfig();
overrideClusterConfig.setClusterName(clusterName);
overrideClusterConfig.setConfigs(clusterConfigs);
overrideClusterConfigs.add(overrideClusterConfig);
} }
} }
...@@ -3,27 +3,11 @@ var appService = angular.module('app.service', ['ngResource']); ...@@ -3,27 +3,11 @@ var appService = angular.module('app.service', ['ngResource']);
/** page module 定义*/ /** page module 定义*/
//项目主页 //项目主页
var application_module = angular.module('application', ['ngResource', 'ui.router', 'app.service', 'toastr', 'angular-loading-bar']); var application_module = angular.module('application', ['ngResource','ui.router', 'app.service', 'toastr', 'angular-loading-bar', 'ngTable']);
//创建项目页面 //创建项目页面
var create_app_module = angular.module('create_app', ['ngResource', 'toastr', 'app.service', 'angular-loading-bar']); var create_app_module = angular.module('create_app', ['ngResource', 'toastr', 'app.service', 'angular-loading-bar']);
/**router*/
application_module.config(['$stateProvider',
function ($stateProvider) {
$stateProvider
.state('config', {
templateUrl: '../../views/app/config.html',
controller: 'AppConfigController'
}).state('info', {
templateUrl: '../../views/app/info.html',
controller: 'AppInfoController'
}).state('setting', {
templateUrl: '../../views/app/setting.html'
});
}]).run(function ($state) {
$state.go('config');
});
......
"use strict";
application_module.controller("AppConfigController", application_module.controller("AppConfigController",
['$scope', '$rootScope', '$state', '$location', 'toastr', ['$scope', '$location', 'toastr', 'NgTableParams', 'AppService', 'ConfigService',
'AppService', 'EnvService', 'ConfigService', 'VersionService', function ($scope, $location, toastr, NgTableParams, AppService, ConfigService) {
function ($scope, $rootScope, $state, $location, toastr, AppService, EnvService, ConfigService, VersionService) {
$scope.appId = $location.$$url.split("=")[1];
var configLocation = {
appId: $rootScope.appId,
env: 'LOCAL',
versionId: -1
};
$rootScope.breadcrumb.nav = '配置'; /////////////
$rootScope.breadcrumb.env = configLocation.env;
$scope.configLocation = configLocation; AppService.load_nav_tree($scope.appId).then(function (result) {
var navTree = [];
var nodes = result.nodes;
nodes.forEach(function (item) {
var node = {};
//first nav
node.text = item.env;
//second nav
var clusterNodes = [];
item.clusters.forEach(function (item) {
var clusterNode = {};
clusterNode.text = item.name;
clusterNodes.push(clusterNode);
});
/**env*/ node.nodes = clusterNodes;
EnvService.getAllEnvs().then(function(result){ navTree.push(node);
$scope.envs = result; });
}, function(result){ $('#treeview').treeview({
toastr.error("加载环境信息失败", result); color: "#428bca",
showBorder: true,
data: navTree,
levels: 99
});
}, function (result) {
toastr.error("加载导航出错");
}); });
$scope.switchEnv = function (selectedEnv) { ///////////
configLocation.env = selectedEnv; $scope.env = 'LOCAL';
$rootScope.breadcrumb.env = configLocation.env; $scope.clusterName = 'DEFAULT';
refreshConfigs(); ConfigService.load_all_namespaces($scope.appId, $scope.env,
$scope.clusterName).then(function (result) {
}, function (result) {
toastr.error("加载配置信息出错");
});
var simpleList = [{
key: 'page',
value: 10,
comment: '页面大小',
dataChangeLastModifiedTime: '2016-02-23',
dataChangeLastModifiedBy: '网鱼'
},
{
key: 'page',
value: 10,
comment: '页面大小',
dataChangeLastModifiedTime: '2016-02-23',
dataChangeLastModifiedBy: '网鱼'
},
{
key: 'page',
value: 10,
comment: '页面大小',
dataChangeLastModifiedTime: '2016-02-23',
dataChangeLastModifiedBy: '网鱼'
},
{
key: 'page',
value: 5,
comment: '页面大小',
modified: "true",
dataChangeLastModifiedTime: '2016-02-23',
dataChangeLastModifiedBy: '网鱼'
},
{
key: 'ss',
value: 10,
comment: '页面大小',
dataChangeLastModifiedTime: '2016-02-23',
dataChangeLastModifiedBy: '网鱼'
},
{
key: 'page',
value: 10,
comment: '页面大小',
dataChangeLastModifiedTime: '2016-02-23',
dataChangeLastModifiedBy: '网鱼'
},
{
key: 'page',
value: 10,
comment: '页面大小',
dataChangeLastModifiedTime: '2016-02-23',
dataChangeLastModifiedBy: '网鱼'
},
{
key: 'page',
value: 10,
comment: '页面大小',
dataChangeLastModifiedTime: '2016-02-23',
dataChangeLastModifiedBy: '网鱼'
},
{
key: 'page',
value: 10,
comment: '页面大小',
dataChangeLastModifiedTime: '2016-02-23',
dataChangeLastModifiedBy: '网鱼'
}];
function Namespace(data) {
Namespace.prototype.data = data;
Namespace.prototype.tableParams = new NgTableParams({
count: 9999
}, {
dataset: angular.copy(data)
});
Namespace.prototype.deleteCount = 0;
Namespace.prototype.isEditing = false;
Namespace.prototype.isAdding = false;
Namespace.prototype.add = function () {
this.isEditing = true;
this.isAdding = true;
this.tableParams.settings().dataset.unshift({
name: "",
age: null,
money: null
});
this.tableParams.sorting({});
this.tableParams.page(1);
this.tableParams.reload();
}; };
/**version*/ Namespace.prototype.cancelChanges = function () {
$scope.releaseVersions = []; this.resetTableStatus();
$scope.currentVersionIsRelease = false; var currentPage = this.tableParams.page();
this.tableParams.settings({
dataset: angular.copy(this.data)
});
// keep the user on the current page when we can
if (!this.isAdding) {
this.tableParams.page(currentPage);
}
};
VersionService.load(configLocation.appId, configLocation.env).then(function (result) { Namespace.prototype.del = function () {
$scope.releaseVersions = result; _.remove(this.tableParams.settings().dataset,
}, function (result) { function (item) {
toastr.error("获取版本失败", result); return row === item;
}); });
this.deleteCount++;
this.tableTracker.untrack(row);
this.tableParams.reload().then(function (data) {
if (data.length === 0 && this.tableParams.total() > 0) {
this.tableParams.page(this.tableParams.page() - 1);
this.tableParams.reload();
}
});
};
Namespace.prototype.hasChanges = function () {
return this.tableForm.$dirty || this.deleteCount > 0
};
Namespace.prototype.saveChanges = function () {
this.resetTableStatus();
var currentPage = this.tableParams.page();
this.data = angular.copy(this.tableParams.settings().dataset);
};
Namespace.prototype.resetTableStatus = function () {
this.isEditing = false;
this.isAdding = false;
this.deleteCount = 0;
this.tableTracker.reset();
this.tableForm.$setPristine();
}
}
$scope.switchVersion = function (versionId) { var space = new Namespace(simpleList);
configLocation.versionId = versionId; $scope.cancelChanges = function () {
$scope.currentVersionIsRelease = configLocation.versionId > 0; space.cancelChanges();
refreshConfigs();
}; };
/**config*/ $scope.space = space;
refreshConfigs(); }]);
//refresh app config infomations
function refreshConfigs() { (function () {
ConfigService.load(configLocation.appId, configLocation.env, configLocation.versionId).then(function (result) { "use strict";
$scope.config = result;
$scope.showClusterConfigs = false; application_module.run(configureDefaults);
if (result.overrideClusterConfigs && result.overrideClusterConfigs[0]) { configureDefaults.$inject = ["ngTableDefaults"];
$scope.showClusterConfigs = true;
//default selected function configureDefaults(ngTableDefaults) {
$scope.config.selectedCluster = result.overrideClusterConfigs[0]; ngTableDefaults.params.count = 5;
$scope.config.selectedClusterKVs = result.overrideClusterConfigs[0].configs; ngTableDefaults.settings.counts = [];
}
})();
//build map clusterName -> configs for switch cluster /**********
$scope.config.overrideClusters = {}; The following directives are necessary in order to track dirty state and validity of the rows
$.each(result.overrideClusterConfigs, function (index, value) { in the table as the user pages within the grid
$scope.config.overrideClusters[value.clusterName] = value.configs; ------------------------
*/
(function () {
application_module.directive("trackedTable", trackedTable);
trackedTable.$inject = [];
function trackedTable() {
return {
restrict: "A",
priority: -1,
require: "ngForm",
controller: trackedTableController
};
}
trackedTableController.$inject = ["$attrs", "$element", "$parse", "$scope"];
function trackedTableController($attrs, $element, $parse, $scope) {
var self = this;
var tableForm = $element.controller("form");
var dirtyCellsByRow = [];
var invalidCellsByRow = [];
init();
////////
function init() {
var setter = $parse($attrs.trackedTable).assign;
setter($scope, self);
$scope.$on("$destroy", function () {
setter(null);
}); });
self.reset = reset;
self.isCellDirty = isCellDirty;
self.setCellDirty = setCellDirty;
self.setCellInvalid = setCellInvalid;
self.untrack = untrack;
} }
}, function (result) {
toastr.error("加载配置出错", result); function getCellsForRow(row, cellsByRow) {
return _.find(cellsByRow, function (entry) {
return entry.row === row;
})
}
function isCellDirty(row, cell) {
var rowCells = getCellsForRow(row, dirtyCellsByRow);
return rowCells && rowCells.cells.indexOf(cell) !== -1;
}
function reset() {
dirtyCellsByRow = [];
invalidCellsByRow = [];
setInvalid(false);
}
function setCellDirty(row, cell, isDirty) {
setCellStatus(row, cell, isDirty, dirtyCellsByRow);
}
function setCellInvalid(row, cell, isInvalid) {
setCellStatus(row, cell, isInvalid, invalidCellsByRow);
setInvalid(invalidCellsByRow.length > 0);
}
function setCellStatus(row, cell, value, cellsByRow) {
var rowCells = getCellsForRow(row, cellsByRow);
if (!rowCells && !value) {
return;
}
if (value) {
if (!rowCells) {
rowCells = {
row: row,
cells: []
};
cellsByRow.push(rowCells);
}
if (rowCells.cells.indexOf(cell) === -1) {
rowCells.cells.push(cell);
}
} else {
_.remove(rowCells.cells, function (item) {
return cell === item;
});
if (rowCells.cells.length === 0) {
_.remove(cellsByRow, function (item) {
return rowCells === item;
}); });
} }
}
}
function setInvalid(isInvalid) {
self.$invalid = isInvalid;
self.$valid = !isInvalid;
}
function untrack(row) {
_.remove(invalidCellsByRow, function (item) {
return item.row === row;
});
_.remove(dirtyCellsByRow, function (item) {
return item.row === row;
});
setInvalid(invalidCellsByRow.length > 0);
}
}
})();
(function () {
application_module.directive("trackedTableRow", trackedTableRow);
//switch cluster trackedTableRow.$inject = [];
$scope.selectCluster = function () {
$scope.config.selectedClusterKVs = $scope.config.overrideClusters[$scope.config.selectedCluster.clusterName]; function trackedTableRow() {
return {
restrict: "A",
priority: -1,
require: ["^trackedTable", "ngForm"],
controller: trackedTableRowController
}; };
}
}]); trackedTableRowController.$inject = ["$attrs", "$element", "$parse", "$scope"];
function trackedTableRowController($attrs, $element, $parse, $scope) {
var self = this;
var row = $parse($attrs.trackedTableRow)($scope);
var rowFormCtrl = $element.controller("form");
var trackedTableCtrl = $element.controller("trackedTable");
self.isCellDirty = isCellDirty;
self.setCellDirty = setCellDirty;
self.setCellInvalid = setCellInvalid;
function isCellDirty(cell) {
return trackedTableCtrl.isCellDirty(row, cell);
}
function setCellDirty(cell, isDirty) {
trackedTableCtrl.setCellDirty(row, cell, isDirty)
}
function setCellInvalid(cell, isInvalid) {
trackedTableCtrl.setCellInvalid(row, cell, isInvalid)
}
}
})();
(function() {
application_module.directive("trackedTableCell", trackedTableCell);
trackedTableCell.$inject = [];
function trackedTableCell() {
return {
restrict: "A",
priority: -1,
scope: true,
require: ["^trackedTableRow", "ngForm"],
controller: trackedTableCellController
};
}
trackedTableCellController.$inject = ["$attrs", "$element", "$scope"];
function trackedTableCellController($attrs, $element, $scope) {
var self = this;
var cellFormCtrl = $element.controller("form");
var cellName = cellFormCtrl.$name;
var trackedTableRowCtrl = $element.controller("trackedTableRow");
if (trackedTableRowCtrl.isCellDirty(cellName)) {
cellFormCtrl.$setDirty();
} else {
cellFormCtrl.$setPristine();
}
// note: we don't have to force setting validaty as angular will run validations
// when we page back to a row that contains invalid data
$scope.$watch(function() {
return cellFormCtrl.$dirty;
}, function(newValue, oldValue) {
if (newValue === oldValue) return;
trackedTableRowCtrl.setCellDirty(cellName, newValue);
});
$scope.$watch(function() {
return cellFormCtrl.$invalid;
}, function(newValue, oldValue) {
if (newValue === oldValue) return;
trackedTableRowCtrl.setCellInvalid(cellName, newValue);
});
}
})();
appService.service('AppService', ['$resource', '$q', function ($resource, $q) { appService.service('AppService', ['$resource', '$q', function ($resource, $q) {
var app_resource = $resource('/apps/:appId', {}, { var app_resource = $resource('/apps/:appId', {}, {
load_navtree:{
methode: 'GET',
isArray:false,
url:'/apps/:appId/navtree'
},
load_app: { load_app: {
method: 'GET', method: 'GET',
isArray: false isArray: false
...@@ -10,6 +15,17 @@ appService.service('AppService', ['$resource', '$q', function ($resource, $q) { ...@@ -10,6 +15,17 @@ appService.service('AppService', ['$resource', '$q', function ($resource, $q) {
} }
}); });
return { return {
load_nav_tree: function loadNavTree(appId){
var d = $q.defer();
app_resource.load_navtree({
appId: appId
}, function(result){
d.resolve(result);
}, function(result){
d.reject(result);
});
return d.promise;
},
add: function add(app) { add: function add(app) {
var d = $q.defer(); var d = $q.defer();
app_resource.add_app({}, app, function (result) { app_resource.add_app({}, app, function (result) {
......
appService.service("ConfigService", ['$resource', '$q', function ($resource, $q) { appService.service("ConfigService", ['$resource', '$q', function ($resource, $q) {
var config_source = $resource("/configs/:appId/:env/:versionId", {}, { var config_source = $resource("", {}, {
load_config: { load_all_groups: {
method: 'GET', method:'GET',
isArray: false isArray: true,
url:'/apps/:appId/env/:env/clusters/:clusterName/namespaces'
} }
}); });
return { return {
load: function (appId, env, versionId) { load_all_namespaces: function (appId, env, clusterName) {
var d = $q.defer(); var d = $q.defer();
config_source.load_config({ config_source.load_all_groups({
appId: appId, appId: appId,
env: env, env: env,
versionId: versionId clusterName: clusterName
}, function (result) { }, function (result) {
d.resolve(result); d.resolve(result);
}, function (result) { }, function (result) {
de.reject(result); d.reject(result);
}); });
return d.promise; return d.promise;
} }
......
body { body {
color: #797979; color: #797979;
background: #f1f2f7; background: #fff;
padding: 0px !important; padding: 0px !important;
margin: 0px !important; margin: 0px !important;
font-size: 13px; font-size: 13px;
padding-bottom: 50px; min-height: 650px;
} }
a{ a{
cursor: pointer; cursor: pointer;
} }
.container {
width: 100%;
min-height: 550px;
}
.footer { .footer {
height: 50px; height: 100px;
width: 100%; width: 100%;
background: #F6F6F6; background: #F6F6F6;
padding-top: 10px; padding-top: 40px;
} }
/*panel*/ /*panel*/
.panel-heading { .panel-heading {
font-size: 16px; height: 45px;
font-size: 14px;
background-color: #f5f5f5; background-color: #f5f5f5;
padding: 5px 20px; padding: 5px 20px;
}
/*sec-panel*/
.sec-panel {
/*padding-left: 5%;*/
}
.sec-panel .panel-heading {
font-size: 14px;
height: 28px;
padding: 5px 20px;
} }
table th { table th {
...@@ -49,22 +35,93 @@ table th { ...@@ -49,22 +35,93 @@ table th {
background-color: #f5f5f5; background-color: #f5f5f5;
} }
.app .tab-content {
background-color: #ffffff;
}
#config-info { #config-info {
padding: 20px 0px; min-height: 500px;
background-color: #ffffff;
} }
#config-info .nav { #config-info .nav {
background-color: #f5f5f5; background-color: #f5f5f5;
} }
#config-info .config-info-container { #config-edit {
padding: 20px 20px; border: 1px solid #ddd;
border-radius: 3px;
}
#config-edit .panel-heading {
border-bottom: 1px solid #ddd;
}
.tocify-header {
font-size: 14px;
}
.tocify-subheader {
font-size: 13px;
}
.Hide {
display: none;
}
.config-item-container .config-items {
height: 500px;
overflow: scroll;
}
.historyview {
padding: 50px 20px;
}
.historyview .commit {
padding: 5px 15px;
border: 1px solid #ddd;
}
.historyview img {
position: absolute;
left: -28px;
}
.historyview .media .row {
padding-left: 35px;
}
.historyview .list {
position: relative;
border-left: 3px solid #ddd;
} }
#config-info nav { .line {
margin-top: -20px; width: 20px;
border: 1px solid #ddd;
} }
.editable-table > tbody > tr > td {
padding: 4px
}
.editable-text {
padding-left: 4px;
padding-top: 4px;
padding-bottom: 4px;
display: inline-block;
}
.editable-table tbody > tr > td > .controls {
/ / width: 100 %
}
.editable-input {
padding-left: 3px;
}
.editable-input.input-sm {
height: 30px;
font-size: 14px;
padding-top: 4px;
padding-bottom: 4px;
}
This source diff could not be displayed because it is too large. You can view the blob instead.
/*! ngTable v1.0.0-beta.9 by Vitalii Savchuk(esvit666@gmail.com) - https://github.com/esvit/ng-table - New BSD License */
!function(a,b){"use strict";return"function"==typeof define&&define.amd?void define(["angular"],function(a){return b(a)}):b(a)}(window.angular||null,function(a){"use strict";return function(){a.module("ngTable",[])}(),function(){a.module("ngTable").value("ngTableDefaults",{params:{},settings:{}})}(),function(){function b(b){function c(b,c){var f=b.charAt(0).toUpperCase()+b.substring(1),g={};return g["on"+f]=d(b),g["publish"+f]=e(b),a.extend(c,g)}function d(c){return function(d){var e=a.identity,g=b;if(2===arguments.length?a.isFunction(arguments[1].$new)?g=arguments[1]:e=arguments[1]:arguments.length>2&&(g=arguments[1],e=arguments[2]),a.isObject(e)){var h=e;e=function(a){return a===h}}return g.$on("ngTable:"+c,function(a,b){if(!b.isNullInstance){var c=f(arguments,2),g=[b].concat(c);e.apply(this,g)&&d.apply(this,g)}})}}function e(a){return function(){var c=["ngTable:"+a].concat(Array.prototype.slice.call(arguments));b.$broadcast.apply(b,c)}}function f(a,b){return Array.prototype.slice.call(a,null==b?1:b)}var g={};return g=c("afterCreated",g),g=c("afterReloadData",g),g=c("datasetChanged",g),g=c("pagesChanged",g)}a.module("ngTable").factory("ngTableEventsChannel",b),b.$inject=["$rootScope"]}(),function(){function b(){function b(){c()}function c(){f=g}function d(b){var c=a.extend({},f,b);c.aliasUrls=a.extend({},f.aliasUrls,b.aliasUrls),f=c}function e(){function b(b,c){return a.isObject(b)&&(b=b.id),-1!==b.indexOf("/")?b:e.getUrlForAlias(b,c)}function c(a){return f.aliasUrls[a]||f.defaultBaseUrl+a+f.defaultExt}var d,e={config:d,getTemplateUrl:b,getUrlForAlias:c};return Object.defineProperty(e,"config",{get:function(){return d=d||a.copy(f)},enumerable:!0}),e}var f,g={defaultBaseUrl:"ng-table/filters/",defaultExt:".html",aliasUrls:{}};this.$get=e,this.resetConfigs=c,this.setConfig=d,b(),e.$inject=[]}a.module("ngTable").provider("ngTableFilterConfig",b),b.$inject=[]}(),function(){function b(){function b(b){function d(d){var e=d.settings().filterOptions;return a.isFunction(e.filterFn)?e.filterFn:b(e.filterFilterName||c.filterFilterName)}function e(){return b(c.sortingFilterName)}function f(a,b){if(!b.hasFilter())return a;var c=b.filter(!0),e=Object.keys(c),f=e.reduce(function(a,b){return a=j(a,c[b],b)},{}),g=d(b);return g.call(b,a,f,b.settings().filterOptions.filterComparator)}function g(a,b){var c=a.slice((b.page()-1)*b.count(),b.page()*b.count());return b.total(a.length),c}function h(a,b){var c=b.orderBy(),d=e(b);return c.length?d(a,c):a}function i(b,c){if(null==b)return[];var d=a.extend({},k,c.settings().dataOptions),e=d.applyFilter?f(b,c):b,i=d.applySort?h(e,c):e;return d.applyPaging?g(i,c):i}function j(a,b,c){var d=c.split("."),e=a,f=d[d.length-1],g=e,h=d.slice(0,d.length-1);return h.forEach(function(a){g.hasOwnProperty(a)||(g[a]={}),g=g[a]}),g[f]=b,e}var k={applyFilter:!0,applySort:!0,applyPaging:!0};return i.applyPaging=g,i.getFilterFn=d,i.getOrderByFn=e,i}var c=this;c.$get=b,c.filterFilterName="filter",c.sortingFilterName="orderBy",b.$inject=["$filter"]}a.module("ngTable").provider("ngTableDefaultGetData",b),b.$inject=[]}(),function(){function b(a){function b(b){return function(){var c=a.defer(),d=b.apply(this,[c].concat(Array.prototype.slice.call(arguments)));return d||(d=c.promise),d}}return b}a.module("ngTable").factory("ngTableGetDataBcShim",b),b.$inject=["$q"]}(),function(){a.module("ngTable").factory("ngTableColumn",[function(){function b(b,d,f){var g=Object.create(b),h=c();for(var i in h)void 0===g[i]&&(g[i]=h[i]),a.isFunction(g[i])||!function(a){var c=function d(){return 1!==arguments.length||e(arguments[0])?b[a]:void d.assign(null,arguments[0])};c.assign=function(c,d){b[a]=d},g[a]=c}(i),function(c){var h=g[c];g[c]=function(){if(1!==arguments.length||e(arguments[0])){var c=arguments[0]||d,i=Object.create(c);return a.extend(i,{$column:g,$columns:f}),h.call(b,i)}h.assign(null,arguments[0])},h.assign&&(g[c].assign=h.assign)}(i);return g}function c(){return{"class":d(""),filter:d(!1),groupable:d(!1),filterData:a.noop,headerTemplateURL:d(!1),headerTitle:d(""),sortable:d(!1),show:d(!0),title:d(""),titleAlt:d("")}}function d(a){var b=a,c=function d(){return 1!==arguments.length||e(arguments[0])?b:void d.assign(null,arguments[0])};return c.assign=function(a,c){b=c},c}function e(b){return null!=b&&a.isFunction(b.$new)}return{buildColumn:b}}])}(),function(){a.module("ngTable").factory("NgTableParams",["$q","$log","$filter","ngTableDefaults","ngTableGetDataBcShim","ngTableDefaultGetData","ngTableEventsChannel",function(b,c,d,e,f,g,h){var i=function(a){return!isNaN(parseFloat(a))&&isFinite(a)},j=function(d,j){function k(b){var c=D.groupOptions&&D.groupOptions.defaultSort;if(a.isFunction(b))return null==b.sortDirection&&(b.sortDirection=c),b;if(a.isString(b)){var d={};return d[b]=c,d}if(a.isObject(b)){for(var e in b)null==b[e]&&(b[e]=c);return b}return b}function l(a){var b=[];for(var c in a)b.push(("asc"===a[c]?"+":"-")+c);return b}function m(){var b={params:C};return a.isFunction(C.group)&&(b.groupSortDirection=C.group.sortDirection),b}function n(){var b=C.filter&&C.filter.$,c=t&&t.params.filter&&t.params.filter.$;return!a.equals(b,c)}function o(){D.filterOptions.filterDelay===z.filterDelay&&D.total<=D.filterOptions.filterDelayThreshold&&D.getData===B.getData&&(D.filterOptions.filterDelay=0)}function p(){var a=D.getDataFnAdaptor(D.getData);return b.when(a.call(D,v))}function q(){var a=D.getGroupsFnAdaptor(D.getGroups);return b.when(a.call(D,v))}function r(a){var c=D.interceptors||[];return c.reduce(function(a,c){var d=c.response&&c.response.bind(c)||b.when,e=c.responseError&&c.responseError.bind(c)||b.reject;return a.then(function(a){return d(a,v)},function(a){return e(a,v)})},a())}function s(){function c(a){return g(a.settings().dataset,a)}function d(c){var d,f=c.group(),h=void 0;if(a.isFunction(f))d=f,h=f.sortDirection;else{var i=Object.keys(f)[0];h=f[i],d=function(a){return e(a,i)}}var j=c.settings(),k=j.dataOptions;j.dataOptions={applyPaging:!1};var m=j.getDataFnAdaptor(j.getData),n=b.when(m.call(j,c));return n.then(function(b){var e={};a.forEach(b,function(a){var b=d(a);e[b]=e[b]||{data:[],$hideRows:!j.groupOptions.isExpanded,value:b},e[b].data.push(a)});var f=[];for(var i in e)f.push(e[i]);if(h){var k=g.getOrderByFn(),m=l({value:h});f=k(f,m)}return g.applyPaging(f,c)})["finally"](function(){j.dataOptions=k})}function e(a,b){return"string"==typeof b&&(b=b.split(".")),void 0===a?void 0:0===b.length?a:null===a?void 0:e(a[b[0]],b.slice(1))}return{getDataFnAdaptor:a.identity,getGroupsFnAdaptor:a.identity,getData:c,getGroups:d}}"boolean"==typeof d&&(this.isNullInstance=!0);var t,u,v=this,w=!1,x=[],y=function(){D.debugMode&&c.debug&&c.debug.apply(c,arguments)},z={filterComparator:void 0,filterDelay:500,filterDelayThreshold:1e4,filterFilterName:void 0,filterFn:void 0,filterLayout:"stack"},A={defaultSort:"asc",isExpanded:!0},B=s();this.data=[],this.parameters=function(b,c){if(c=c||!1,a.isDefined(b)){for(var d in b){var e=b[d];if(c&&d.indexOf("[")>=0){for(var f=d.split(/\[(.*)\]/).reverse(),g="",h=0,j=f.length;j>h;h++){var l=f[h];if(""!==l){var m=e;e={},e[g=l]=i(m)?parseFloat(m):m}}"sorting"===g&&(C[g]={}),C[g]=a.extend(C[g]||{},e[g])}else C[d]="group"===d?k(b[d]):i(b[d])?parseFloat(b[d]):b[d]}return y("ngTable: set parameters",C),this}return C},this.settings=function(b){if(a.isDefined(b)){b.filterOptions&&(b.filterOptions=a.extend({},D.filterOptions,b.filterOptions)),b.groupOptions&&(b.groupOptions=a.extend({},D.groupOptions,b.groupOptions)),a.isArray(b.dataset)&&(b.total=b.dataset.length),b.getData&&b.getData.length>1&&(b.getDataFnAdaptor=f),b.getGroups&&b.getGroups.length>2&&(b.getGroupsFnAdaptor=f);var c=D.dataset;D=a.extend(D,b),a.isArray(b.dataset)&&o();var d=b.hasOwnProperty("dataset")&&b.dataset!=c;if(d){w&&this.page(1),w=!1;var e=function(){h.publishDatasetChanged(v,b.dataset,c)};x?x.push(e):e()}return y("ngTable: set settings",D),this}return D},this.page=function(b){return a.isDefined(b)?this.parameters({page:b}):C.page},this.total=function(b){return a.isDefined(b)?this.settings({total:b}):D.total},this.count=function(b){return a.isDefined(b)?this.parameters({count:b,page:1}):C.count},this.filter=function(b){if(a.isDefined(b)&&a.isObject(b))return this.parameters({filter:b,page:1});if(b===!0){for(var c=Object.keys(C.filter),d={},e=0;e<c.length;e++){var f=C.filter[c[e]];null!=f&&""!==f&&(d[c[e]]=f)}return d}return C.filter},this.group=function(b,c){if(!a.isDefined(b))return C.group;var d={page:1};if(a.isFunction(b)&&a.isDefined(c))b.sortDirection=c,d.group=b;else if(a.isDefined(b)&&a.isDefined(c)){var e={};e[b]=c,d.group=e}else d.group=b;return this.parameters(d),this},this.sorting=function(b){if(2==arguments.length){var c={};return c[b]=arguments[1],this.parameters({sorting:c}),this}return a.isDefined(b)?this.parameters({sorting:b}):C.sorting},this.isSortBy=function(b,c){return void 0!==c?a.isDefined(C.sorting[b])&&C.sorting[b]==c:a.isDefined(C.sorting[b])},this.orderBy=function(){return l(C.sorting)},this.generatePagesArray=function(a,b,c,d){arguments.length||(a=this.page(),b=this.total(),c=this.count());var e,f,g,h,i;if(d=d&&6>d?6:d,i=[],h=Math.ceil(b/c),h>1){i.push({type:"prev",number:Math.max(1,a-1),active:a>1}),i.push({type:"first",number:1,active:a>1,current:1===a}),f=Math.round((D.paginationMaxBlocks-D.paginationMinBlocks)/2),g=Math.max(2,a-f),e=Math.min(h-1,a+2*f-(a-g)),g=Math.max(2,g-(2*f-(e-g)));for(var j=g;e>=j;)i.push(j===g&&2!==j||j===e&&j!==h-1?{type:"more",active:!1}:{type:"page",number:j,active:a!==j,current:a===j}),j++;i.push({type:"last",number:h,active:a!==h,current:a===h}),i.push({type:"next",number:Math.min(h,a+1),active:h>a})}return i},this.isDataReloadRequired=function(){return!w||!a.equals(m(),t)||n()},this.hasFilter=function(){return Object.keys(this.filter(!0)).length>0},this.hasGroup=function(b,c){return null==b?a.isFunction(C.group)||Object.keys(C.group).length>0:a.isFunction(b)?null==c?C.group===b:C.group===b&&b.sortDirection===c:null==c?-1!==Object.keys(C.group).indexOf(b):C.group[b]===c},this.hasFilterChanges=function(){var b=t&&t.params.filter;return!a.equals(C.filter,b)||n()},this.url=function(b){function c(a,c){b?e.push(c+"="+encodeURIComponent(a)):e[c]=encodeURIComponent(a)}function d(b,c){return"group"===c?!0:a.isDefined(b)&&""!==b}b=b||!1;var e=b?[]:{};for(var f in C)if(C.hasOwnProperty(f)){var g=C[f],h=encodeURIComponent(f);if("object"==typeof g){for(var i in g)if(d(g[i],f)){var j=h+"["+encodeURIComponent(i)+"]";c(g[i],j)}}else!a.isFunction(g)&&d(g,f)&&c(g,h)}return e},this.reload=function(){var c=this,d=null;D.$loading=!0,t=a.copy(m()),w=!0,d=r(c.hasGroup()?q:p),y("ngTable: reload data");var e=c.data;return d.then(function(a){return D.$loading=!1,u=null,c.data=a,h.publishAfterReloadData(c,a,e),c.reloadPages(),D.$scope&&D.$scope.$emit("ngTableAfterReloadData"),a})["catch"](function(a){return u=t,b.reject(a)})},this.hasErrorState=function(){return!(!u||!a.equals(u,m()))},this.reloadPages=function(){var b;return function(){var c=b,d=v.generatePagesArray(v.page(),v.total(),v.count());a.equals(c,d)||(b=d,h.publishPagesChanged(this,d,c))}}();var C={page:1,count:10,filter:{},sorting:{},group:{}};a.extend(C,e.params);var D={$scope:null,$loading:!1,dataset:null,total:0,defaultSort:"desc",filterOptions:a.copy(z),groupOptions:a.copy(A),counts:[10,25,50,100],interceptors:[],paginationMaxBlocks:11,paginationMinBlocks:5,sortingIndicator:"span"};return this.settings(B),this.settings(e.settings),this.settings(j),this.parameters(d,!0),h.publishAfterCreated(this),a.forEach(x,function(a){a()}),x=null,this};return j}]),a.module("ngTable").factory("ngTableParams",["NgTableParams",function(a){return a}])}(),function(){a.module("ngTable").controller("ngTableController",["$scope","NgTableParams","$timeout","$parse","$compile","$attrs","$element","ngTableColumn","ngTableEventsChannel",function(b,c,d,e,f,g,h,i,j){function k(a){if(a&&!b.params.hasErrorState()){b.params.settings().$scope=b;var c=b.params,d=c.settings().filterOptions;if(c.hasFilterChanges()){var e=function(){c.page(1),c.reload()};d.filterDelay?r(e,d.filterDelay):e()}else c.reload()}}function l(){g.showFilter?b.$parent.$watch(g.showFilter,function(a){b.show_filter=a}):b.$watch(o,function(a){b.show_filter=a}),g.disableFilter&&b.$parent.$watch(g.disableFilter,function(a){b.$filterRow.disabled=a})}function m(){if(b.$groupRow={},g.showGroup){var a=e(g.showGroup);b.$parent.$watch(a,function(a){b.$groupRow.show=a}),a.assign&&b.$watch("$groupRow.show",function(c){a.assign(b.$parent,c)})}else b.$watch("params.hasGroup()",function(a){b.$groupRow.show=a})}function n(){return(b.$columns||[]).filter(function(a){return a.show(b)})}function o(){return b.$columns?p(b.$columns,function(a){return a.show(b)&&a.filter(b)}):!1}function p(a,b){for(var c=!1,d=0;d<a.length;d++){var e=a[d];if(b(e)){c=!0;break}}return c}function q(){function a(a,c){var d=n();a.hasGroup()?(b.$groups=c||[],b.$groups.visibleColumnCount=d.length):(b.$data=c||[],b.$data.visibleColumnCount=d.length)}function c(a,c){b.pages=c}function d(a){return b.params===a}j.onAfterReloadData(a,b,d),j.onPagesChanged(c,b,d)}b.$filterRow={},b.$loading=!1,b.hasOwnProperty("params")||(b.params=new c(!0)),b.params.settings().$scope=b;var r=function(){var a=0;return function(b,c){d.cancel(a),a=d(b,c)}}();b.$watch("params",function(a,b){a!==b&&a&&a.reload()},!1),b.$watch("params.isDataReloadRequired()",k),this.compileDirectiveTemplates=function(){if(!h.hasClass("ng-table")){b.templates={header:g.templateHeader?g.templateHeader:"ng-table/header.html",pagination:g.templatePagination?g.templatePagination:"ng-table/pager.html"},h.addClass("ng-table");var c=null,d=!1;a.forEach(h.children(),function(a){"THEAD"===a.tagName&&(d=!0)}),d||(c=a.element(document.createElement("thead")).attr("ng-include","templates.header"),h.prepend(c));var e=a.element(document.createElement("div")).attr({"ng-table-pagination":"params","template-url":"templates.pagination"});h.after(e),c&&f(c)(b),f(e)(b)}},this.loadFilterData=function(c){a.forEach(c,function(c){var d;if(d=c.filterData(b),!d)return void delete c.filterData;if(a.isObject(d)&&(a.isObject(d.promise)||a.isFunction(d.then))){var e=a.isFunction(d.then)?d:d.promise;return delete c.filterData,e.then(function(b){a.isArray(b)||a.isFunction(b)||a.isObject(b)||(b=[]),c.data=b})}return c.data=d})},this.buildColumns=function(a){var c=[];return(a||[]).forEach(function(a){c.push(i.buildColumn(a,b,c))}),c},this.parseNgTableDynamicExpr=function(a){if(!a||a.indexOf(" with ")>-1){var b=a.split(/\s+with\s+/);return{tableParams:b[0],columns:b[1]}}throw new Error("Parse error (expected example: ng-table-dynamic='tableParams with cols')")},this.setupBindingsToInternalScope=function(c){var d=e(c);b.$watch(d,function(c){a.isUndefined(c)||(b.paramsModel=d,b.params=c)},!1),l(),m()},q()}])}(),function(){a.module("ngTable").directive("ngTable",["$q","$parse",function(b,c){return{restrict:"A",priority:1001,scope:!0,controller:"ngTableController",compile:function(b){var d,e,f=[],g=0,h=[];return a.forEach(b.find("tr"),function(b){h.push(a.element(b))}),d=h.filter(function(a){return!a.hasClass("ng-table-group")})[0],e=h.filter(function(a){return a.hasClass("ng-table-group")})[0],d?(a.forEach(d.find("td"),function(b){var d=a.element(b);if(!d.attr("ignore-cell")||"true"!==d.attr("ignore-cell")){var h=function(a){return d.attr("x-data-"+a)||d.attr("data-"+a)||d.attr(a)},i=function(a,b){d.attr("x-data-"+a)?d.attr("x-data-"+a,b):d.attr("data"+a)?d.attr("data"+a,b):d.attr(a,b)},j=function(a){var b=h(a);if(!b)return void 0;var d,e=function(a){return void 0!==d?d:c(b)(a)};return e.assign=function(a,e){var f=c(b);f.assign?f.assign(a.$parent,e):d=e},e},k=h("title-alt")||h("title");k&&d.attr("data-title-text","{{"+k+"}}"),f.push({id:g++,title:j("title"),titleAlt:j("title-alt"),headerTitle:j("header-title"),sortable:j("sortable"),"class":j("header-class"),filter:j("filter"),groupable:j("groupable"),headerTemplateURL:j("header"),filterData:j("filter-data"),show:d.attr("ng-if")?j("ng-if"):void 0}),(e||d.attr("ng-if"))&&i("ng-if","$columns["+(f.length-1)+"].show(this)")}}),function(a,b,c,d){a.$columns=f=d.buildColumns(f),d.setupBindingsToInternalScope(c.ngTable),d.loadFilterData(f),d.compileDirectiveTemplates()}):void 0}}}])}(),function(){a.module("ngTable").directive("ngTableDynamic",[function(){return{restrict:"A",priority:1001,scope:!0,controller:"ngTableController",compile:function(b){var c;return a.forEach(b.find("tr"),function(b){b=a.element(b),b.hasClass("ng-table-group")||c||(c=b)}),c?(a.forEach(c.find("td"),function(b){var c=a.element(b),d=function(a){return c.attr("x-data-"+a)||c.attr("data-"+a)||c.attr(a)},e=d("title");e||c.attr("data-title-text","{{$columns[$index].titleAlt(this) || $columns[$index].title(this)}}");var f=c.attr("ng-if");f||c.attr("ng-if","$columns[$index].show(this)")}),function(a,b,c,d){var e=d.parseNgTableDynamicExpr(c.ngTableDynamic);d.setupBindingsToInternalScope(e.tableParams),d.compileDirectiveTemplates(),a.$watchCollection(e.columns,function(b){a.$columns=d.buildColumns(b),d.loadFilterData(a.$columns)})}):void 0}}}])}(),function(){function b(a){function b(b,c,d){var e=a(d.ngTableColumnsBinding).assign;e&&b.$watch("$columns",function(a){var c=(a||[]).slice(0);e(b,c)})}var c={restrict:"A",require:"ngTable",link:b};return c}a.module("ngTable").directive("ngTableColumnsBinding",b),b.$inject=["$parse"]}(),function(){a.module("ngTable").directive("ngTablePagination",["$compile","ngTableEventsChannel",function(b,c){return{restrict:"A",scope:{params:"=ngTablePagination",templateUrl:"="},replace:!1,link:function(d,e){c.onAfterReloadData(function(a){d.pages=a.generatePagesArray()},d,function(a){return a===d.params}),d.$watch("templateUrl",function(c){if(!a.isUndefined(c)){var f=a.element(document.createElement("div"));f.attr({"ng-include":"templateUrl"}),e.append(f),b(f)(d)}})}}}])}(),function(){function b(b,c){b.config=c,b.getFilterCellCss=function(a,b){if("horizontal"!==b)return"s12";var c=Object.keys(a).length,d=parseInt(12/c,10);return"s"+d},b.getFilterPlaceholderValue=function(b){return a.isObject(b)?b.placeholder:""}}a.module("ngTable").controller("ngTableFilterRowController",b),b.$inject=["$scope","ngTableFilterConfig"]}(),function(){function b(){var a={restrict:"E",replace:!0,templateUrl:"ng-table/filterRow.html",scope:!0,controller:"ngTableFilterRowController"};return a}a.module("ngTable").directive("ngTableFilterRow",b),b.$inject=[]}(),function(){function b(b){function c(){b.getGroupables=g,b.getGroupTitle=f,b.getVisibleColumns=h,b.groupBy=i,b.isSelectedGroup=j,b.toggleDetail=l,b.$watch("params.group()",k,!0)}function d(){var a;a=b.params.hasGroup(b.$selGroup,"asc")?"desc":b.params.hasGroup(b.$selGroup,"desc")?"":"asc",b.params.group(b.$selGroup,a)}function e(a){return b.$columns.filter(function(c){return c.groupable(b)===a})[0]}function f(c){return a.isFunction(c)?c.title:c.title(b)}function g(){var a=b.$columns.filter(function(a){return a.groupable(b)});return m.concat(a)}function h(){return b.$columns.filter(function(a){return a.show(b)})}function i(a){j(a)?d():b.params.group(a.groupable?a.groupable(b):a)}function j(a){return a.groupable?a.groupable(b)===b.$selGroup:a===b.$selGroup}function k(c){var d=e(b.$selGroup);if(d&&d.show.assign&&d.show.assign(b,!0),a.isFunction(c))m=[c],b.$selGroup=c,b.$selGroupTitle=c.title;else{var f=Object.keys(c||{})[0],g=e(f);g&&(b.$selGroupTitle=g.title(b),b.$selGroup=f,g.show.assign&&g.show.assign(b,!1))}}function l(){return b.params.settings().groupOptions.isExpanded=!b.params.settings().groupOptions.isExpanded,b.params.reload()}var m=[];c()}a.module("ngTable").controller("ngTableGroupRowController",b),b.$inject=["$scope"]}(),function(){function b(){var a={restrict:"E",replace:!0,templateUrl:"ng-table/groupRow.html",scope:!0,controller:"ngTableGroupRowController",controllerAs:"dctrl"};return a}a.module("ngTable").directive("ngTableGroupRow",b),b.$inject=[]}(),function(){function b(a){function b(b,c){var d=b.sortable&&b.sortable();if(d){var e=a.params.settings().defaultSort,f="asc"===e?"desc":"asc",g=a.params.sorting()&&a.params.sorting()[d]&&a.params.sorting()[d]===e,h=c.ctrlKey||c.metaKey?a.params.sorting():{};h[d]=g?f:e,a.params.parameters({sorting:h})}}a.sortBy=b}a.module("ngTable").controller("ngTableSorterRowController",b),b.$inject=["$scope"]}(),function(){function b(){var a={restrict:"E",replace:!0,templateUrl:"ng-table/sorterRow.html",scope:!0,controller:"ngTableSorterRowController"};return a}a.module("ngTable").directive("ngTableSorterRow",b),b.$inject=[]}(),function(){function b(){var a={restrict:"A",controller:c};return a}function c(b,c,d,e){function f(){j=c(d.ngTableSelectFilterDs)(b),b.$watch(function(){return j.data},g)}function g(){i(j).then(function(a){a&&!h(a)&&a.unshift({id:"",title:""}),a=a||[],b.$selectData=a})}function h(a){for(var b,c=0;c<a.length;c++){var d=a[c];if(d&&""===d.id){b=!0;break}}return b}function i(b){var c=a.isFunction(b.data)?b.data():b.data;return e.when(c)}var j={};f()}a.module("ngTable").directive("ngTableSelectFilterDs",b),b.$inject=[],c.$inject=["$scope","$parse","$attrs","$q"]}(),a.module("ngTable").run(["$templateCache",function(a){a.put("ng-table/filterRow.html",'<tr ng-show="show_filter" class="ng-table-filters"> <th data-title-text="{{$column.titleAlt(this) || $column.title(this)}}" ng-repeat="$column in $columns" ng-if="$column.show(this)" class="filter {{$column.class(this)}}" ng-class="params.settings().filterOptions.filterLayout===\'horizontal\' ? \'filter-horizontal\' : \'\'"> <div ng-repeat="(name, filter) in $column.filter(this)" ng-include="config.getTemplateUrl(filter)" class="filter-cell" ng-class="[getFilterCellCss($column.filter(this), params.settings().filterOptions.filterLayout), $last ? \'last\' : \'\']"> </div> </th> </tr> '),a.put("ng-table/filters/number.html",'<input type="number" name="{{name}}" ng-disabled="$filterRow.disabled" ng-model="params.filter()[name]" class="input-filter form-control" placeholder="{{getFilterPlaceholderValue(filter, name)}}"/> '),a.put("ng-table/filters/select-multiple.html",'<select ng-options="data.id as data.title for data in $column.data" ng-disabled="$filterRow.disabled" multiple ng-multiple="true" ng-model="params.filter()[name]" class="filter filter-select-multiple form-control" name="{{name}}"> </select> '),a.put("ng-table/filters/select.html",'<select ng-options="data.id as data.title for data in $selectData" ng-table-select-filter-ds="$column" ng-disabled="$filterRow.disabled" ng-model="params.filter()[name]" class="filter filter-select form-control" name="{{name}}"> <option style="display:none" value=""></option> </select> '),a.put("ng-table/filters/text.html",'<input type="text" name="{{name}}" ng-disabled="$filterRow.disabled" ng-model="params.filter()[name]" class="input-filter form-control" placeholder="{{getFilterPlaceholderValue(filter, name)}}"/> '),a.put("ng-table/groupRow.html",'<tr ng-if="params.hasGroup()" ng-show="$groupRow.show" class="ng-table-group-header"> <th colspan="{{getVisibleColumns().length}}" class="sortable" ng-class="{ \'sort-asc\': params.hasGroup($selGroup, \'asc\'), \'sort-desc\':params.hasGroup($selGroup, \'desc\') }"> <a href="" ng-click="isSelectorOpen=!isSelectorOpen" class="ng-table-group-selector"> <strong class="sort-indicator">{{$selGroupTitle}}</strong> <button class="btn btn-default btn-xs ng-table-group-close" ng-click="$groupRow.show=false; $event.preventDefault(); $event.stopPropagation();"> <span class="glyphicon glyphicon-remove"></span> </button> <button class="btn btn-default btn-xs ng-table-group-toggle" ng-click="toggleDetail(); $event.preventDefault(); $event.stopPropagation();"> <span class="glyphicon" ng-class="{ \'glyphicon-resize-small\': params.settings().groupOptions.isExpanded, \'glyphicon-resize-full\': !params.settings().groupOptions.isExpanded }"></span> </button> </a> <div class="list-group" ng-if="isSelectorOpen"> <a href="" class="list-group-item" ng-repeat="group in getGroupables()" ng-click="groupBy(group)"> <strong>{{ getGroupTitle(group)}}</strong> <strong ng-class="isSelectedGroup(group) && \'sort-indicator\'"></strong> </a> </div> </th> </tr> '),a.put("ng-table/header.html","<ng-table-group-row></ng-table-group-row> <ng-table-sorter-row></ng-table-sorter-row> <ng-table-filter-row></ng-table-filter-row> "),a.put("ng-table/pager.html",'<div class="ng-cloak ng-table-pager" ng-if="params.data.length"> <div ng-if="params.settings().counts.length" class="ng-table-counts btn-group pull-right"> <button ng-repeat="count in params.settings().counts" type="button" ng-class="{\'active\':params.count()==count}" ng-click="params.count(count)" class="btn btn-default"> <span ng-bind="count"></span> </button> </div> <ul ng-if="pages.length" class="pagination ng-table-pagination"> <li ng-class="{\'disabled\': !page.active && !page.current, \'active\': page.current}" ng-repeat="page in pages" ng-switch="page.type"> <a ng-switch-when="prev" ng-click="params.page(page.number)" href="">&laquo;</a> <a ng-switch-when="first" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="page" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="more" ng-click="params.page(page.number)" href="">&#8230;</a> <a ng-switch-when="last" ng-click="params.page(page.number)" href=""><span ng-bind="page.number"></span></a> <a ng-switch-when="next" ng-click="params.page(page.number)" href="">&raquo;</a> </li> </ul> </div> '),a.put("ng-table/sorterRow.html",'<tr class="ng-table-sort-header"> <th title="{{$column.headerTitle(this)}}" ng-repeat="$column in $columns" ng-class="{ \'sortable\': $column.sortable(this), \'sort-asc\': params.sorting()[$column.sortable(this)]==\'asc\', \'sort-desc\': params.sorting()[$column.sortable(this)]==\'desc\' }" ng-click="sortBy($column, $event)" ng-if="$column.show(this)" ng-init="template=$column.headerTemplateURL(this)" class="header {{$column.class(this)}}"> <div ng-if="!template" class="ng-table-header" ng-class="{\'sort-indicator\': params.settings().sortingIndicator==\'div\'}"> <span ng-bind="$column.title(this)" ng-class="{\'sort-indicator\': params.settings().sortingIndicator==\'span\'}"></span> </div> <div ng-if="template" ng-include="template"></div> </th> </tr> ')}]),a.module("ngTable")});
//# sourceMappingURL=ng-table.min.js.map
\ No newline at end of file
!function(a,b,c,d){"use strict";var e="treeview",f={};f.settings={injectStyle:!0,levels:2,expandIcon:"glyphicon glyphicon-plus",collapseIcon:"glyphicon glyphicon-minus",emptyIcon:"glyphicon",nodeIcon:"",selectedIcon:"",checkedIcon:"glyphicon glyphicon-check",uncheckedIcon:"glyphicon glyphicon-unchecked",color:d,backColor:d,borderColor:d,onhoverColor:"#F5F5F5",selectedColor:"#FFFFFF",selectedBackColor:"#428bca",searchResultColor:"#D9534F",searchResultBackColor:d,enableLinks:!1,highlightSelected:!0,highlightSearchResults:!0,showBorder:!0,showIcon:!0,showCheckbox:!1,showTags:!1,multiSelect:!1,onNodeChecked:d,onNodeCollapsed:d,onNodeDisabled:d,onNodeEnabled:d,onNodeExpanded:d,onNodeSelected:d,onNodeUnchecked:d,onNodeUnselected:d,onSearchComplete:d,onSearchCleared:d},f.options={silent:!1,ignoreChildren:!1},f.searchOptions={ignoreCase:!0,exactMatch:!1,revealResults:!0};var g=function(b,c){return this.$element=a(b),this.elementId=b.id,this.styleId=this.elementId+"-style",this.init(c),{options:this.options,init:a.proxy(this.init,this),remove:a.proxy(this.remove,this),getNode:a.proxy(this.getNode,this),getParent:a.proxy(this.getParent,this),getSiblings:a.proxy(this.getSiblings,this),getSelected:a.proxy(this.getSelected,this),getUnselected:a.proxy(this.getUnselected,this),getExpanded:a.proxy(this.getExpanded,this),getCollapsed:a.proxy(this.getCollapsed,this),getChecked:a.proxy(this.getChecked,this),getUnchecked:a.proxy(this.getUnchecked,this),getDisabled:a.proxy(this.getDisabled,this),getEnabled:a.proxy(this.getEnabled,this),selectNode:a.proxy(this.selectNode,this),unselectNode:a.proxy(this.unselectNode,this),toggleNodeSelected:a.proxy(this.toggleNodeSelected,this),collapseAll:a.proxy(this.collapseAll,this),collapseNode:a.proxy(this.collapseNode,this),expandAll:a.proxy(this.expandAll,this),expandNode:a.proxy(this.expandNode,this),toggleNodeExpanded:a.proxy(this.toggleNodeExpanded,this),revealNode:a.proxy(this.revealNode,this),checkAll:a.proxy(this.checkAll,this),checkNode:a.proxy(this.checkNode,this),uncheckAll:a.proxy(this.uncheckAll,this),uncheckNode:a.proxy(this.uncheckNode,this),toggleNodeChecked:a.proxy(this.toggleNodeChecked,this),disableAll:a.proxy(this.disableAll,this),disableNode:a.proxy(this.disableNode,this),enableAll:a.proxy(this.enableAll,this),enableNode:a.proxy(this.enableNode,this),toggleNodeDisabled:a.proxy(this.toggleNodeDisabled,this),search:a.proxy(this.search,this),clearSearch:a.proxy(this.clearSearch,this)}};g.prototype.init=function(b){this.tree=[],this.nodes=[],b.data&&("string"==typeof b.data&&(b.data=a.parseJSON(b.data)),this.tree=a.extend(!0,[],b.data),delete b.data),this.options=a.extend({},f.settings,b),this.destroy(),this.subscribeEvents(),this.setInitialStates({nodes:this.tree},0),this.render()},g.prototype.remove=function(){this.destroy(),a.removeData(this,e),a("#"+this.styleId).remove()},g.prototype.destroy=function(){this.initialized&&(this.$wrapper.remove(),this.$wrapper=null,this.unsubscribeEvents(),this.initialized=!1)},g.prototype.unsubscribeEvents=function(){this.$element.off("click"),this.$element.off("nodeChecked"),this.$element.off("nodeCollapsed"),this.$element.off("nodeDisabled"),this.$element.off("nodeEnabled"),this.$element.off("nodeExpanded"),this.$element.off("nodeSelected"),this.$element.off("nodeUnchecked"),this.$element.off("nodeUnselected"),this.$element.off("searchComplete"),this.$element.off("searchCleared")},g.prototype.subscribeEvents=function(){this.unsubscribeEvents(),this.$element.on("click",a.proxy(this.clickHandler,this)),"function"==typeof this.options.onNodeChecked&&this.$element.on("nodeChecked",this.options.onNodeChecked),"function"==typeof this.options.onNodeCollapsed&&this.$element.on("nodeCollapsed",this.options.onNodeCollapsed),"function"==typeof this.options.onNodeDisabled&&this.$element.on("nodeDisabled",this.options.onNodeDisabled),"function"==typeof this.options.onNodeEnabled&&this.$element.on("nodeEnabled",this.options.onNodeEnabled),"function"==typeof this.options.onNodeExpanded&&this.$element.on("nodeExpanded",this.options.onNodeExpanded),"function"==typeof this.options.onNodeSelected&&this.$element.on("nodeSelected",this.options.onNodeSelected),"function"==typeof this.options.onNodeUnchecked&&this.$element.on("nodeUnchecked",this.options.onNodeUnchecked),"function"==typeof this.options.onNodeUnselected&&this.$element.on("nodeUnselected",this.options.onNodeUnselected),"function"==typeof this.options.onSearchComplete&&this.$element.on("searchComplete",this.options.onSearchComplete),"function"==typeof this.options.onSearchCleared&&this.$element.on("searchCleared",this.options.onSearchCleared)},g.prototype.setInitialStates=function(b,c){if(b.nodes){c+=1;var d=b,e=this;a.each(b.nodes,function(a,b){b.nodeId=e.nodes.length,b.parentId=d.nodeId,b.hasOwnProperty("selectable")||(b.selectable=!0),b.state=b.state||{},b.state.hasOwnProperty("checked")||(b.state.checked=!1),b.state.hasOwnProperty("disabled")||(b.state.disabled=!1),b.state.hasOwnProperty("expanded")||(!b.state.disabled&&c<e.options.levels&&b.nodes&&b.nodes.length>0?b.state.expanded=!0:b.state.expanded=!1),b.state.hasOwnProperty("selected")||(b.state.selected=!1),e.nodes.push(b),b.nodes&&e.setInitialStates(b,c)})}},g.prototype.clickHandler=function(b){this.options.enableLinks||b.preventDefault();var c=a(b.target),d=this.findNode(c);if(d&&!d.state.disabled){var e=c.attr("class")?c.attr("class").split(" "):[];-1!==e.indexOf("expand-icon")?(this.toggleExpandedState(d,f.options),this.render()):-1!==e.indexOf("check-icon")?(this.toggleCheckedState(d,f.options),this.render()):(d.selectable?this.toggleSelectedState(d,f.options):this.toggleExpandedState(d,f.options),this.render())}},g.prototype.findNode=function(a){var b=a.closest("li.list-group-item").attr("data-nodeid"),c=this.nodes[b];return c||console.log("Error: node does not exist"),c},g.prototype.toggleExpandedState=function(a,b){a&&this.setExpandedState(a,!a.state.expanded,b)},g.prototype.setExpandedState=function(b,c,d){c!==b.state.expanded&&(c&&b.nodes?(b.state.expanded=!0,d.silent||this.$element.trigger("nodeExpanded",a.extend(!0,{},b))):c||(b.state.expanded=!1,d.silent||this.$element.trigger("nodeCollapsed",a.extend(!0,{},b)),b.nodes&&!d.ignoreChildren&&a.each(b.nodes,a.proxy(function(a,b){this.setExpandedState(b,!1,d)},this))))},g.prototype.toggleSelectedState=function(a,b){a&&this.setSelectedState(a,!a.state.selected,b)},g.prototype.setSelectedState=function(b,c,d){c!==b.state.selected&&(c?(this.options.multiSelect||a.each(this.findNodes("true","g","state.selected"),a.proxy(function(a,b){this.setSelectedState(b,!1,d)},this)),b.state.selected=!0,d.silent||this.$element.trigger("nodeSelected",a.extend(!0,{},b))):(b.state.selected=!1,d.silent||this.$element.trigger("nodeUnselected",a.extend(!0,{},b))))},g.prototype.toggleCheckedState=function(a,b){a&&this.setCheckedState(a,!a.state.checked,b)},g.prototype.setCheckedState=function(b,c,d){c!==b.state.checked&&(c?(b.state.checked=!0,d.silent||this.$element.trigger("nodeChecked",a.extend(!0,{},b))):(b.state.checked=!1,d.silent||this.$element.trigger("nodeUnchecked",a.extend(!0,{},b))))},g.prototype.setDisabledState=function(b,c,d){c!==b.state.disabled&&(c?(b.state.disabled=!0,this.setExpandedState(b,!1,d),this.setSelectedState(b,!1,d),this.setCheckedState(b,!1,d),d.silent||this.$element.trigger("nodeDisabled",a.extend(!0,{},b))):(b.state.disabled=!1,d.silent||this.$element.trigger("nodeEnabled",a.extend(!0,{},b))))},g.prototype.render=function(){this.initialized||(this.$element.addClass(e),this.$wrapper=a(this.template.list),this.injectStyle(),this.initialized=!0),this.$element.empty().append(this.$wrapper.empty()),this.buildTree(this.tree,0)},g.prototype.buildTree=function(b,c){if(b){c+=1;var d=this;a.each(b,function(b,e){for(var f=a(d.template.item).addClass("node-"+d.elementId).addClass(e.state.checked?"node-checked":"").addClass(e.state.disabled?"node-disabled":"").addClass(e.state.selected?"node-selected":"").addClass(e.searchResult?"search-result":"").attr("data-nodeid",e.nodeId).attr("style",d.buildStyleOverride(e)),g=0;c-1>g;g++)f.append(d.template.indent);var h=[];if(e.nodes?(h.push("expand-icon"),h.push(e.state.expanded?d.options.collapseIcon:d.options.expandIcon)):h.push(d.options.emptyIcon),f.append(a(d.template.icon).addClass(h.join(" "))),d.options.showIcon){var h=["node-icon"];h.push(e.icon||d.options.nodeIcon),e.state.selected&&(h.pop(),h.push(e.selectedIcon||d.options.selectedIcon||e.icon||d.options.nodeIcon)),f.append(a(d.template.icon).addClass(h.join(" ")))}if(d.options.showCheckbox){var h=["check-icon"];h.push(e.state.checked?d.options.checkedIcon:d.options.uncheckedIcon),f.append(a(d.template.icon).addClass(h.join(" ")))}return f.append(d.options.enableLinks?a(d.template.link).attr("href",e.href).append(e.text):e.text),d.options.showTags&&e.tags&&a.each(e.tags,function(b,c){f.append(a(d.template.badge).append(c))}),d.$wrapper.append(f),e.nodes&&e.state.expanded&&!e.state.disabled?d.buildTree(e.nodes,c):void 0})}},g.prototype.buildStyleOverride=function(a){if(a.state.disabled)return"";var b=a.color,c=a.backColor;return this.options.highlightSelected&&a.state.selected&&(this.options.selectedColor&&(b=this.options.selectedColor),this.options.selectedBackColor&&(c=this.options.selectedBackColor)),this.options.highlightSearchResults&&a.searchResult&&!a.state.disabled&&(this.options.searchResultColor&&(b=this.options.searchResultColor),this.options.searchResultBackColor&&(c=this.options.searchResultBackColor)),"color:"+b+";background-color:"+c+";"},g.prototype.injectStyle=function(){this.options.injectStyle&&!c.getElementById(this.styleId)&&a('<style type="text/css" id="'+this.styleId+'"> '+this.buildStyle()+" </style>").appendTo("head")},g.prototype.buildStyle=function(){var a=".node-"+this.elementId+"{";return this.options.color&&(a+="color:"+this.options.color+";"),this.options.backColor&&(a+="background-color:"+this.options.backColor+";"),this.options.showBorder?this.options.borderColor&&(a+="border:1px solid "+this.options.borderColor+";"):a+="border:none;",a+="}",this.options.onhoverColor&&(a+=".node-"+this.elementId+":not(.node-disabled):hover{background-color:"+this.options.onhoverColor+";}"),this.css+a},g.prototype.template={list:'<ul class="list-group"></ul>',item:'<li class="list-group-item"></li>',indent:'<span class="indent"></span>',icon:'<span class="icon"></span>',link:'<a href="#" style="color:inherit;"></a>',badge:'<span class="badge"></span>'},g.prototype.css=".treeview .list-group-item{cursor:pointer}.treeview span.indent{margin-left:10px;margin-right:10px}.treeview span.icon{width:12px;margin-right:5px}.treeview .node-disabled{color:silver;cursor:not-allowed}",g.prototype.getNode=function(a){return this.nodes[a]},g.prototype.getParent=function(a){var b=this.identifyNode(a);return this.nodes[b.parentId]},g.prototype.getSiblings=function(a){var b=this.identifyNode(a),c=this.getParent(b),d=c?c.nodes:this.tree;return d.filter(function(a){return a.nodeId!==b.nodeId})},g.prototype.getSelected=function(){return this.findNodes("true","g","state.selected")},g.prototype.getUnselected=function(){return this.findNodes("false","g","state.selected")},g.prototype.getExpanded=function(){return this.findNodes("true","g","state.expanded")},g.prototype.getCollapsed=function(){return this.findNodes("false","g","state.expanded")},g.prototype.getChecked=function(){return this.findNodes("true","g","state.checked")},g.prototype.getUnchecked=function(){return this.findNodes("false","g","state.checked")},g.prototype.getDisabled=function(){return this.findNodes("true","g","state.disabled")},g.prototype.getEnabled=function(){return this.findNodes("false","g","state.disabled")},g.prototype.selectNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setSelectedState(a,!0,b)},this)),this.render()},g.prototype.unselectNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setSelectedState(a,!1,b)},this)),this.render()},g.prototype.toggleNodeSelected=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.toggleSelectedState(a,b)},this)),this.render()},g.prototype.collapseAll=function(b){var c=this.findNodes("true","g","state.expanded");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setExpandedState(a,!1,b)},this)),this.render()},g.prototype.collapseNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setExpandedState(a,!1,b)},this)),this.render()},g.prototype.expandAll=function(b){if(b=a.extend({},f.options,b),b&&b.levels)this.expandLevels(this.tree,b.levels,b);else{var c=this.findNodes("false","g","state.expanded");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setExpandedState(a,!0,b)},this))}this.render()},g.prototype.expandNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setExpandedState(a,!0,b),a.nodes&&b&&b.levels&&this.expandLevels(a.nodes,b.levels-1,b)},this)),this.render()},g.prototype.expandLevels=function(b,c,d){d=a.extend({},f.options,d),a.each(b,a.proxy(function(a,b){this.setExpandedState(b,c>0?!0:!1,d),b.nodes&&this.expandLevels(b.nodes,c-1,d)},this))},g.prototype.revealNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){for(var c=this.getParent(a);c;)this.setExpandedState(c,!0,b),c=this.getParent(c)},this)),this.render()},g.prototype.toggleNodeExpanded=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.toggleExpandedState(a,b)},this)),this.render()},g.prototype.checkAll=function(b){var c=this.findNodes("false","g","state.checked");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setCheckedState(a,!0,b)},this)),this.render()},g.prototype.checkNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setCheckedState(a,!0,b)},this)),this.render()},g.prototype.uncheckAll=function(b){var c=this.findNodes("true","g","state.checked");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setCheckedState(a,!1,b)},this)),this.render()},g.prototype.uncheckNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setCheckedState(a,!1,b)},this)),this.render()},g.prototype.toggleNodeChecked=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.toggleCheckedState(a,b)},this)),this.render()},g.prototype.disableAll=function(b){var c=this.findNodes("false","g","state.disabled");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setDisabledState(a,!0,b)},this)),this.render()},g.prototype.disableNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setDisabledState(a,!0,b)},this)),this.render()},g.prototype.enableAll=function(b){var c=this.findNodes("true","g","state.disabled");this.forEachIdentifier(c,b,a.proxy(function(a,b){this.setDisabledState(a,!1,b)},this)),this.render()},g.prototype.enableNode=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setDisabledState(a,!1,b)},this)),this.render()},g.prototype.toggleNodeDisabled=function(b,c){this.forEachIdentifier(b,c,a.proxy(function(a,b){this.setDisabledState(a,!a.state.disabled,b)},this)),this.render()},g.prototype.forEachIdentifier=function(b,c,d){c=a.extend({},f.options,c),b instanceof Array||(b=[b]),a.each(b,a.proxy(function(a,b){d(this.identifyNode(b),c)},this))},g.prototype.identifyNode=function(a){return"number"==typeof a?this.nodes[a]:a},g.prototype.search=function(b,c){c=a.extend({},f.searchOptions,c),this.clearSearch({render:!1});var d=[];if(b&&b.length>0){c.exactMatch&&(b="^"+b+"$");var e="g";c.ignoreCase&&(e+="i"),d=this.findNodes(b,e),a.each(d,function(a,b){b.searchResult=!0})}return c.revealResults?this.revealNode(d):this.render(),this.$element.trigger("searchComplete",a.extend(!0,{},d)),d},g.prototype.clearSearch=function(b){b=a.extend({},{render:!0},b);var c=a.each(this.findNodes("true","g","searchResult"),function(a,b){b.searchResult=!1});b.render&&this.render(),this.$element.trigger("searchCleared",a.extend(!0,{},c))},g.prototype.findNodes=function(b,c,d){c=c||"g",d=d||"text";var e=this;return a.grep(this.nodes,function(a){var f=e.getNodeValue(a,d);return"string"==typeof f?f.match(new RegExp(b,c)):void 0})},g.prototype.getNodeValue=function(a,b){var c=b.indexOf(".");if(c>0){var e=a[b.substring(0,c)],f=b.substring(c+1,b.length);return this.getNodeValue(e,f)}return a.hasOwnProperty(b)?a[b].toString():d};var h=function(a){b.console&&b.console.error(a)};a.fn[e]=function(b,c){var d;return this.each(function(){var f=a.data(this,e);"string"==typeof b?f?a.isFunction(f[b])&&"_"!==b.charAt(0)?(c instanceof Array||(c=[c]),d=f[b].apply(f,c)):h("No such method : "+b):h("Not initialized, can not call method : "+b):"boolean"==typeof b?d=f:a.data(this,e,new g(this,a.extend(!0,{},b)))}),d||this}}(jQuery,window,document);
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -13,59 +13,294 @@ ...@@ -13,59 +13,294 @@
<body> <body>
<div ng-include="'../common/nav.html'"></div> <div ng-include="'../common/nav.html'"></div>
<div class="container">
<div class="container-fluid"> <div class="container-fluid">
<div class="app" ng-controller="AppPageController"> <div class="app" ng-controller="AppConfigController as appConfig">
<ol class="breadcrumb">
<li>{{breadcrumb.project}}</li>
<li>{{breadcrumb.nav}}</li>
<li ng-show="breadcrumb.env">{{breadcrumb.env}}</li>
<!--<li>1.0</li>-->
<!--<li>default</li>-->
</ol>
<!-- Nav tabs -->
<ul class="nav nav-tabs">
<li role="presentation" ui-sref="config"><a href="#">配置</a></li>
<li role="presentation" ui-sref="info"><a href="#">应用信息</a></li>
<li role="presentation" ui-sref="setting"><a href="#">设置</a></li>
</ul>
<!-- Tab panes --> <!--配置信息-->
<div class="tab-content"> <div id="config-info">
<ui-view>
</ui-view> <!--具体配置信息-->
<div class="row config-info-container">
<!--tag导航-->
<div class="col-md-2">
<div id="treeview"></div>
</div>
<div class="col-md-10 config-item-container">
<div class="cluster-namespace J_cluster-namespace">
<div class="panel">
<header class="panel-heading">
<div class="row">
<div class="col-md-3">
<b>hermas</b>
<span class="label label-info">有修改,可发布
<span class="badge">4</span></span>
</div>
<div class="col-md-7">
<div class="btn-toolbar" role="toolbar" aria-label="...">
<div class="btn-group" role="group" aria-label="...">
<button type="button"
class="btn btn-primary btn-sm J_tableview_btn">发布
</button>
<button type="button"
class="btn btn-danger btn-sm J_tableview_btn">回滚
</button>
<button type="button"
class="btn btn-default btn-sm J_historyview_btn">
查看历史版本
</button>
</div>
<div class="btn-group" role="group" aria-label="...">
<div class="btn-group" role="group">
<button type="button"
class="btn btn-default btn-sm J_tableview_btn">授权
</button>
<button type="button"
class="btn btn-default btn-sm J_tableview_btn">克隆
</button>
</div>
</div>
<div class="btn-group" role="group" aria-label="...">
<button type="button"
class="btn btn-info btn-sm J_tableview_btn" disabled>
视图
</button>
<button type="button"
class="btn btn-default btn-sm J_tableview_btn">文本
</button>
<button type="button"
class="btn btn-default btn-sm J_tableview_btn">表格
</button>
<button type="button"
class="btn btn-default btn-sm J_historyview_btn">
更改日志
</button>
</div>
</div>
</div>
<div class="col-md-2 text-right">
&nbsp;&nbsp;
<a data-toggle="tooltip" data-placement="top" title="取消修改"
ng-if="appConfig.space.isEditing" ng-click="cancelChanges()">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</a>
&nbsp;
<a data-toggle="tooltip" data-placement="top" title="修改配置"
ng-if="!appConfig.space.isEditing" ng-click="appConfig.space.isEditing = true">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
</a>
<a data-toggle="tooltip" data-placement="top" title="保存修改"
ng-if="appConfig.space.isEditing"
ng-disabled="!appConfig.space.hasChanges() || appConfig.space.tableTracker.$invalid"
ng-click="appConfig.space.saveChanges()">
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
</a>
&nbsp;
<a data-toggle="tooltip" data-placement="top" title="添加配置"
ng-click="appConfig.space.add()" ng-if="appConfig.space.isEditing">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
</a>
</div>
</div>
</header>
<!--配置列表-->
<div class="J_tableview">
<table ng-table="appConfig.space.tableParams"
class="table table-bordered text-center table-hover editable-table"
ng-form="appConfig.space.tableForm" disable-filter="appConfig.space.isAdding"
tracked-table="appConfig.space.tableTracker">
<colgroup>
<col width="20%"/>
<col width="25%"/>
<col width="25%"/>
<col width="10%"/>
<col width="10%"/>
<col width="10%"/>
</colgroup>
<tr ng-repeat="row in $data" ng-form="rowForm" tracked-table-row="row"
ng-class="{danger:row.modified}">
<td title="'Key'" filter="{key: 'text'}" sortable="'key'"
ng-switch="appConfig.space.isEditing" ng-class="key.$dirty ? 'bg-warning' : ''"
ng-form="key" tracked-table-cell>
<span ng-switch-default class="editable-text">{{row.key}}</span>
<div class="controls"
ng-class="key.$invalid && key.$dirty ? 'has-error' : ''"
ng-switch-when="true">
<input type="text" name="key" ng-model="row.key"
class="editable-input form-control input-sm" required/>
</div>
</td>
<td title="'Value'" filter="{value: 'text'}" sortable="'value'"
ng-switch="appConfig.space.isEditing"
ng-class="value.$dirty ? 'bg-warning' : ''"
ng-form="value" tracked-table-cell>
<a data-toggle="tooltip" data-placement="top" title="查看旧值"
class="glyphicon glyphicon-eye-open" ng-show="row.modified"
aria-hidden="true"></a>
<span ng-switch-default class="editable-text">{{row.value}}</span>
<div class="controls"
ng-class="value.$invalid && value.$dirty ? 'has-error' : ''"
ng-switch-when="true">
<input type="text" name="value" ng-model="row.value"
class="editable-input form-control input-sm" required/>
</div>
</td>
<td title="'备注'" filter="{comment: 'text'}" sortable="'comment'"
ng-switch="appConfig.space.isEditing"
ng-class="comment.$dirty ? 'bg-warning' : ''"
ng-form="comment" tracked-table-cell>
<span ng-switch-default class="editable-text">{{row.comment}}</span>
<div class="controls"
ng-class="comment.$invalid && comment.$dirty ? 'has-error' : ''"
ng-switch-when="true">
<input type="text" name="comment" ng-model="row.comment"
class="editable-input form-control input-sm" required/>
</div>
</td>
<td title="'最后修改者'"
filter="{dataChangeLastModifiedBy: 'text'}"
sortable="'dataChangeLastModifiedBy'"
ng-switch="appConfig.space.isEditing"
ng-class="dataChangeLastModifiedBy.$dirty ? 'bg-warning' : ''"
ng-form="dataChangeLastModifiedBy" tracked-table-cell>
<span ng-switch-default class="editable-text">{{row.dataChangeLastModifiedBy}}</span>
<div class="controls"
ng-class="dataChangeLastModifiedBy.$invalid && dataChangeLastModifiedBy.$dirty ? 'has-error' : ''"
ng-switch-when="true">
<input type="text" name="dataChangeLastModifiedBy"
ng-model="row.dataChangeLastModifiedBy"
class="editable-input form-control input-sm" required/>
</div>
</td>
<td title="'最后修改时间'"
sortable="'value'"
ng-switch="appConfig.space.isEditing"
ng-class="dataChangeLastModifiedTime.$dirty ? 'bg-warning' : ''"
ng-form="dataChangeLastModifiedTime" tracked-table-cell>
<span ng-switch-default class="editable-text">{{row.dataChangeLastModifiedTime}}</span>
<div class="controls"
ng-class="dataChangeLastModifiedTime.$invalid && dataChangeLastModifiedTime.$dirty ? 'has-error' : ''"
ng-switch-when="true">
<input type="text" name="dataChangeLastModifiedTime"
ng-model="row.dataChangeLastModifiedTime"
class="editable-input form-control input-sm" required/>
</div>
</td>
<td ng-if="appConfig.space.isEditing" tracked-table-cell>
<button class="btn btn-danger btn-sm" ng-click="appConfig.space.del(row)"
ng-disabled="!appConfig.space.isEditing"><span
class="glyphicon glyphicon-trash"></span></button>
</td>
</tr>
</table>
</div>
<!--历史修改视图-->
<div class="J_historyview Hide historyview">
<div class="row">
<div class="col-md-11 col-md-offset-1 list" style="">
<div class="media">
<img src="../../img/history.png"/>
<div class="row">
<div class="col-md-10"><h5 class="media-heading">2016-02-23
12:23
王玉</h5>
<p>
修改comment
</p></div>
<div class="col-md-2 text-right">
<button class="btn btn-default" type="submit">查看修改内容
</button>
</div>
</div>
<hr>
</div>
<div class="media">
<img src="../../img/history.png"/>
<div class="row">
<div class="col-md-10"><h5 class="media-heading">2016-02-23
12:23
王玉</h5>
<p>
修改comment
</p></div>
<div class="col-md-2 text-right">
<button class="btn btn-default" type="submit">查看修改内容
</button>
</div>
</div> </div>
</div> </div>
</div>
</div>
</div>
</div>
<!--commit-->
<div class="media J_commitchange Hide">
<div class="media-left">
<a href="#">
<img class="media-object" src="../../img/icon.jpg" style="height: 75px; width: 75px;">
</a>
</div>
<div class="media-body">
<h4 class="media-heading">Commit changes</h4>
<textarea class="form-control" rows="4"></textarea>
<br>
<button class="btn btn-default" type="submit">submit</button>
<button class="btn btn-default" type="submit">cancel</button>
</div>
</div> </div>
</div> </div>
</div>
</div>
</div>
</div>
</div>
<div ng-include="'../common/footer.html'"></div> <div ng-include="'../common/footer.html'"></div>
<!-- jquery.js -->
<script src="../../vendor/jquery.js" type="text/javascript"></script>
<!--lodash.js-->
<script src="../../vendor/lodash.min.js" type="text/javascript"></script>
<!--angular--> <!--angular-->
<script src="../../vendor/angular/angular.min.js"></script> <script src="../../vendor/angular/angular.min.js"></script>
<script src="../../vendor/angular/angular-ui-router.min.js"></script> <script src="../../vendor/angular/angular-ui-router.min.js"></script>
<script src="../../vendor/angular/angular-resource.min.js"></script> <script src="../../vendor/angular/angular-resource.min.js"></script>
<script src="../../vendor/angular/angular-toastr-1.4.1.tpls.min.js"></script> <script src="../../vendor/angular/angular-toastr-1.4.1.tpls.min.js"></script>
<script src="../../vendor/angular/loading-bar.min.js"></script> <script src="../../vendor/angular/loading-bar.min.js"></script>
<script src="../../vendor/angular/ng-table.min.js"></script>
<!-- jquery.js -->
<script src="../../vendor/jquery.js" type="text/javascript"></script>
<!-- bootstrap.js --> <!-- bootstrap.js -->
<script src="../../vendor/bootstrap/js/bootstrap.min.js" type="text/javascript"></script> <script src="../../vendor/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
<script src="../../vendor/bootstrap/js/bootstrap-treeview.min.js" type="text/javascript"></script>
<!--biz script--> <!--biz script-->
<script type="application/javascript" src="../../scripts/app.js"></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/AppService.js"></script>
<script type="application/javascript" src="../../scripts/services/EnvService.js"></script> <script type="application/javascript" src="../../scripts/services/EnvService.js"></script>
<script type="application/javascript" src="../../scripts/services/ConfigService.js"></script> <script type="application/javascript" src="../../scripts/services/ConfigService.js"></script>
<script type="application/javascript" src="../../scripts/services/VersionService.js"></script> <script type="application/javascript" src="../../scripts/services/VersionService.js"></script>
<!--controller-->
<script type="application/javascript" src="../../scripts/controller/app/AppConfigController.js"></script> <script type="application/javascript" src="../../scripts/controller/app/AppConfigController.js"></script>
<script type="application/javascript" src="../../scripts/controller/app/AppInfoController.js"></script>
<script type="application/javascript" src="../../scripts/controller/app/AppPageController.js"></script>
</body> </body>
</html> </html>
...@@ -40,23 +40,23 @@ public class ConfigServiceTest { ...@@ -40,23 +40,23 @@ public class ConfigServiceTest {
private ServiceLocator serviceLocator; private ServiceLocator serviceLocator;
@Spy @Spy
private AdminServiceAPI.ClusterAPI clusterAPI; private AdminServiceAPI.ClusterAPI clusterAPI;
@Spy // @Spy
private AdminServiceAPI.ConfigAPI configAPI; // private AdminServiceAPI.ConfigAPI configAPI;
@Before @Before
public void setUp() throws ServiceException { public void setUp() throws ServiceException {
ReflectionTestUtils.setField(clusterAPI, "restTemplate", restTemplate); // ReflectionTestUtils.setField(clusterAPI, "restTemplate", restTemplate);
ReflectionTestUtils.setField(configAPI, "restTemplate", restTemplate); // ReflectionTestUtils.setField(configAPI, "restTemplate", restTemplate);
//
ReflectionTestUtils.setField(clusterAPI, "serviceLocator", serviceLocator); // ReflectionTestUtils.setField(clusterAPI, "serviceLocator", serviceLocator);
ReflectionTestUtils.setField(configAPI, "serviceLocator", serviceLocator); // ReflectionTestUtils.setField(configAPI, "serviceLocator", serviceLocator);
//
String defaultAdminService = "http://localhost:8090"; // String defaultAdminService = "http://localhost:8090";
ServiceDTO service = new ServiceDTO(); // ServiceDTO service = new ServiceDTO();
service.setHomepageUrl(defaultAdminService); // service.setHomepageUrl(defaultAdminService);
Mockito.doReturn(service).when(serviceLocator).getAdminService(Env.DEV); // Mockito.doReturn(service).when(serviceLocator).getAdminService(Env.DEV);
} }
// @Test // @Test
......
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