Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/config-parser/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mock/
163 changes: 163 additions & 0 deletions lib/config-parser/factory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
const toString = Object.prototype.toString;
const isObject = val => toString.call(val) === '[object Object]';
const isNumber = val => toString.call(val) === '[object Number]';

const validRenderTypes = [
'radio',
// 'checkbox',
'input',
'select',
'text',
'slider',
'number',
'node-role',
'yaml'
];

const TEXT_KEYS = ['description']; // keys will be transformed to text area

const NODE_MUST_HAVE_KEYS = ['cpu', 'memory'];

// keys to check if object can be normalized to Section
const featureKeys = ['key'];

const sectionProto = {
default: '',
description: '',
key: '',
label: '',
required: false,
type: 'string',
getRenderType() {
if (validRenderTypes.includes(this.renderType)) {
return this.renderType;
}

if (TEXT_KEYS.includes(this.key)) {
return 'text';
}

if (
this.type === 'string' &&
!this.items &&
!TEXT_KEYS.includes(this.key) &&
!(this.range || this.step)
) {
return 'input';
}

if (this.type === 'integer' && Array.isArray(this.range)) {
return 'radio';
}

if (
this.type === 'integer' &&
isNumber(this.step) &&
isNumber(this.min) &&
isNumber(this.max)
) {
return 'slider';
}

if (this.type === 'integer' && !this.step && (isNumber(this.min) || isNumber(this.max))) {
return 'number';
}

if (this.type === 'string' && Array.isArray(this.items)) {
return 'select';
}

if (this.type === 'array' && isNodeItem(this)) {
return 'node-role';
}

if (typeof this.type === 'string' && 'changeable' in this) {
return Array.isArray(this.range) ? 'radio' : 'input';
}

throw Error(`unknown render type for section: ${this}`);
},
toString() {
return JSON.stringify(...this);
},
toJSON() {
// transform to object that react can accept
const ret = Object.assign(
{ ...this },
{
default: this.default,
description: this.description,
label: this.label || this.key,
required: typeof this.required === 'boolean' ? this.required : this.required !== 'false',
type: this.type,
renderType: this.getRenderType()
}
);

ret.keyName = ret.originKey = ret.key;
ret.defaultValue = ret.default;

delete ret.key;
delete ret.default;

// generate unique keyName based on each level
if (ret.keyPrefix) {
ret.keyName = [ret.keyPrefix, ret.keyName].join('.');
delete ret.keyPrefix;
}

return ret;
}
};

export const isNodeItem = (item = {}) => {
if (!Array.isArray(item.properties)) {
return false;
}

const itemKeys = item.properties.map(o => o.key);

return NODE_MUST_HAVE_KEYS.reduce((check, key) => {
return check && itemKeys.includes(key);
}, true);
};

export const genPrefix = (prefix, base) => {
if (typeof prefix === 'string' && prefix) {
return [prefix, base].join('.');
}
return base;
};

const factory = (ownProps = {}, extendProps = {}) => {
const wrapObj = obj => {
const ownKeys = (obj && Object.getOwnPropertyNames(obj)) || [];
const hasFeatureKeys = featureKeys.reduce((check, key) => {
return check && ownKeys.includes(key);
}, true);

if (!hasFeatureKeys) {
return obj;
}

const inst = Object.create(sectionProto);

if (Array.isArray(obj.properties)) {
obj.properties = factory(obj.properties, {
keyPrefix: genPrefix(extendProps.keyPrefix, obj.key)
});
}

return Object.assign(inst, obj, extendProps);
};

if (isObject(ownProps)) {
return wrapObj(ownProps);
}

if (Array.isArray(ownProps)) {
return ownProps.filter(Boolean).map(prop => wrapObj(prop));
}
};

export default factory;
166 changes: 166 additions & 0 deletions lib/config-parser/vm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import _ from 'lodash';

import factory, { isNodeItem } from './factory';

const defaultOptions = {};

const CLUSTER_KEY = 'cluster';
const NAME_KEY = 'name';
const DESC_KEY = 'description';
const SUBNET_KEY = 'subnet';
const ENV_KEY = 'env';

// setting placeholder
const defaultNameSetting = {
key: 'name',
label: 'Name',
type: 'string',
default: '',
required: false
};

const defaultRuntimeSetting = {
key: 'runtime',
label: 'Runtime',
renderType: 'radio',
range: []
};

const defaultVersionSetting = {
key: 'version',
label: 'Version',
items: [
// {name: 'x1', value: 'v1'},
]
};

export default class VMConfigParser {
constructor(config, options = {}) {
this.config = config;
this.options = _.extend(defaultOptions, options);

this.runtimeConf = _.clone(defaultRuntimeSetting);
this.versionConf = _.clone(defaultVersionSetting);
this.vxnetConf = { items: [] };
}

setConfig(config = {}) {
if (typeof config === 'string') {
config = JSON.parse(config);
}

if (!this.validConfig(config)) {
throw Error('invalid config input');
}

// config.json
this.config = config;
}

validConfig(config) {
return (
_.isObject(config) && typeof config.type === 'string' && Array.isArray(config.properties)
);
}

isReady() {
return !_.isEmpty(this.config);
}

getConfigByKey(config, key) {
if (typeof config === 'string' && !key) {
key = config;
config = null;
}

if (typeof key !== 'string') {
throw Error(`key should be string, but ${typeof key} given`);
}

config = config || this.config;
if (_.isEmpty(config)) {
return;
}

const properties = config.properties || config;

if (_.isArray(properties)) {
return _.find(properties, { key });
}
if (_.isObject(properties)) {
return _.extend({}, properties[key], { key });
}
}

getClusterSetting() {
return this.getConfigByKey(CLUSTER_KEY);
}

/**
* mock basic setting for helm app
*/
getDefaultBasicSetting(override = {}) {
const setting = [];
const nameConf = _.clone(defaultNameSetting);
setting.push(nameConf, this.runtimeConf, this.versionConf);

if (!_.isEmpty(override)) {
_.forEach(setting, setting => {
if (setting.key === override.key) {
_.extend(setting, override);
}
});
}

return factory(setting, { keyPrefix: CLUSTER_KEY });
}

getBasicSetting() {
const clusterSetting = this.getClusterSetting();
const setting = [];
const nameConf = this.getConfigByKey(clusterSetting, NAME_KEY);
const descConf = this.getConfigByKey(clusterSetting, DESC_KEY);

setting.push(nameConf, descConf, this.runtimeConf, this.versionConf);

return factory(setting, { keyPrefix: CLUSTER_KEY });
}

getNodeSetting() {
const clusterSetting = this.getClusterSetting();
return factory(_.filter(clusterSetting.properties, isNodeItem), { keyPrefix: 'node' });
}

getVxnetSetting() {
return factory([this.vxnetConf], { keyPrefix: CLUSTER_KEY });
}

getEnvSetting() {
const envSetting = this.getConfigByKey(ENV_KEY);
return factory(_.get(envSetting, 'properties', []), { keyPrefix: 'env', labelPrefix: 'env' });
}

setRuntimes(runtimes = [], mergeProps) {
this.runtimeConf.range = runtimes;
!_.isEmpty(mergeProps) && _.extend(this.runtimeConf, mergeProps);
}

setVersions(versions = [], mergeProps) {
this.versionConf.items = versions;
!_.isEmpty(mergeProps) && _.extend(this.versionConf, mergeProps);
}

setSubnets(nets = [], mergeProps) {
if (!this.vxnetConf.key) {
const clusterSetting = this.getClusterSetting();

if (!_.isEmpty(clusterSetting)) {
_.extend(this.vxnetConf, this.getConfigByKey(clusterSetting, SUBNET_KEY));
}
}

this.vxnetConf.items = nets;

!_.isEmpty(mergeProps) && _.extend(this.vxnetConf, mergeProps);
}
}
12 changes: 6 additions & 6 deletions src/components/Base/Button/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,27 @@ import styles from './index.scss';

export default class Button extends PureComponent {
static propTypes = {
type: PropTypes.string,
type: PropTypes.oneOf(['default', 'primary', 'delete']),
htmlType: PropTypes.oneOf(['submit', 'button', 'reset']),
className: PropTypes.string,
style: PropTypes.object,
loading: PropTypes.bool,
disabled: PropTypes.bool,
onClick: PropTypes.func,
onClick: PropTypes.func
};

static defaultProps = {
type: 'default',
htmlType: 'button',
htmlType: 'button'
};

handleClick = (e) => {
handleClick = e => {
const isDisabled = this.props.disabled;

if (!isDisabled && isFunction(this.props.onClick)) {
this.props.onClick(e);
}
}
};

render() {
const { children, type, htmlType, className, loading, ...others } = this.props;
Expand All @@ -37,7 +37,7 @@ export default class Button extends PureComponent {
<button
className={classNames(styles.button, styles[type], {
[styles.loading]: loading,
[className]: className,
[className]: className
})}
type={htmlType}
onClick={this.handleClick}
Expand Down
1 change: 1 addition & 0 deletions src/components/Base/CodeMirror/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default class CodeMirrorX extends React.Component {
onChange={onChange}
options={{ mode, lineNumbers: true }}
ref="editor"
{...rest}
/>
);
}
Expand Down
Loading