Merge pull request #894 from jameslcj/feature/issue479
Standardize the editor of metadata #479
This commit is contained in:
commit
367a8b9ee9
@ -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'],
|
||||
},
|
||||
|
@ -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;
|
@ -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,
|
||||
};
|
@ -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.
|
||||
*/
|
@ -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;
|
@ -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 })}
|
||||
/>
|
||||
|
@ -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 })}
|
||||
/>
|
||||
|
@ -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 })}
|
||||
/>
|
||||
|
@ -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 }),
|
||||
|
@ -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 />
|
||||
|
@ -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';
|
||||
|
@ -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)}
|
||||
>
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -6,16 +6,15 @@
|
||||
"jsx": "preserve",
|
||||
"noUnusedParameters": true,
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitAny": false,
|
||||
"target": "es6",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es7"
|
||||
]
|
||||
"lib": ["dom", "es7"]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib",
|
||||
"es"
|
||||
"**/*.spec.ts",
|
||||
]
|
||||
}
|
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