category详解[三]
# 2.2.4 渲染推荐方案并显示结果 前端逻辑详解
Ambari 完成推荐方案计算后,需要将结果在前端以图形化方式渲染展示出来。这个过程涉及推荐数据的处理、主机与组件的分配映射、以及最终页面上的动态渲染。以下结合核心 JS 逻辑,图示与数据结构,完整还原每一步实现。
# 2.2.4.1 recommendationsSuccessCallback 函数详解
每当用户点击“下一步”触发服务部署推荐时,浏览器会发起 AJAX
请求,后端返回的推荐数据将交由 loadRecommendationsSuccessCallback
函数进行处理。该函数的核心作用是把后端返回的
host_group、component 分布信息转化为便于前端页面渲染的数据结构。
/**
* Success-callback for recommendations request
* @param {object} data
* @method loadRecommendationsSuccessCallback
*/
loadRecommendationsSuccessCallback: function loadRecommendationsSuccessCallback(data) {
var recommendations = data.resources[0].recommendations;
this.set('recommendations', recommendations);
if (this.get('content.controllerName')) {
this.set('content.recommendations', recommendations);
}
var recommendedHostsForComponent = {};
var hostsForHostGroup = {};
recommendations.blueprint_cluster_binding.host_groups.forEach(function (hostGroup) {
hostsForHostGroup[hostGroup.name] = hostGroup.hosts.mapProperty('fqdn');
});
recommendations.blueprint.host_groups.forEach(function (hostGroup) {
var components = hostGroup.components.mapProperty('name');
components.forEach(function (componentName) {
var hostList = recommendedHostsForComponent[componentName] || [];
var hostNames = hostsForHostGroup[hostGroup.name] || [];
hostList.pushObjects(hostNames);
recommendedHostsForComponent[componentName] = hostList;
});
});
this.set('recommendedHostsForComponents', recommendedHostsForComponent);
if (this.get('content.controllerName')) {
this.set('content.recommendedHostsForComponents', recommendedHostsForComponent);
}
}
,
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
hostsForHostGroup
:用于将推荐的 host_group 与实际主机进行映射,方便后续查找和展示。
示例结构如下:
{
"host-group-1": [
"centos1"
],
"host-group-2": [
"centos2"
],
"host-group-3": [
"centos3"
]
}
2
3
4
5
6
7
8
9
10
11
随后,通过循环,将每个组件名称与具体分配的主机关联起来:
recommendations.blueprint.host_groups.forEach(function (hostGroup) {
var components = hostGroup.components.mapProperty('name');
components.forEach(function (componentName) {
var hostList = recommendedHostsForComponent[componentName] || [];
var hostNames = hostsForHostGroup[hostGroup.name] || [];
hostList.pushObjects(hostNames);
recommendedHostsForComponent[componentName] = hostList;
});
});
2
3
4
5
6
7
8
9
最终,得到了组件到主机的完整映射关系 recommendedHostsForComponent
,例如:
{
"REDIS_CLIENT": [
"centos1",
"centos2",
"centos3"
],
"REDIS_MASTER": [
"centos1",
"centos2",
"centos3"
],
"REDIS_SLAVE": [
"centos1",
"centos2",
"centos3"
],
"ZOOKEEPER_CLIENT": [
"centos1",
"centos2",
"centos3"
],
"ZOOKEEPER_SERVER": [
"centos1",
"centos2",
"centos3"
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 2.2.4.2 createComponentInstallationObjects 函数详解
在页面渲染阶段,loadStep
会调用 createComponentInstallationObjects
,进一步生成前端展示所需的组件-主机映射对象。
self.loadStepCallback(self.createComponentInstallationObjects(), self);
核心实现如下:
/**
* 为 UI 分配对话框创建用于显示组件-主机组合框的对象
* content.recommendations 期望已填充推荐的 API 调用结果
* @return {Object[]}
*/
createComponentInstallationObjects: function createComponentInstallationObjects() {
var stackMasterComponentsMap = {}, // 用于存储主组件映射
masterHosts = this.get('content.masterComponentHosts') || this.get('masterComponentHosts'), // 获取主组件及其主机信息
servicesToAdd = (this.get('content.services') || []).filterProperty('isSelected').filterProperty('isInstalled', false).mapProperty('serviceName'), // 筛选出尚未安装的已选服务
recommendations = this.get('recommendations'), // 获取推荐的主机和组件映射
resultComponents = [], // 最终生成的组件安装对象数组
multipleComponentHasBeenAdded = {}, // 用于跟踪哪些允许多次出现的组件已经被添加
hostGroupsMap = {}; // 主机组名称与主机组对象的映射
// 遍历所有服务组件,挑选出需要显示的主组件并存储到 stackMasterComponentsMap 中
App.StackServiceComponent.find().forEach(function (component) {
var isMasterCreateOnConfig = this.get('mastersToCreate').contains(component.get('componentName')); // 判断是否需要根据配置创建主组件
// 如果是在安装向导中,并且组件需要显示在主组件分配页面或通过配置需要创建,则将其加入映射
if (this.get('isInstallerWizard') && (component.get('isShownOnInstallerAssignMasterPage') || isMasterCreateOnConfig)) {
stackMasterComponentsMap[component.get('componentName')] = component;
// 如果是在添加服务时需要显示或通过配置需要创建的主组件,也将其加入映射
} else if (component.get('isShownOnAddServiceAssignMasterPage') || this.get('mastersToShow').contains(component.get('componentName')) || isMasterCreateOnConfig) {
stackMasterComponentsMap[component.get('componentName')] = component;
}
}, this);
// 遍历推荐的主机组,构建主机组与主机的映射关系
recommendations.blueprint_cluster_binding.host_groups.forEach(function (group) {
hostGroupsMap[group.name] = group;
});
// 遍历推荐的主机组中的组件,并决定哪些组件需要显示
recommendations.blueprint.host_groups.forEach(function (host_group) {
var hosts = hostGroupsMap[host_group.name] ? hostGroupsMap[host_group.name].hosts : []; // 获取当前主机组中的主机
// 遍历主机组中的每个主机
hosts.forEach(function (host) {
host_group.components.forEach(function (component) {
var willBeDisplayed = true; // 变量表示是否需要显示该组件
// 获取当前组件对应的主组件
var stackMasterComponent = stackMasterComponentsMap[component.name];
// 如果该组件是主组件,则进行进一步判断
if (stackMasterComponent) {
var isMasterCreateOnConfig = this.get('mastersToCreate').contains(component.name); // 判断是否需要根据配置创建该主组件
// 如果服务已经安装并且不打算添加新服务,则仅渲染那些已经安装的主组件
if (!servicesToAdd.contains(stackMasterComponent.get('serviceName')) && !isMasterCreateOnConfig) {
willBeDisplayed = masterHosts.someProperty('component', component.name); // 检查当前组件是否已安装
}
// 如果组件需要显示,则生成组件安装对象
if (willBeDisplayed) {
var savedComponents = masterHosts.filterProperty('component', component.name); // 获取已保存的组件信息
// 处理可以多次出现的组件,如果组件已经存在多个实例,确保每个实例只被添加一次
if (this.get('multipleComponents').contains(component.name) && savedComponents.length > 0) {
if (!multipleComponentHasBeenAdded[component.name]) {
multipleComponentHasBeenAdded[component.name] = true;
savedComponents.forEach(function (saved) {
resultComponents.push(this.createComponentInstallationObject(stackMasterComponent, host.fqdn.toLowerCase(), saved)); // 创建组件安装对象并加入结果集
}, this);
}
} else {
var savedComponent = masterHosts.findProperty('component', component.name); // 获取已保存的组件信息
resultComponents.push(this.createComponentInstallationObject(stackMasterComponent, host.fqdn.toLowerCase(), savedComponent)); // 创建组件安装对象
}
}
}
}, this);
}, this);
}, this);
return resultComponents; // 返回生成的组件安装对象数组
}
,
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
stackMasterComponentsMap
:记录所有 MASTER 类型组件。这里面主要包括
REDIS_MASTER
和ZOOKEEPER_SERVER
。
# 2.2.4.3 处理主机与 MASTER 组件的配对
结合 stackMasterComponentsMap
,最终生成的 resultComponents
只会包含属于 MASTER 的组件和分配主机的配对关系。
例如,结果结构如下:
[
{
"component_name": "REDIS_MASTER",
"display_name": "Redis Master",
"serviceId": "REDIS",
"isServiceCoHost": false,
"selectedHost": "centos1",
"isInstalled": true
},
{
"component_name": "REDIS_MASTER",
"display_name": "Redis Master",
"serviceId": "REDIS",
"isServiceCoHost": false,
"selectedHost": "centos2",
"isInstalled": true
},
{
"component_name": "REDIS_MASTER",
"display_name": "Redis Master",
"serviceId": "REDIS",
"isServiceCoHost": false,
"selectedHost": "centos3",
"isInstalled": true
},
{
"component_name": "ZOOKEEPER_SERVER",
"display_name": "ZooKeeper Server",
"serviceId": "ZOOKEEPER",
"isServiceCoHost": false,
"selectedHost": "centos3",
"isInstalled": false
},
{
"component_name": "ZOOKEEPER_SERVER",
"display_name": "ZooKeeper Server",
"serviceId": "ZOOKEEPER",
"isServiceCoHost": false,
"selectedHost": "centos2",
"isInstalled": false
},
{
"component_name": "ZOOKEEPER_SERVER",
"display_name": "ZooKeeper Server",
"serviceId": "ZOOKEEPER",
"isServiceCoHost": false,
"selectedHost": "centos1",
"isInstalled": false
}
]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 2.2.4.4 页面渲染结果
最终,所有分配到 MASTER
类别的组件(如 Redis Master、ZooKeeper Server)都会以可视化形式展现在主机分配页面,每个组件都标明分配在哪台服务器。
笔记
本节详细解析了从后端推荐方案到前端 JS 处理再到 UI 渲染的完整链路,全流程可追溯,且所有关键数据结构、图片与代码均已完整呈现,便于快速定位与扩展页面功能。