Merge pull request #2194 from KeRan213539/sameNamespaceClone#2171
Same namespace clone#2171
This commit is contained in:
commit
7e2e530729
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -44,6 +44,8 @@ public enum ResultCodeEnum implements IResultCode {
|
|||||||
|
|
||||||
DATA_EMPTY(100005, "导入的文件数据为空"),
|
DATA_EMPTY(100005, "导入的文件数据为空"),
|
||||||
|
|
||||||
|
NO_SELECTED_CONFIG(100006, "没有选择任何配制"),
|
||||||
|
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@ -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',
|
||||||
|
@ -311,6 +311,7 @@ const I18N_CONF = {
|
|||||||
delSelectedAlertTitle: '配置删除',
|
delSelectedAlertTitle: '配置删除',
|
||||||
delSelectedAlertContent: '请选择要删除的配置',
|
delSelectedAlertContent: '请选择要删除的配置',
|
||||||
delSuccessMsg: '删除成功',
|
delSuccessMsg: '删除成功',
|
||||||
|
cloneEditableTitle: '修改 Data Id 和 Group (可选操作)',
|
||||||
},
|
},
|
||||||
NewConfig: {
|
NewConfig: {
|
||||||
newListingMain: '新建配置',
|
newListingMain: '新建配置',
|
||||||
|
@ -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
Loading…
Reference in New Issue
Block a user