Merge pull request #894 from jameslcj/feature/issue479

Standardize the editor of metadata #479
This commit is contained in:
Fury Zhu 2019-03-13 18:37:40 +08:00 committed by GitHub
commit 367a8b9ee9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 306 additions and 80 deletions

View File

@ -31,7 +31,7 @@ module.exports = {
path: path.resolve(__dirname, '../dist'),
},
resolve: {
extensions: ['.js', '.jsx', '.json'],
extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
alias: {
'@': resolve('src'),
utils: resolve('src/utils'),
@ -51,7 +51,7 @@ module.exports = {
include: [resolve('src')],
},
{
test: /\.(js|jsx)$/,
test: /\.(js|jsx|ts|tsx)$/,
include: [resolve('src')],
use: ['babel-loader'],
},

View File

@ -0,0 +1,111 @@
/*
* 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.
*/
import * as React from 'react';
import { MONACO_OPTIONS } from './constant';
import './index.scss';
interface PropsType {
dispatch: (obj: Object) => void;
options: Object;
value: string;
language: string;
width: number;
height: number;
onChange: (value: string) => void;
}
interface StateType {}
class MonacoEditor extends React.Component<PropsType, StateType> {
static displayName = 'MonacoEditor';
private nodeRef: any = React.createRef();
public monacoEditor: any = null;
public state: StateType;
public props: PropsType;
constructor(props: PropsType) {
super(props);
}
componentWillReceiveProps(nextProps): void {
if (!this.monacoEditor) {
return;
}
const { value = '', language = 'js', width, height, options = {} } = this.props;
if (value !== nextProps.value) {
this.monacoEditor.setValue(nextProps.value || '');
}
if (language !== nextProps.language) {
this.monacoEditor.editor.setModelLanguage(this.monacoEditor.getModel(), nextProps.language);
}
if (this.monacoEditor && (width !== nextProps.width || height !== nextProps.height)) {
this.monacoEditor.layout();
}
if (this.monacoEditor && nextProps.options && options !== nextProps.options) {
this.monacoEditor.updateOptions({ ...MONACO_OPTIONS, ...nextProps.options });
}
}
componentDidMount(): void {
if (!window.monaco) {
window.importEditor &&
window.importEditor(() => {
this.initMoacoEditor();
});
} else {
this.initMoacoEditor();
}
}
componentWillUnmount(): void {
this.monacoEditor && this.monacoEditor.dispose();
this.nodeRef = null;
}
initMoacoEditor(): void {
const { options = {}, language = 'js', value = '' } = this.props;
try {
this.monacoEditor = window.monaco.editor.create(this.nodeRef && this.nodeRef.current, {
...MONACO_OPTIONS,
...options,
language,
value,
});
this.editorDidMount(this.monacoEditor);
} catch (error) {}
}
editorDidMount(editor: any) {
const { onChange } = this.props;
editor.onDidChangeModelContent(event => {
const value = editor.getValue();
typeof onChange === 'function' && onChange(value);
});
}
render(): HTMLElement {
const { width = '100%', height = 0 } = this.props;
const style = {
width,
height,
};
return <div ref={this.nodeRef} style={style} />;
}
}
export default MonacoEditor;

View File

@ -0,0 +1,27 @@
/*
* 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.
*/
export const MONACO_OPTIONS: Object = {
codeLens: true,
selectOnLineNumbers: true,
roundedSelection: false,
readOnly: false,
lineNumbersMinChars: true,
theme: 'vs-dark',
wordWrapColumn: 120,
folding: true,
showFoldingControls: 'always',
wordWrap: 'wordWrapColumn',
cursorStyle: 'line',
automaticLayout: true,
};

View File

@ -0,0 +1,12 @@
/*
* 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.
*/

View File

@ -0,0 +1,16 @@
/*
* 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.
*/
import MonacoEditor from './MonacoEditor';
export default MonacoEditor;

View File

@ -15,7 +15,9 @@ import React from 'react';
import PropTypes from 'prop-types';
import { request } from '../../../globalLib';
import { Dialog, Form, Input, Switch, Select, Message, ConfigProvider } from '@alifd/next';
import { DIALOG_FORM_LAYOUT } from './constant';
import { DIALOG_FORM_LAYOUT, METADATA_SEPARATOR, METADATA_ENTER } from './constant';
import MonacoEditor from 'components/MonacoEditor';
import { replaceEnter, processMetaData } from 'utils/nacosutil';
@ConfigProvider.config
class EditClusterDialog extends React.Component {
@ -40,9 +42,7 @@ class EditClusterDialog extends React.Component {
show(_editCluster) {
let editCluster = _editCluster;
const { metadata = {} } = editCluster;
editCluster.metadataText = Object.keys(metadata)
.map(k => `${k}=${metadata[k]}`)
.join(',');
editCluster.metadataText = processMetaData(METADATA_ENTER)(metadata);
this.setState({
editCluster,
editClusterDialogVisible: true,
@ -69,7 +69,7 @@ class EditClusterDialog extends React.Component {
data: {
serviceName,
clusterName: name,
metadata: metadataText,
metadata: replaceEnter(METADATA_SEPARATOR)(metadataText),
checkPort: defaultCheckPort,
useInstancePort4Check: useIPPort4Check,
healthChecker: JSON.stringify(healthChecker),
@ -113,6 +113,7 @@ class EditClusterDialog extends React.Component {
return (
<Dialog
className="cluster-edit-dialog"
style={{ width: 600 }}
title={updateCluster}
visible={editClusterDialogVisible}
onOk={() => this.onConfirm()}
@ -172,8 +173,10 @@ class EditClusterDialog extends React.Component {
</div>
) : null}
<Form.Item label={`${locale.metadata}:`}>
<Input
className="in-text"
<MonacoEditor
language={'properties'}
width={'100%'}
height={200}
value={metadataText}
onChange={metadataText => this.onChangeCluster({ metadataText })}
/>

View File

@ -15,7 +15,9 @@ import React from 'react';
import PropTypes from 'prop-types';
import { request } from '../../../globalLib';
import { Dialog, Form, Input, Switch, Message, ConfigProvider } from '@alifd/next';
import { DIALOG_FORM_LAYOUT } from './constant';
import { DIALOG_FORM_LAYOUT, METADATA_ENTER, METADATA_SEPARATOR } from './constant';
import MonacoEditor from 'components/MonacoEditor';
import { replaceEnter, processMetaData } from 'utils/nacosutil';
@ConfigProvider.config
class EditInstanceDialog extends React.Component {
@ -43,9 +45,7 @@ class EditInstanceDialog extends React.Component {
let editInstance = _editInstance;
const { metadata = {} } = editInstance;
if (Object.keys(metadata).length) {
editInstance.metadataText = Object.keys(metadata)
.map(k => `${k}=${metadata[k]}`)
.join(',');
editInstance.metadataText = processMetaData(METADATA_ENTER)(metadata);
}
this.setState({ editInstance, editInstanceDialogVisible: true });
}
@ -60,7 +60,15 @@ class EditInstanceDialog extends React.Component {
request({
method: 'PUT',
url: 'v1/ns/instance',
data: { serviceName, clusterName, ip, port, weight, enable: enabled, metadata: metadataText },
data: {
serviceName,
clusterName,
ip,
port,
weight,
enable: enabled,
metadata: replaceEnter(METADATA_SEPARATOR)(metadataText),
},
dataType: 'text',
beforeSend: () => openLoading(),
success: res => {
@ -89,6 +97,7 @@ class EditInstanceDialog extends React.Component {
<Dialog
className="instance-edit-dialog"
title={locale.updateInstance}
style={{ width: 600 }}
visible={editInstanceDialogVisible}
onOk={() => this.onConfirm()}
onCancel={() => this.hide()}
@ -115,8 +124,10 @@ class EditInstanceDialog extends React.Component {
/>
</Form.Item>
<Form.Item label={`${locale.metadata}:`}>
<Input
className="in-text"
<MonacoEditor
language={'properties'}
width={'100%'}
height={200}
value={editInstance.metadataText}
onChange={metadataText => this.onChangeCluster({ metadataText })}
/>

View File

@ -15,7 +15,9 @@ import React from 'react';
import PropTypes from 'prop-types';
import { request } from '../../../globalLib';
import { Dialog, Form, Input, Select, Message, ConfigProvider } from '@alifd/next';
import { DIALOG_FORM_LAYOUT } from './constant';
import { DIALOG_FORM_LAYOUT, METADATA_SEPARATOR, METADATA_ENTER } from './constant';
import MonacoEditor from 'components/MonacoEditor';
import { replaceEnter, processMetaData } from 'utils/nacosutil';
@ConfigProvider.config
class EditServiceDialog extends React.Component {
@ -42,9 +44,7 @@ class EditServiceDialog extends React.Component {
let editService = _editService;
const { metadata = {}, name } = editService;
if (Object.keys(metadata).length) {
editService.metadataText = Object.keys(metadata)
.map(k => `${k}=${metadata[k]}`)
.join(',');
editService.metadataText = processMetaData(METADATA_ENTER)(metadata);
}
this.setState({ editService, editServiceDialogVisible: true, isCreate: !name });
}
@ -77,16 +77,15 @@ class EditServiceDialog extends React.Component {
onConfirm() {
const { isCreate } = this.state;
const editService = Object.assign({}, this.state.editService);
const { name, protectThreshold, healthCheckMode, metadataText, selector } = editService;
if (!this.validator({ name, protectThreshold, healthCheckMode })) return;
const { name, protectThreshold, healthCheckMode, metadataText = '', selector } = editService;
if (!this.validator({ name, protectThreshold })) return;
request({
method: isCreate ? 'POST' : 'PUT',
url: 'v1/ns/service',
data: {
serviceName: name,
protectThreshold,
healthCheckMode,
metadata: metadataText,
metadata: replaceEnter(METADATA_SEPARATOR)(metadataText),
selector: JSON.stringify(selector),
},
dataType: 'text',
@ -172,7 +171,7 @@ class EditServiceDialog extends React.Component {
onChange={protectThreshold => this.onChangeCluster({ protectThreshold })}
/>
</Form.Item>
<Form.Item
{/* <Form.Item
required
{...formItemLayout}
label={`${locale.healthCheckPattern}:`}
@ -187,9 +186,12 @@ class EditServiceDialog extends React.Component {
<Select.Option value="client">{locale.healthCheckPatternClient}</Select.Option>
<Select.Option value="none">{locale.healthCheckPatternNone}</Select.Option>
</Select>
</Form.Item>
</Form.Item> */}
<Form.Item label={`${locale.metadata}:`} {...formItemLayout}>
<Input.TextArea
<MonacoEditor
language={'properties'}
width={'100%'}
height={200}
value={metadataText}
onChange={metadataText => this.onChangeCluster({ metadataText })}
/>

View File

@ -56,12 +56,12 @@ class InstanceTable extends React.Component {
if (!clusterName) return;
const { pageSize, pageNum } = this.state;
request({
url: 'v1/ns/catalog/instanceList',
url: 'v1/ns/catalog/instances',
data: {
serviceName,
clusterName,
pgSize: pageSize,
startPg: pageNum,
pageSize,
pageNo: pageNum,
},
beforeSend: () => this.openLoading(),
success: instance => this.setState({ instance }),

View File

@ -18,7 +18,9 @@ import { Input, Button, Card, ConfigProvider, Form, Loading } from '@alifd/next'
import EditServiceDialog from './EditServiceDialog';
import EditClusterDialog from './EditClusterDialog';
import InstanceTable from './InstanceTable';
import { getParameter } from 'utils/nacosutil';
import { getParameter, processMetaData } from 'utils/nacosutil';
import MonacoEditor from 'components/MonacoEditor';
import { MONACO_READONLY_OPTIONS, METADATA_ENTER } from './constant';
import './ServiceDetail.scss';
const FormItem = Form.Item;
@ -64,7 +66,7 @@ class ServiceDetail extends React.Component {
getServiceDetail() {
const { serviceName } = this.state;
request({
url: `v1/ns/catalog/serviceDetail?serviceName=${serviceName}`,
url: `v1/ns/catalog/service?serviceName=${serviceName}`,
beforeSend: () => this.openLoading(),
success: ({ clusters = [], service = {} }) => this.setState({ service, clusters }),
complete: () => this.closeLoading(),
@ -96,9 +98,7 @@ class ServiceDetail extends React.Component {
client: locale.healthCheckPatternClient,
none: locale.healthCheckPatternNone,
};
const metadataText = Object.keys(metadata)
.map(key => `${key}=${metadata[key]}`)
.join(',');
const metadataText = processMetaData(METADATA_ENTER)(metadata);
return (
<div className="main-container service-detail">
<Loading
@ -138,11 +138,17 @@ class ServiceDetail extends React.Component {
<FormItem label={`${locale.protectThreshold}:`}>
<Input value={service.protectThreshold} readOnly />
</FormItem>
<FormItem label={`${locale.healthCheckPattern}:`}>
{/* <FormItem label={`${locale.healthCheckPattern}:`}>
<Input value={healthCheckMap[service.healthCheckMode]} readOnly />
</FormItem>
</FormItem> */}
<FormItem label={`${locale.metadata}:`}>
<Input value={metadataText} readOnly />
<MonacoEditor
language={'properties'}
width={'100%'}
height={200}
value={metadataText}
options={MONACO_READONLY_OPTIONS}
/>
</FormItem>
<FormItem label={`${locale.type}:`}>
<Input value={selector.type} readOnly />

View File

@ -12,11 +12,19 @@
*/
export const DIALOG_FORM_LAYOUT = {
labelCol: { fixedSpan: 12 },
wrapperCol: { span: 12 },
labelCol: { fixedSpan: 6 },
wrapperCol: { span: 18 },
};
export const HEALTHY_COLOR_MAPPING = {
true: 'green',
false: 'red',
};
export const MONACO_READONLY_OPTIONS = {
readOnly: true,
};
export const METADATA_SEPARATOR = ',';
export const METADATA_ENTER = '\r\n';

View File

@ -74,10 +74,15 @@ class ServiceList extends React.Component {
}
queryServiceList() {
const { currentPage, pageSize, keyword } = this.state;
const parameter = [`startPg=${currentPage}`, `pgSize=${pageSize}`, `keyword=${keyword}`];
const { currentPage, pageSize, keyword, withInstances = false } = this.state;
const parameter = [
`withInstances=${withInstances}`,
`pageNo=${currentPage}`,
`pageSize=${pageSize}`,
`keyword=${keyword}`,
];
request({
url: `v1/ns/catalog/serviceList?${parameter.join('&')}`,
url: `v1/ns/catalog/services?${parameter.join('&')}`,
beforeSend: () => this.openLoading(),
success: ({ count = 0, serviceList = [] } = {}) => {
this.setState({
@ -155,7 +160,7 @@ class ServiceList extends React.Component {
<div className="main-container service-management">
<Loading
shape="flower"
style={{ position: 'relative' }}
style={{ position: 'relative', width: '100%' }}
visible={this.state.loading}
tip="Loading..."
color="#333"
@ -204,8 +209,6 @@ class ServiceList extends React.Component {
<Col span="24" style={{ padding: 0 }}>
<Table
dataSource={this.state.dataSource}
fixedHeader
maxBodyHeight={530}
locale={{ empty: pubNoData }}
getRowProps={row => this.rowColor(row)}
>

View File

@ -46,3 +46,31 @@ export const getParameter = (search, name) => {
const [, value = ''] = hit.split('=');
return value;
};
/**
* 将回车符和空格替换
* @param {*} separator 替换符
*/
export const replaceEnter = (separator = ',') => text => {
if (typeof text !== 'string') {
return text;
}
return text
.replace(/\r\n/g, separator)
.replace(/[\r\n]/g, separator)
.replace(/[\t\s]/g, '');
};
/**
* 处理metaData对象生成可显示对象
*/
export const processMetaData = (separator = ',') => (metadata = {}) => {
if (Object.prototype.toString.call(metadata) !== '[object Object]') {
return '';
}
return Object.keys(metadata)
.map(key => `${key}=${metadata[key]}`)
.join(separator);
};

View File

@ -1,21 +1,20 @@
{
"compilerOptions": {
"strictNullChecks": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"jsx": "preserve",
"noUnusedParameters": true,
"noUnusedLocals": true,
"noImplicitAny": true,
"target": "es6",
"lib": [
"dom",
"es7"
]
},
"exclude": [
"node_modules",
"lib",
"es"
]
"compilerOptions": {
"strictNullChecks": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"jsx": "preserve",
"noUnusedParameters": true,
"noUnusedLocals": true,
"noImplicitAny": false,
"target": "es6",
"lib": ["dom", "es7"]
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts",
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long