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.config.server.constant.Constants;
import com.alibaba.nacos.api.exception.NacosException; 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.model.*;
import com.alibaba.nacos.config.server.result.ResultBuilder; import com.alibaba.nacos.config.server.result.ResultBuilder;
import com.alibaba.nacos.config.server.result.code.ResultCodeEnum; import com.alibaba.nacos.config.server.result.code.ResultCodeEnum;
@ -46,6 +47,7 @@ import java.io.IOException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import static com.alibaba.nacos.core.utils.SystemUtils.LOCAL_IP; 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_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; private final ConfigServletInner inner;
@ -525,14 +527,20 @@ public class ConfigController {
return ResultBuilder.buildSuccessResult("导入成功", saveResult); return ResultBuilder.buildSuccessResult("导入成功", saveResult);
} }
@GetMapping(params = "clone=true") @PostMapping(params = "clone=true")
public RestResult<Map<String, Object>> cloneConfig(HttpServletRequest request, public RestResult<Map<String, Object>> cloneConfig(HttpServletRequest request,
@RequestParam(value = "src_user", required = false) String srcUser, @RequestParam(value = "src_user", required = false) String srcUser,
@RequestParam(value = "tenant", required = true) String namespace, @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") @RequestParam(value = "policy", defaultValue = "ABORT")
SameConfigPolicy policy) throws NacosException { SameConfigPolicy policy) throws NacosException {
Map<String, Object> failedData = new HashMap<>(4); 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)) { if (NAMESPACE_PUBLIC_KEY.equalsIgnoreCase(namespace)) {
namespace = ""; namespace = "";
@ -541,8 +549,14 @@ public class ConfigController {
return ResultBuilder.buildResult(ResultCodeEnum.NAMESPACE_NOT_EXIST, failedData); return ResultBuilder.buildResult(ResultCodeEnum.NAMESPACE_NOT_EXIST, failedData);
} }
ids.removeAll(Collections.singleton(null)); List<Long> idList = new ArrayList<>(configBeansList.size());
List<ConfigAllInfo> queryedDataList = persistService.findAllConfigInfo4Export(null, null, null, null, ids); 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()) { if (queryedDataList == null || queryedDataList.isEmpty()) {
failedData.put("succCount", 0); failedData.put("succCount", 0);
@ -552,11 +566,12 @@ public class ConfigController {
List<ConfigAllInfo> configInfoList4Clone = new ArrayList<>(queryedDataList.size()); List<ConfigAllInfo> configInfoList4Clone = new ArrayList<>(queryedDataList.size());
for (ConfigAllInfo ci : queryedDataList) { for (ConfigAllInfo ci : queryedDataList) {
SameNamespaceCloneConfigBean prarmBean = configBeansMap.get(ci.getId());
ConfigAllInfo ci4save = new ConfigAllInfo(); ConfigAllInfo ci4save = new ConfigAllInfo();
ci4save.setTenant(namespace); ci4save.setTenant(namespace);
ci4save.setType(ci.getType()); ci4save.setType(ci.getType());
ci4save.setGroup(ci.getGroup()); ci4save.setGroup((prarmBean != null && StringUtils.isNotBlank(prarmBean.getGroup())) ? prarmBean.getGroup() : ci.getGroup());
ci4save.setDataId(ci.getDataId()); ci4save.setDataId((prarmBean != null && StringUtils.isNotBlank(prarmBean.getDataId())) ? prarmBean.getDataId() : ci.getDataId());
ci4save.setContent(ci.getContent()); ci4save.setContent(ci.getContent());
if (StringUtils.isNotBlank(ci.getAppName())) { if (StringUtils.isNotBlank(ci.getAppName())) {
ci4save.setAppName(ci.getAppName()); ci4save.setAppName(ci.getAppName());
@ -580,7 +595,7 @@ public class ConfigController {
configInfo.getTenant(), requestIpApp, time.getTime(), configInfo.getTenant(), requestIpApp, time.getTime(),
LOCAL_IP, ConfigTraceService.PERSISTENCE_EVENT_PUB, configInfo.getContent()); 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, "导入的文件数据为空"), DATA_EMPTY(100005, "导入的文件数据为空"),
NO_SELECTED_CONFIG(100006, "没有选择任何配制"),
; ;

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long