Same namespace clone#2171
This commit is contained in:
Peter Zhu 2020-01-10 15:24:35 +08:00 committed by GitHub
commit 7e2e530729
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 227 additions and 41 deletions

View File

@ -17,6 +17,7 @@ package com.alibaba.nacos.config.server.controller;
import com.alibaba.nacos.config.server.constant.Constants;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.config.server.controller.parameters.SameNamespaceCloneConfigBean;
import com.alibaba.nacos.config.server.model.*;
import com.alibaba.nacos.config.server.result.ResultBuilder;
import com.alibaba.nacos.config.server.result.code.ResultCodeEnum;
@ -46,6 +47,7 @@ import java.io.IOException;
import java.net.URLDecoder;
import java.sql.Timestamp;
import java.util.*;
import java.util.stream.Collectors;
import static com.alibaba.nacos.core.utils.SystemUtils.LOCAL_IP;
@ -66,7 +68,7 @@ public class ConfigController {
private static final String EXPORT_CONFIG_FILE_NAME_EXT = ".zip";
private static final String EXPORT_CONFIG_FILE_NAME_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
private static final String EXPORT_CONFIG_FILE_NAME_DATE_FORMAT = "yyyyMMddHHmmss";
private final ConfigServletInner inner;
@ -525,14 +527,20 @@ public class ConfigController {
return ResultBuilder.buildSuccessResult("导入成功", saveResult);
}
@GetMapping(params = "clone=true")
@PostMapping(params = "clone=true")
public RestResult<Map<String, Object>> cloneConfig(HttpServletRequest request,
@RequestParam(value = "src_user", required = false) String srcUser,
@RequestParam(value = "tenant", required = true) String namespace,
@RequestParam(value = "ids", required = true) List<Long> ids,
@RequestBody(required = true)
List<SameNamespaceCloneConfigBean> configBeansList,
@RequestParam(value = "policy", defaultValue = "ABORT")
SameConfigPolicy policy) throws NacosException {
Map<String, Object> failedData = new HashMap<>(4);
if(CollectionUtils.isEmpty(configBeansList)){
failedData.put("succCount", 0);
return ResultBuilder.buildResult(ResultCodeEnum.NO_SELECTED_CONFIG, failedData);
}
configBeansList.removeAll(Collections.singleton(null));
if (NAMESPACE_PUBLIC_KEY.equalsIgnoreCase(namespace)) {
namespace = "";
@ -541,8 +549,14 @@ public class ConfigController {
return ResultBuilder.buildResult(ResultCodeEnum.NAMESPACE_NOT_EXIST, failedData);
}
ids.removeAll(Collections.singleton(null));
List<ConfigAllInfo> queryedDataList = persistService.findAllConfigInfo4Export(null, null, null, null, ids);
List<Long> idList = new ArrayList<>(configBeansList.size());
Map<Long, SameNamespaceCloneConfigBean> configBeansMap = configBeansList.stream()
.collect(Collectors.toMap(SameNamespaceCloneConfigBean::getCfgId, cfg -> {
idList.add(cfg.getCfgId());
return cfg;
},(k1, k2) -> k1));
List<ConfigAllInfo> queryedDataList = persistService.findAllConfigInfo4Export(null, null, null, null, idList);
if (queryedDataList == null || queryedDataList.isEmpty()) {
failedData.put("succCount", 0);
@ -552,11 +566,12 @@ public class ConfigController {
List<ConfigAllInfo> configInfoList4Clone = new ArrayList<>(queryedDataList.size());
for (ConfigAllInfo ci : queryedDataList) {
SameNamespaceCloneConfigBean prarmBean = configBeansMap.get(ci.getId());
ConfigAllInfo ci4save = new ConfigAllInfo();
ci4save.setTenant(namespace);
ci4save.setType(ci.getType());
ci4save.setGroup(ci.getGroup());
ci4save.setDataId(ci.getDataId());
ci4save.setGroup((prarmBean != null && StringUtils.isNotBlank(prarmBean.getGroup())) ? prarmBean.getGroup() : ci.getGroup());
ci4save.setDataId((prarmBean != null && StringUtils.isNotBlank(prarmBean.getDataId())) ? prarmBean.getDataId() : ci.getDataId());
ci4save.setContent(ci.getContent());
if (StringUtils.isNotBlank(ci.getAppName())) {
ci4save.setAppName(ci.getAppName());
@ -580,7 +595,7 @@ public class ConfigController {
configInfo.getTenant(), requestIpApp, time.getTime(),
LOCAL_IP, ConfigTraceService.PERSISTENCE_EVENT_PUB, configInfo.getContent());
}
return ResultBuilder.buildSuccessResult("导入成功", saveResult);
return ResultBuilder.buildSuccessResult("克隆成功", saveResult);
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.nacos.config.server.controller.parameters;
/**
* @author klw(213539 @ qq.com)
* @ClassName: SameNamespaceCloneConfigBean
* @Description: 同namespace克隆接口的配制bean
* @date 2019/12/13 16:10
*/
public class SameNamespaceCloneConfigBean {
private Long cfgId;
private String dataId;
private String group;
public Long getCfgId() {
return cfgId;
}
public void setCfgId(Long cfgId) {
this.cfgId = cfgId;
}
public String getDataId() {
return dataId;
}
public void setDataId(String dataId) {
this.dataId = dataId;
}
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
}

View File

@ -44,6 +44,8 @@ public enum ResultCodeEnum implements IResultCode {
DATA_EMPTY(100005, "导入的文件数据为空"),
NO_SELECTED_CONFIG(100006, "没有选择任何配制"),
;

View File

@ -313,6 +313,7 @@ const I18N_CONF = {
delSelectedAlertTitle: 'Delete config',
delSelectedAlertContent: 'please select the configuration to delete',
delSuccessMsg: 'delete successful',
cloneEditableTitle: 'Modify Data Id and Group (optional)',
},
NewConfig: {
newListingMain: 'Create Configuration',

View File

@ -311,6 +311,7 @@ const I18N_CONF = {
delSelectedAlertTitle: '配置删除',
delSelectedAlertContent: '请选择要删除的配置',
delSuccessMsg: '删除成功',
cloneEditableTitle: '修改 Data Id Group (可选操作)',
},
NewConfig: {
newListingMain: '新建配置',

View File

@ -39,6 +39,7 @@ import ShowCodeing from 'components/ShowCodeing';
import DeleteDialog from 'components/DeleteDialog';
import DashboardCard from './DashboardCard';
import { getParams, setParams, request, aliwareIntl } from '@/globalLib';
import axios from 'axios';
import './index.scss';
import { LANGUAGE_KEY } from '../../../constants';
@ -689,10 +690,9 @@ class ConfigurationManagement extends React.Component {
}
exportData() {
let url =
`v1/cs/configs?export=true&group=${this.group}&tenant=${getParams('namespace')}&appName=${
this.appName
}&ids=&dataId=` + this.dataId;
let url = `v1/cs/configs?export=true&group=${this.group}&tenant=${getParams(
'namespace'
)}&appName=${this.appName}&ids=&dataId=${this.dataId}`;
window.location.href = url;
}
@ -783,19 +783,54 @@ class ConfigurationManagement extends React.Component {
}
let namespaces = data.data;
let namespaceSelectData = [];
namespaces.forEach(item => {
if (self.state.nownamespace_id !== item.namespace) {
let dataItem = {};
if (item.namespaceShowName === 'public') {
dataItem.label = 'public | public';
dataItem.value = 'public';
} else {
dataItem.label = `${item.namespaceShowName} | ${item.namespace}`;
dataItem.value = item.namespace;
}
namespaceSelectData.push(dataItem);
let namespaceSelecItemRender = item => {
if (item.isCurrent) {
return <span style={{ color: '#00AA00', 'font-weight': 'bold' }}>{item.label}</span>;
} else {
return <span>{item.label}</span>;
}
};
namespaces.forEach(item => {
let dataItem = {};
dataItem.isCurrent = false;
if (self.state.nownamespace_id === item.namespace) {
dataItem.isCurrent = true;
}
if (item.namespaceShowName === 'public') {
dataItem.label = 'public | public';
dataItem.value = 'public';
} else {
dataItem.label = `${item.namespaceShowName} | ${item.namespace}`;
dataItem.value = item.namespace;
}
namespaceSelectData.push(dataItem);
});
let editableTableData = [];
let configsTableSelectedDeepCopyed = new Map();
configsTableSelected.forEach((value, key, map) => {
let dataItem = {};
dataItem.id = key;
dataItem.dataId = value.dataId;
dataItem.group = value.group;
editableTableData.push(dataItem);
configsTableSelectedDeepCopyed.set(key, JSON.parse(JSON.stringify(value)));
});
let editableTableOnBlur = (record, type, e) => {
if (type === 1) {
configsTableSelectedDeepCopyed.get(record.id).dataId = e.target.value;
} else {
configsTableSelectedDeepCopyed.get(record.id).group = e.target.value;
}
};
let renderEditableTableCellDataId = (value, index, record) => (
<Input defaultValue={value} onBlur={editableTableOnBlur.bind(this, record, 1)} />
);
let renderEditableTableCellGroup = (value, index, record) => (
<Input defaultValue={value} onBlur={editableTableOnBlur.bind(this, record, 2)} />
);
const cloneConfirm = Dialog.confirm({
title: locale.cloningConfiguration,
footer: false,
@ -822,6 +857,7 @@ class ConfigurationManagement extends React.Component {
showSearch
hasClear={false}
mode="single"
itemRender={namespaceSelecItemRender}
dataSource={namespaceSelectData}
onChange={(value, actionType, item) => {
if (value) {
@ -866,7 +902,7 @@ class ConfigurationManagement extends React.Component {
}}
/>
</div>
<div>
<div style={{ marginBottom: 10 }}>
<Button
type={'primary'}
style={{ marginRight: 10 }}
@ -878,13 +914,21 @@ class ConfigurationManagement extends React.Component {
document.getElementById('cloneTargetSpaceSelectErr').style.display = 'none';
}
let idsStr = '';
configsTableSelected.forEach((value, key, map) => {
idsStr = `${idsStr + key},`;
let clonePostData = [];
configsTableSelectedDeepCopyed.forEach((value, key, map) => {
let postDataItem = {};
postDataItem.cfgId = key;
postDataItem.dataId = value.dataId;
postDataItem.group = value.group;
clonePostData.push(postDataItem);
});
let cloneTargetSpace = self.field.getValue('cloneTargetSpace');
let sameConfigPolicy = self.field.getValue('sameConfigPolicy');
request({
url: `v1/cs/configs?clone=true&tenant=${cloneTargetSpace}&policy=${sameConfigPolicy}&ids=${idsStr}`,
url: `v1/cs/configs?clone=true&tenant=${cloneTargetSpace}&policy=${sameConfigPolicy}&namespaceId=`,
method: 'post',
data: JSON.stringify(clonePostData),
contentType: 'application/json',
beforeSend() {
self.openLoading();
},
@ -908,6 +952,25 @@ class ConfigurationManagement extends React.Component {
{locale.startCloning}
</Button>
</div>
<div style={{ marginBottom: 10 }}>
<span style={{ color: '#00AA00', 'font-weight': 'bold' }}>
{locale.cloneEditableTitle}
</span>
</div>
<div>
<Table dataSource={editableTableData}>
<Table.Column
title="Data Id"
dataIndex="dataId"
cell={renderEditableTableCellDataId}
/>
<Table.Column
title="Group"
dataIndex="group"
cell={renderEditableTableCellGroup}
/>
</Table>
</div>
</div>
),
});
@ -1089,7 +1152,9 @@ class ConfigurationManagement extends React.Component {
rowSelection.selectedRowKeys = ids;
this.setState({ rowSelection });
configsTableSelected.clear();
records.forEach(item => configsTableSelected.set(item.id, item));
records.forEach((record, i) => {
configsTableSelected.set(record.id, record);
});
}
render() {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long