Skip to content

Commit aa84b64

Browse files
authored
feat: Add vm config parser, unify vm and helm type app deployment (#503)
* refactor: Deploy page combine vm and helm type app * refactor: VM config translator, compat with edge case * Deploy page updated when changed runtime, version * Fix test code
1 parent 87e2e36 commit aa84b64

File tree

25 files changed

+1156
-787
lines changed

25 files changed

+1156
-787
lines changed

lib/config-parser/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mock/

lib/config-parser/factory.js

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
const toString = Object.prototype.toString;
2+
const isObject = val => toString.call(val) === '[object Object]';
3+
const isNumber = val => toString.call(val) === '[object Number]';
4+
5+
const validRenderTypes = [
6+
'radio',
7+
// 'checkbox',
8+
'input',
9+
'select',
10+
'text',
11+
'slider',
12+
'number',
13+
'node-role',
14+
'yaml'
15+
];
16+
17+
const TEXT_KEYS = ['description']; // keys will be transformed to text area
18+
19+
const NODE_MUST_HAVE_KEYS = ['cpu', 'memory'];
20+
21+
// keys to check if object can be normalized to Section
22+
const featureKeys = ['key'];
23+
24+
const sectionProto = {
25+
default: '',
26+
description: '',
27+
key: '',
28+
label: '',
29+
required: false,
30+
type: 'string',
31+
getRenderType() {
32+
if (validRenderTypes.includes(this.renderType)) {
33+
return this.renderType;
34+
}
35+
36+
if (TEXT_KEYS.includes(this.key)) {
37+
return 'text';
38+
}
39+
40+
if (
41+
this.type === 'string' &&
42+
!this.items &&
43+
!TEXT_KEYS.includes(this.key) &&
44+
!(this.range || this.step)
45+
) {
46+
return 'input';
47+
}
48+
49+
if (this.type === 'integer' && Array.isArray(this.range)) {
50+
return 'radio';
51+
}
52+
53+
if (
54+
this.type === 'integer' &&
55+
isNumber(this.step) &&
56+
isNumber(this.min) &&
57+
isNumber(this.max)
58+
) {
59+
return 'slider';
60+
}
61+
62+
if (this.type === 'integer' && !this.step && (isNumber(this.min) || isNumber(this.max))) {
63+
return 'number';
64+
}
65+
66+
if (this.type === 'string' && Array.isArray(this.items)) {
67+
return 'select';
68+
}
69+
70+
if (this.type === 'array' && isNodeItem(this)) {
71+
return 'node-role';
72+
}
73+
74+
if (typeof this.type === 'string' && 'changeable' in this) {
75+
return Array.isArray(this.range) ? 'radio' : 'input';
76+
}
77+
78+
throw Error(`unknown render type for section: ${this}`);
79+
},
80+
toString() {
81+
return JSON.stringify(...this);
82+
},
83+
toJSON() {
84+
// transform to object that react can accept
85+
const ret = Object.assign(
86+
{ ...this },
87+
{
88+
default: this.default,
89+
description: this.description,
90+
label: this.label || this.key,
91+
required: typeof this.required === 'boolean' ? this.required : this.required !== 'false',
92+
type: this.type,
93+
renderType: this.getRenderType()
94+
}
95+
);
96+
97+
ret.keyName = ret.originKey = ret.key;
98+
ret.defaultValue = ret.default;
99+
100+
delete ret.key;
101+
delete ret.default;
102+
103+
// generate unique keyName based on each level
104+
if (ret.keyPrefix) {
105+
ret.keyName = [ret.keyPrefix, ret.keyName].join('.');
106+
delete ret.keyPrefix;
107+
}
108+
109+
return ret;
110+
}
111+
};
112+
113+
export const isNodeItem = (item = {}) => {
114+
if (!Array.isArray(item.properties)) {
115+
return false;
116+
}
117+
118+
const itemKeys = item.properties.map(o => o.key);
119+
120+
return NODE_MUST_HAVE_KEYS.reduce((check, key) => {
121+
return check && itemKeys.includes(key);
122+
}, true);
123+
};
124+
125+
export const genPrefix = (prefix, base) => {
126+
if (typeof prefix === 'string' && prefix) {
127+
return [prefix, base].join('.');
128+
}
129+
return base;
130+
};
131+
132+
const factory = (ownProps = {}, extendProps = {}) => {
133+
const wrapObj = obj => {
134+
const ownKeys = (obj && Object.getOwnPropertyNames(obj)) || [];
135+
const hasFeatureKeys = featureKeys.reduce((check, key) => {
136+
return check && ownKeys.includes(key);
137+
}, true);
138+
139+
if (!hasFeatureKeys) {
140+
return obj;
141+
}
142+
143+
const inst = Object.create(sectionProto);
144+
145+
if (Array.isArray(obj.properties)) {
146+
obj.properties = factory(obj.properties, {
147+
keyPrefix: genPrefix(extendProps.keyPrefix, obj.key)
148+
});
149+
}
150+
151+
return Object.assign(inst, obj, extendProps);
152+
};
153+
154+
if (isObject(ownProps)) {
155+
return wrapObj(ownProps);
156+
}
157+
158+
if (Array.isArray(ownProps)) {
159+
return ownProps.filter(Boolean).map(prop => wrapObj(prop));
160+
}
161+
};
162+
163+
export default factory;

lib/config-parser/vm.js

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import _ from 'lodash';
2+
3+
import factory, { isNodeItem } from './factory';
4+
5+
const defaultOptions = {};
6+
7+
const CLUSTER_KEY = 'cluster';
8+
const NAME_KEY = 'name';
9+
const DESC_KEY = 'description';
10+
const SUBNET_KEY = 'subnet';
11+
const ENV_KEY = 'env';
12+
13+
// setting placeholder
14+
const defaultNameSetting = {
15+
key: 'name',
16+
label: 'Name',
17+
type: 'string',
18+
default: '',
19+
required: false
20+
};
21+
22+
const defaultRuntimeSetting = {
23+
key: 'runtime',
24+
label: 'Runtime',
25+
renderType: 'radio',
26+
range: []
27+
};
28+
29+
const defaultVersionSetting = {
30+
key: 'version',
31+
label: 'Version',
32+
items: [
33+
// {name: 'x1', value: 'v1'},
34+
]
35+
};
36+
37+
export default class VMConfigParser {
38+
constructor(config, options = {}) {
39+
this.config = config;
40+
this.options = _.extend(defaultOptions, options);
41+
42+
this.runtimeConf = _.clone(defaultRuntimeSetting);
43+
this.versionConf = _.clone(defaultVersionSetting);
44+
this.vxnetConf = { items: [] };
45+
}
46+
47+
setConfig(config = {}) {
48+
if (typeof config === 'string') {
49+
config = JSON.parse(config);
50+
}
51+
52+
if (!this.validConfig(config)) {
53+
throw Error('invalid config input');
54+
}
55+
56+
// config.json
57+
this.config = config;
58+
}
59+
60+
validConfig(config) {
61+
return (
62+
_.isObject(config) && typeof config.type === 'string' && Array.isArray(config.properties)
63+
);
64+
}
65+
66+
isReady() {
67+
return !_.isEmpty(this.config);
68+
}
69+
70+
getConfigByKey(config, key) {
71+
if (typeof config === 'string' && !key) {
72+
key = config;
73+
config = null;
74+
}
75+
76+
if (typeof key !== 'string') {
77+
throw Error(`key should be string, but ${typeof key} given`);
78+
}
79+
80+
config = config || this.config;
81+
if (_.isEmpty(config)) {
82+
return;
83+
}
84+
85+
const properties = config.properties || config;
86+
87+
if (_.isArray(properties)) {
88+
return _.find(properties, { key });
89+
}
90+
if (_.isObject(properties)) {
91+
return _.extend({}, properties[key], { key });
92+
}
93+
}
94+
95+
getClusterSetting() {
96+
return this.getConfigByKey(CLUSTER_KEY);
97+
}
98+
99+
/**
100+
* mock basic setting for helm app
101+
*/
102+
getDefaultBasicSetting(override = {}) {
103+
const setting = [];
104+
const nameConf = _.clone(defaultNameSetting);
105+
setting.push(nameConf, this.runtimeConf, this.versionConf);
106+
107+
if (!_.isEmpty(override)) {
108+
_.forEach(setting, setting => {
109+
if (setting.key === override.key) {
110+
_.extend(setting, override);
111+
}
112+
});
113+
}
114+
115+
return factory(setting, { keyPrefix: CLUSTER_KEY });
116+
}
117+
118+
getBasicSetting() {
119+
const clusterSetting = this.getClusterSetting();
120+
const setting = [];
121+
const nameConf = this.getConfigByKey(clusterSetting, NAME_KEY);
122+
const descConf = this.getConfigByKey(clusterSetting, DESC_KEY);
123+
124+
setting.push(nameConf, descConf, this.runtimeConf, this.versionConf);
125+
126+
return factory(setting, { keyPrefix: CLUSTER_KEY });
127+
}
128+
129+
getNodeSetting() {
130+
const clusterSetting = this.getClusterSetting();
131+
return factory(_.filter(clusterSetting.properties, isNodeItem), { keyPrefix: 'node' });
132+
}
133+
134+
getVxnetSetting() {
135+
return factory([this.vxnetConf], { keyPrefix: CLUSTER_KEY });
136+
}
137+
138+
getEnvSetting() {
139+
const envSetting = this.getConfigByKey(ENV_KEY);
140+
return factory(_.get(envSetting, 'properties', []), { keyPrefix: 'env', labelPrefix: 'env' });
141+
}
142+
143+
setRuntimes(runtimes = [], mergeProps) {
144+
this.runtimeConf.range = runtimes;
145+
!_.isEmpty(mergeProps) && _.extend(this.runtimeConf, mergeProps);
146+
}
147+
148+
setVersions(versions = [], mergeProps) {
149+
this.versionConf.items = versions;
150+
!_.isEmpty(mergeProps) && _.extend(this.versionConf, mergeProps);
151+
}
152+
153+
setSubnets(nets = [], mergeProps) {
154+
if (!this.vxnetConf.key) {
155+
const clusterSetting = this.getClusterSetting();
156+
157+
if (!_.isEmpty(clusterSetting)) {
158+
_.extend(this.vxnetConf, this.getConfigByKey(clusterSetting, SUBNET_KEY));
159+
}
160+
}
161+
162+
this.vxnetConf.items = nets;
163+
164+
!_.isEmpty(mergeProps) && _.extend(this.vxnetConf, mergeProps);
165+
}
166+
}

src/components/Base/Button/index.jsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,27 @@ import styles from './index.scss';
88

99
export default class Button extends PureComponent {
1010
static propTypes = {
11-
type: PropTypes.string,
11+
type: PropTypes.oneOf(['default', 'primary', 'delete']),
1212
htmlType: PropTypes.oneOf(['submit', 'button', 'reset']),
1313
className: PropTypes.string,
1414
style: PropTypes.object,
1515
loading: PropTypes.bool,
1616
disabled: PropTypes.bool,
17-
onClick: PropTypes.func,
17+
onClick: PropTypes.func
1818
};
1919

2020
static defaultProps = {
2121
type: 'default',
22-
htmlType: 'button',
22+
htmlType: 'button'
2323
};
2424

25-
handleClick = (e) => {
25+
handleClick = e => {
2626
const isDisabled = this.props.disabled;
2727

2828
if (!isDisabled && isFunction(this.props.onClick)) {
2929
this.props.onClick(e);
3030
}
31-
}
31+
};
3232

3333
render() {
3434
const { children, type, htmlType, className, loading, ...others } = this.props;
@@ -37,7 +37,7 @@ export default class Button extends PureComponent {
3737
<button
3838
className={classNames(styles.button, styles[type], {
3939
[styles.loading]: loading,
40-
[className]: className,
40+
[className]: className
4141
})}
4242
type={htmlType}
4343
onClick={this.handleClick}

src/components/Base/CodeMirror/index.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export default class CodeMirrorX extends React.Component {
3434
onChange={onChange}
3535
options={{ mode, lineNumbers: true }}
3636
ref="editor"
37+
{...rest}
3738
/>
3839
);
3940
}

0 commit comments

Comments
 (0)