Compare commits

...

12 Commits
main ... v1.2.4

Author SHA1 Message Date
github-actions[bot] 75fd406f23
Feat: change the version in package.json (#377)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit c09fc203d7)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-02-24 09:46:25 +08:00
github-actions[bot] b557d60bad
Feat: optimize the uischema rendering engine (#374)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit b818d5daf0)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-02-23 11:12:05 +08:00
github-actions[bot] 8b9cd0f9fb
Fix: change app condition show (#370)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit cbfeb65823)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-02-18 15:50:27 +08:00
github-actions[bot] 43d3af949d
Fix: optimize the total number of parameters calculation algorithm. (#368)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 6253bf7e73)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-02-18 15:22:16 +08:00
github-actions[bot] 29e490a51b
Fix: inaccurate hints after new cluster (#365)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit c733cb21d6)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-02-18 14:07:30 +08:00
github-actions[bot] d4fa85d7d9
[Backport release-1.2] Fix: create cluster add addon message show (#362)
* Fix: cluster add addon message

Signed-off-by: wb-wb665667 <wb-wb665667@alibaba-inc.com>
(cherry picked from commit 4df1ca54dc)

* Fix: yarn run lint

Signed-off-by: wb-wb665667 <wb-wb665667@alibaba-inc.com>
(cherry picked from commit 70a81e5f59)

* Fix: create cluster addon message style

Signed-off-by: wb-wb665667 <wb-wb665667@alibaba-inc.com>
(cherry picked from commit e564fdf89f)

Co-authored-by: wb-wb665667 <wb-wb665667@alibaba-inc.com>
2022-02-18 14:07:18 +08:00
github-actions[bot] 07b7d795b4
Fix: the pattern of uischema is not effective (#360)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit f0703b326c)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-02-18 14:07:05 +08:00
github-actions[bot] 8bfb67708a
Fix: compoent type add filter search and group (#359)
Signed-off-by: wb-wb665667 <wb-wb665667@alibaba-inc.com>
(cherry picked from commit 383a14f9ec)

Co-authored-by: wb-wb665667 <wb-wb665667@alibaba-inc.com>
2022-02-18 14:06:51 +08:00
barnettZQG de8134046a
Chore: merge main to 1.2 (#355)
* Feat: refactor the editing mode of the workflow (#340)

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Feat: Added version info into sidebar (#344)

* Added version info in the velaux

Signed-off-by: Tyler Henderson <henderson.tyler@outlook.com>

* Removed a div

Signed-off-by: Tyler Henderson <henderson.tyler@outlook.com>

* Changed width to paddling-left

Changed width and text-align to padding-left

Signed-off-by: Tyler Henderson <henderson.tyler@outlook.com>

* Feat: change version (#347)

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

* Fix: uiSchema structs optimization (#353)

* Fix: uishcema style optimization

Signed-off-by: wb-wb665667 <wb-wb665667@alibaba-inc.com>

* Fix: uisheme struct style optimization

Signed-off-by: wb-wb665667 <wb-wb665667@alibaba-inc.com>

* Fix: run eslint

Signed-off-by: wb-wb665667 <wb-wb665667@alibaba-inc.com>

* Fix: structs style show

Signed-off-by: wb-wb665667 <wb-wb665667@alibaba-inc.com>

Co-authored-by: wb-wb665667 <wb-wb665667@alibaba-inc.com>

* Fix: create targets namespace optimization (#350)

* Fix: create targets namespace optimization

Signed-off-by: wb-wb665667 <wb-wb665667@alibaba-inc.com>

* Fix: create nampsace data success, data write back

Signed-off-by: wb-wb665667 <wb-wb665667@alibaba-inc.com>

Co-authored-by: wb-wb665667 <wb-wb665667@alibaba-inc.com>
Co-authored-by: barnettZQG <barnett.zqg@gmail.com>

Co-authored-by: Tyler Henderson <51831689+HendersonTyler@users.noreply.github.com>
Co-authored-by: wangbow <18700441876@126.com>
Co-authored-by: wb-wb665667 <wb-wb665667@alibaba-inc.com>
2022-02-14 10:45:50 +08:00
github-actions[bot] a2c793f6eb
Feat: change version (#348)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit c7fe642845)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-02-09 11:38:20 +08:00
github-actions[bot] e1c88692a6
[Backport release-1.2] Feat: Added version info into sidebar (#345)
* Added version info in the velaux

Signed-off-by: Tyler Henderson <henderson.tyler@outlook.com>
(cherry picked from commit 9acba53ad5)

* Removed a div

Signed-off-by: Tyler Henderson <henderson.tyler@outlook.com>
(cherry picked from commit 460a2d1855)

* Changed width to paddling-left

Changed width and text-align to padding-left

Signed-off-by: Tyler Henderson <henderson.tyler@outlook.com>
(cherry picked from commit be28556e8f)

Co-authored-by: Tyler Henderson <henderson.tyler@outlook.com>
2022-02-09 10:50:14 +08:00
barnettZQG 3563dbe98d Feat: refactor the editing mode of the workflow
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2022-01-25 17:01:01 +08:00
57 changed files with 1798 additions and 1040 deletions

View File

@ -1,6 +1,6 @@
{
"name": "valaux",
"version": "1.1.0",
"version": "1.2.4",
"private": true,
"scripts": {
"start": "node scripts/start.js",
@ -42,10 +42,6 @@
"@alifd/meet-react": "^2.0.0",
"@antv/g6": "4.3.11",
"@b-design/ui": "^1.0.63",
"@types/js-yaml": "^4.0.1",
"@types/lodash": "^4.14.176",
"@types/react": "^16.3.0",
"@types/react-cookies": "^0.1.0",
"ansi-to-react": "^6.1.6",
"axios": "0.24.0",
"diagram-maker": "^1.3.0",
@ -72,7 +68,8 @@
"redux": "4.1.2",
"remark-gfm": "3.0.1",
"tiny-pubsub": "^1.1.0",
"tsx-control-statements": "4.1.1"
"tsx-control-statements": "4.1.1",
"uuid": "^8.3.2"
},
"devDependencies": {
"@babel/core": "^7.12.10",
@ -87,6 +84,11 @@
"@types/jest": "^26.0.24",
"@types/react-copy-to-clipboard": "^5.0.2",
"@types/react-dom": "^17.0.9",
"@types/js-yaml": "^4.0.1",
"@types/lodash": "^4.14.176",
"@types/react": "^16.3.0",
"@types/react-cookies": "^0.1.0",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^4.0.0",
"@typescript-eslint/parser": "^4.0.0",
"@umijs/fabric": "2.8.1",

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Dialog, Table, Card, Step, Loading, Button, Balloon } from '@b-design/ui';
import { Dialog, Table, Card, Loading, Button, Balloon, Icon } from '@b-design/ui';
import type { ApplicationStatus, Condition } from '../../interface/application';
import Translation from '../../components/Translation';
import { If } from 'tsx-control-statements/components';
@ -17,37 +17,6 @@ type Props = {
class StatusShow extends React.Component<Props> {
render() {
const { applicationStatus, onClose, loading, title } = this.props;
const allConditions: Condition[] = [
{ type: 'Parsed', status: 'False' },
{ type: 'Revision', status: 'False' },
{ type: 'Policy', status: 'False' },
{ type: 'Render', status: 'False' },
{ type: 'WorkflowFinished', status: 'False' },
{ type: 'Ready', status: 'False' },
];
const getCurrent = (conditions?: Condition[]) => {
let index = 0;
conditions?.map((condition: Condition, i: number) => {
if (condition.status == 'False') {
index = i;
}
allConditions.map((c) => {
if (c.type == condition.type) {
c.status = condition.status;
c.lastTransitionTime = condition.lastTransitionTime;
c.reason = condition.reason;
c.message = condition.message;
}
});
if (condition.type == 'Deleting') {
allConditions.push(condition);
}
});
if (index == 0 && conditions) {
return conditions.length;
}
return index;
};
return (
<Dialog
locale={locale.Dialog}
@ -107,26 +76,42 @@ class StatusShow extends React.Component<Props> {
<Card
locale={locale.Card}
style={{ marginTop: '8px' }}
contentHeight="auto"
title={<Translation>Conditions</Translation>}
>
<Step current={getCurrent(applicationStatus?.conditions)}>
{allConditions.map((condition) => {
const content = condition.message ? (
<Balloon
trigger={
<span style={{ cursor: 'pointer', color: '#1b58f4' }}>
{condition.reason}
</span>
}
>
{condition.message}
</Balloon>
) : (
condition.reason
);
return <Step.Item title={condition.type} content={content} />;
})}
</Step>
<Table locale={locale.Table} dataSource={applicationStatus?.conditions}>
<Table.Column
width="150px"
dataIndex="type"
title={<Translation>Type</Translation>}
/>
<Table.Column dataIndex="status" title={<Translation>Status</Translation>} />
<Table.Column
dataIndex="lastTransitionTime"
title={<Translation>LastTransitionTime</Translation>}
/>
<Table.Column
dataIndex="reason"
title={<Translation>Reason</Translation>}
cell={(v: string, index: number, row: Condition) => {
if (row.message) {
return (
<Balloon
trigger={
<span style={{ color: 'red', cursor: 'pointer' }}>
{v} <Icon size={'xs'} type="question-circle" />
</span>
}
>
{row.message}
</Balloon>
);
}
return <span>{v}</span>;
}}
/>
</Table>
</Card>
</If>
<If condition={applicationStatus?.services}>

View File

@ -1,6 +1,9 @@
.ui-schema-container,
.group-container {
.next-form-item {
.next-form-item-control {
width: 100%;
}
.next-form-item-label {
label {
color: #333;

View File

@ -1,5 +1,5 @@
import React, { Component } from 'react';
import { Form, Input, Select, Field, Switch } from '@b-design/ui';
import { Form, Input, Select, Field, Switch, Grid, Divider } from '@b-design/ui';
import Translation from '../Translation';
import type { UIParam, UIParamValidate } from '../../interface/application';
import Group from '../../extends/Group';
@ -9,8 +9,8 @@ import SecretSelect from '../../extends/SecretSelect';
import SecretKeySelect from '../../extends/SecretKeySelect';
import Structs from '../../extends/Structs';
import CPUNumber from '../../extends/CPUNumber';
import DiskNumber from '../../extends/DiskNumber';
import MemoryNumber from '../../extends/MemoryNumber';
import InnerGroup from '../../extends/InnerGroup';
import K8sObjectsCode from '../../extends/K8sObjectsCode';
import type { Rule } from '@alifd/field';
import KV from '../../extends/KV';
@ -18,55 +18,62 @@ import './index.less';
import { checkImageName, replaceUrl } from '../../utils/common';
import locale from '../../utils/locale';
import HelmValues from '../../extends/HelmValues';
import { If } from 'tsx-control-statements/components';
import i18n from 'i18next';
const { Col, Row } = Grid;
type Props = {
inline?: boolean;
id?: string;
value?: any;
uiSchema?: UIParam[];
maxColSpan?: number;
onChange?: (params: any) => void;
registerForm?: (form: Field) => void;
disableRenderRow?: boolean;
mode: 'new' | 'edit';
};
function converRule(validete: UIParamValidate) {
function convertRule(validate: UIParamValidate) {
const rules = [];
if (!validete) {
if (!validate) {
return [];
}
if (validete.required) {
if (validate.required) {
rules.push({
required: true,
message: 'This field is required.',
});
}
if (validete.min) {
if (validate.min != undefined) {
rules.push({
min: validete.min,
message: 'Enter a number greater than ' + validete.min,
min: validate.min,
message: 'Enter a number greater than ' + validate.min,
});
}
if (validete.max) {
if (validate.max != undefined) {
rules.push({
max: validete.max,
message: 'Enter a number less than ' + validete.max,
max: validate.max,
message: 'Enter a number less than ' + validate.max,
});
}
if (validete.minLength) {
if (validate.minLength != undefined) {
rules.push({
minLength: validete.minLength,
message: `Enter a minimum of ${validete.minLength} characters.`,
minLength: validate.minLength,
message: `Enter a minimum of ${validate.minLength} characters.`,
});
}
if (validete.maxLength) {
if (validate.maxLength != undefined) {
rules.push({
maxLength: validete.maxLength,
message: `Enter a maximum of ${validete.maxLength} characters.`,
maxLength: validate.maxLength,
message: `Enter a maximum of ${validate.maxLength} characters.`,
});
}
if (validete.pattern) {
if (validate.pattern) {
rules.push({
pattern: validete.pattern,
message: `Please enter a value that conforms to the specification. ` + validete.pattern,
pattern: new RegExp(validate.pattern),
message: `Please enter a value that conforms to the specification. ` + validate.pattern,
});
}
return rules;
@ -74,6 +81,7 @@ function converRule(validete: UIParamValidate) {
type State = {
secretKeys?: string[];
advanced: boolean;
};
class UISchema extends Component<Props, State> {
@ -94,6 +102,7 @@ class UISchema extends Component<Props, State> {
}
this.state = {
secretKeys: [],
advanced: false,
};
}
@ -105,6 +114,10 @@ class UISchema extends Component<Props, State> {
this.registerForm[key] = form;
};
onChangeAdvanced = (advanced: boolean) => {
this.setState({ advanced: advanced });
};
setValues = () => {
const { value } = this.props;
if (value) {
@ -126,11 +139,15 @@ class UISchema extends Component<Props, State> {
};
render() {
const { uiSchema, inline } = this.props;
const { advanced } = this.state;
const { uiSchema, inline, maxColSpan, disableRenderRow, value } = this.props;
if (!uiSchema) {
return <div />;
}
let onlyShowRequired = false;
if (uiSchema.length > 5) {
onlyShowRequired = true;
}
const items = uiSchema.map((param) => {
const init = this.form.init;
const required = param.validate && param.validate.required;
@ -138,6 +155,10 @@ class UISchema extends Component<Props, State> {
return;
}
if (onlyShowRequired && !param.validate.required && !advanced) {
return;
}
const validator = (rule: Rule, v: any, callback: (error?: string) => void) => {
if (this.registerForm[param.jsonKey]) {
this.registerForm[param.jsonKey].validate((errors: any) => {
@ -153,12 +174,22 @@ class UISchema extends Component<Props, State> {
}
};
let description = param.description;
if (description) {
description = i18n.t(description);
}
let label = param.label;
if (label) {
label = i18n.t(label);
}
const initValue = (value && value[param.jsonKey]) || param.validate.defaultValue;
const disableEdit = (param.validate.immutable && this.props.mode == 'edit') || false;
const getGroup = (children: React.ReactNode) => {
return (
<Group
hasToggleIcon
description={<Translation>{param.description || ''}</Translation>}
title={<Translation>{param.label || ''}</Translation>}
description={description}
title={label}
closed={true}
required={param.validate && param.validate.required}
field={this.form}
@ -170,279 +201,369 @@ class UISchema extends Component<Props, State> {
}
}}
>
<Form.Item required={required} disabled={param.disable} key={param.jsonKey}>
<Form.Item required={required} disabled={disableEdit} key={param.jsonKey}>
{children}
</Form.Item>
</Group>
);
};
const { value } = this.props;
const initValue = param.validate.defaultValue || (value && value[param.jsonKey]);
switch (param.uiType) {
case 'Switch':
const switchResult = init(param.jsonKey, {
initValue: initValue,
rules: converRule(param.validate),
});
return (
<Form.Item
className="switch-container"
required={required}
key={param.jsonKey}
label={<span title={param.description}>{param.label}</span>}
help={param.description}
disabled={param.disable}
>
<Switch
id={switchResult.id}
onChange={switchResult.onChange}
checked={switchResult.value ? true : false}
/>
</Form.Item>
);
case 'Input':
return (
<Form.Item
required={required}
labelAlign={inline ? 'inset' : 'left'}
label={param.label}
key={param.jsonKey}
help={
<div dangerouslySetInnerHTML={{ __html: replaceUrl(param.description || '') }} />
const item = () => {
switch (param.uiType) {
case 'Switch':
const getDefaultSwtichValue = (validate: any) => {
if (validate.required === true) {
return false;
}
disabled={param.disable}
>
<Input
{...init(param.jsonKey, {
initValue: initValue,
rules: converRule(param.validate),
})}
/>
</Form.Item>
);
case 'Password':
return (
<Form.Item
required={required}
labelAlign={inline ? 'inset' : 'left'}
label={param.label}
key={param.jsonKey}
help={
<div dangerouslySetInnerHTML={{ __html: replaceUrl(param.description || '') }} />
}
disabled={param.disable}
>
<Input
htmlType="password"
{...init(param.jsonKey, {
initValue: initValue,
rules: converRule(param.validate),
})}
/>
</Form.Item>
);
case 'Select':
return (
<Form.Item
required={required}
labelAlign={inline ? 'inset' : 'left'}
label={param.label}
key={param.jsonKey}
help={param.description}
disabled={param.disable}
>
<Select
locale={locale.Select}
{...init(param.jsonKey, {
initValue: initValue,
rules: converRule(param.validate),
})}
dataSource={param.validate && param.validate.options}
/>
</Form.Item>
);
case 'Number':
return (
<Form.Item
required={required}
label={param.label}
key={param.jsonKey}
help={param.description}
disabled={param.disable}
>
<Input
{...init(param.jsonKey, {
initValue: initValue,
rules: converRule(param.validate),
})}
htmlType="number"
/>
</Form.Item>
);
case 'ImageInput':
return (
<Form.Item
required={required}
label={param.label}
help={param.description}
disabled={param.disable}
key={param.jsonKey}
>
<ImageInput
{...init(param.jsonKey, {
initValue: initValue,
rules: [
{
required: true,
pattern: checkImageName,
message: 'Please enter a valid image name',
},
],
})}
/>
</Form.Item>
);
case 'KV':
const children = (
<KV
{...init(param.jsonKey, {
initValue: initValue,
rules: converRule(param.validate),
})}
key={param.jsonKey}
additional={param.additional}
additionalParameter={param.additionalParameter}
/>
);
return getGroup(children);
case 'HelmValues':
return getGroup(
<HelmValues
{...init(param.jsonKey, {
initValue: initValue,
rules: converRule(param.validate),
})}
key={param.jsonKey}
additional={param.additional}
additionalParameter={param.additionalParameter}
/>,
);
case 'Strings':
return getGroup(
<Strings
key={param.jsonKey}
{...init(param.jsonKey, {
initValue: initValue,
rules: converRule(param.validate),
})}
/>,
);
case 'SecretSelect':
return (
<Form.Item
labelAlign={inline ? 'inset' : 'left'}
required={required}
label={param.label}
help={param.description}
disabled={param.disable}
key={param.jsonKey}
>
<SecretSelect
setKeys={(keys: string[]) => {
this.setState({ secretKeys: keys });
}}
{...init(param.jsonKey, {
initValue: param.validate.defaultValue || this.props.value?.name,
rules: converRule(param.validate),
})}
/>
</Form.Item>
);
case 'SecretKeySelect':
return (
<Form.Item
required={required}
labelAlign={inline ? 'inset' : 'left'}
label={param.label}
help={param.description}
disabled={param.disable}
key={param.jsonKey}
>
<SecretKeySelect
secretKeys={this.state.secretKeys}
{...init(param.jsonKey, {
initValue: param.validate.defaultValue || this.props.value?.key,
rules: converRule(param.validate),
})}
/>
</Form.Item>
);
case 'CPUNumber':
return (
<Form.Item
required={required}
label={param.label}
help={param.description}
disabled={param.disable}
key={param.jsonKey}
>
<CPUNumber
{...init(param.jsonKey, {
initValue: initValue,
rules: [
{
required: param.validate.required,
min: 0,
message: 'Please enter a valid cpu request number',
},
],
})}
/>
</Form.Item>
);
case 'MemoryNumber':
return (
<Form.Item
required={required}
label={param.label}
help={param.description}
disabled={param.disable}
key={param.jsonKey}
>
<MemoryNumber
{...init(param.jsonKey, {
initValue: initValue,
rules: [
{
required: param.validate.required,
min: 0,
message: 'Please enter a valid memory request number',
},
],
})}
/>
</Form.Item>
);
case 'Group':
if (param.subParameters && param.subParameters.length > 0) {
};
const switchResult = init(param.jsonKey, {
initValue: initValue || getDefaultSwtichValue(param.validate),
rules: convertRule(param.validate),
});
return (
<Group
<Form.Item
className="switch-container"
required={required}
key={param.jsonKey}
hasToggleIcon
description={<Translation>{param.description || ''}</Translation>}
title={<Translation>{param.label || ''}</Translation>}
closed={true}
required={param.validate && param.validate.required}
field={this.form}
jsonKey={param.jsonKey || ''}
propertyValue={this.props.value}
onChange={(values) => {
if (this.props.onChange) {
this.props.onChange(values);
}
}}
label={<span title={description}>{label}</span>}
help={<div dangerouslySetInnerHTML={{ __html: replaceUrl(description || '') }} />}
>
<Switch
disabled={disableEdit}
id={switchResult.id}
onChange={switchResult.onChange}
size="medium"
checked={switchResult.value ? true : false}
/>
</Form.Item>
);
case 'Input':
return (
<Form.Item
required={required}
labelAlign={inline ? 'inset' : 'left'}
label={label}
key={param.jsonKey}
help={<div dangerouslySetInnerHTML={{ __html: replaceUrl(description || '') }} />}
>
<Input
disabled={disableEdit}
{...init(param.jsonKey, {
initValue: initValue,
rules: convertRule(param.validate),
})}
/>
</Form.Item>
);
case 'Password':
return (
<Form.Item
required={required}
labelAlign={inline ? 'inset' : 'left'}
label={label}
key={param.jsonKey}
help={<div dangerouslySetInnerHTML={{ __html: replaceUrl(description || '') }} />}
>
<Input
disabled={disableEdit}
htmlType="password"
{...init(param.jsonKey, {
initValue: initValue,
rules: convertRule(param.validate),
})}
/>
</Form.Item>
);
case 'Select':
return (
<Form.Item
required={required}
labelAlign={inline ? 'inset' : 'left'}
label={label}
key={param.jsonKey}
help={<div dangerouslySetInnerHTML={{ __html: replaceUrl(description || '') }} />}
>
<Select
disabled={disableEdit}
locale={locale.Select}
{...init(param.jsonKey, {
initValue: initValue,
rules: convertRule(param.validate),
})}
dataSource={param.validate && param.validate.options}
/>
</Form.Item>
);
case 'Number':
return (
<Form.Item
labelAlign={inline ? 'inset' : 'left'}
required={required}
label={label}
key={param.jsonKey}
help={<div dangerouslySetInnerHTML={{ __html: replaceUrl(description || '') }} />}
>
<Input
disabled={disableEdit}
{...init(param.jsonKey, {
initValue: initValue,
rules: convertRule(param.validate),
})}
htmlType="number"
/>
</Form.Item>
);
case 'ImageInput':
return (
<Form.Item
required={required}
label={label}
help={<div dangerouslySetInnerHTML={{ __html: replaceUrl(description || '') }} />}
key={param.jsonKey}
>
<ImageInput
disabled={disableEdit}
{...init(param.jsonKey, {
initValue: initValue,
rules: [
{
required: true,
pattern: checkImageName,
message: 'Please enter a valid image name',
},
],
})}
/>
</Form.Item>
);
case 'KV':
const children = (
<KV
disabled={disableEdit}
{...init(param.jsonKey, {
initValue: initValue,
rules: convertRule(param.validate),
})}
key={param.jsonKey}
additional={param.additional}
additionalParameter={param.additionalParameter}
/>
);
return getGroup(children);
case 'HelmValues':
return getGroup(
<HelmValues
disabled={disableEdit}
{...init(param.jsonKey, {
initValue: initValue,
rules: convertRule(param.validate),
})}
key={param.jsonKey}
additional={param.additional}
additionalParameter={param.additionalParameter}
/>,
);
case 'Strings':
return getGroup(
<Strings
disabled={disableEdit}
key={param.jsonKey}
{...init(param.jsonKey, {
initValue: initValue,
rules: convertRule(param.validate),
})}
/>,
);
case 'SecretSelect':
return (
<Form.Item
labelAlign={inline ? 'inset' : 'left'}
required={required}
label={label}
help={<div dangerouslySetInnerHTML={{ __html: replaceUrl(description || '') }} />}
disabled={disableEdit}
key={param.jsonKey}
>
<SecretSelect
disabled={disableEdit}
setKeys={(keys: string[]) => {
this.setState({ secretKeys: keys });
}}
{...init(param.jsonKey, {
initValue: this.props.value?.name || param.validate.defaultValue,
rules: convertRule(param.validate),
})}
/>
</Form.Item>
);
case 'SecretKeySelect':
return (
<Form.Item
required={required}
labelAlign={inline ? 'inset' : 'left'}
label={label}
help={<div dangerouslySetInnerHTML={{ __html: replaceUrl(description || '') }} />}
disabled={disableEdit}
key={param.jsonKey}
>
<SecretKeySelect
disabled={disableEdit}
secretKeys={this.state.secretKeys}
{...init(param.jsonKey, {
initValue: this.props.value?.key || param.validate.defaultValue,
rules: convertRule(param.validate),
})}
/>
</Form.Item>
);
case 'CPUNumber':
return (
<Form.Item
required={required}
label={label}
help={<div dangerouslySetInnerHTML={{ __html: replaceUrl(description || '') }} />}
disabled={disableEdit}
key={param.jsonKey}
>
<CPUNumber
disabled={disableEdit}
{...init(param.jsonKey, {
initValue: initValue,
rules: [
{
required: param.validate.required,
min: 0,
message: 'Please enter a valid cpu request number',
},
],
})}
/>
</Form.Item>
);
case 'MemoryNumber':
return (
<Form.Item
required={required}
label={label}
help={<div dangerouslySetInnerHTML={{ __html: replaceUrl(description || '') }} />}
disabled={disableEdit}
key={param.jsonKey}
>
<MemoryNumber
disabled={disableEdit}
{...init(param.jsonKey, {
initValue: initValue,
rules: [
{
required: param.validate.required,
min: 0,
message: 'Please enter a valid memory request number',
},
],
})}
/>
</Form.Item>
);
case 'DiskNumber':
return (
<Form.Item
required={required}
label={label}
help={<div dangerouslySetInnerHTML={{ __html: replaceUrl(description || '') }} />}
disabled={disableEdit}
key={param.jsonKey}
>
<DiskNumber
disabled={disableEdit}
{...init(param.jsonKey, {
initValue: initValue,
rules: [
{
required: param.validate.required,
min: 0,
message: 'Please enter a valid disk size',
},
],
})}
/>
</Form.Item>
);
case 'Group':
if (param.subParameters && param.subParameters.length > 0) {
return (
<Group
key={param.jsonKey}
hasToggleIcon
description={
<div dangerouslySetInnerHTML={{ __html: replaceUrl(description || '') }} />
}
title={label}
closed={true}
required={param.validate && param.validate.required}
field={this.form}
jsonKey={param.jsonKey || ''}
propertyValue={this.props.value}
onChange={(values) => {
if (this.props.onChange) {
this.props.onChange(values);
}
}}
>
<UISchema
{...init(param.jsonKey, {
initValue: initValue,
rules: [
{
validator: validator,
},
],
})}
registerForm={(form: Field) => {
this.onRegisterForm(param.jsonKey, form);
}}
uiSchema={param.subParameters}
mode={this.props.mode}
/>
</Group>
);
}
return <div />;
case 'Structs':
if (param.subParameters && param.subParameters.length > 0) {
return getGroup(
<Structs
key={param.jsonKey}
label={label}
param={param.subParameters}
parameterGroupOption={param.subParameterGroupOption}
registerForm={(form: Field) => {
this.onRegisterForm(param.jsonKey, form);
}}
mode={this.props.mode}
{...init(param.jsonKey, {
initValue: initValue,
rules: [
{
validator: validator,
message: `Please check ${label} config`,
},
],
})}
/>,
);
}
return <div />;
case 'Ignore':
if (param.subParameters && param.subParameters.length > 0) {
const itemCount = param.subParameters?.filter((p) => !p.disable).length || 1;
return (
<UISchema
uiSchema={param.subParameters}
registerForm={(form: Field) => {
this.onRegisterForm(param.jsonKey, form);
}}
inline={inline}
maxColSpan={24 / itemCount}
{...init(param.jsonKey, {
initValue: initValue,
rules: [
@ -451,100 +572,77 @@ class UISchema extends Component<Props, State> {
},
],
})}
registerForm={(form: Field) => {
this.onRegisterForm(param.jsonKey, form);
}}
uiSchema={param.subParameters}
mode={this.props.mode}
/>
</Group>
);
}
return <div />;
case 'InnerGroup':
return (
<InnerGroup
key={param.jsonKey}
uiSchema={param.subParameters}
title={param.label}
description={param.description}
{...init(param.jsonKey, {
initValue: initValue,
rules: converRule(param.validate),
})}
/>
);
case 'Structs':
if (param.subParameters && param.subParameters.length > 0) {
return getGroup(
<Structs
key={param.jsonKey}
param={param.subParameters}
parameterGroupOption={param.subParameterGroupOption}
registerForm={(form: Field) => {
this.onRegisterForm(param.jsonKey, form);
}}
{...init(param.jsonKey, {
initValue: initValue,
rules: [
{
validator: validator,
message: `Please check ${param.label} config`,
},
],
})}
/>,
);
}
return <div />;
case 'Ignore':
if (param.subParameters && param.subParameters.length > 0) {
);
}
return <div />;
case 'K8sObjectsCode':
return (
<UISchema
<Form.Item
required={required}
label={label}
help={<div dangerouslySetInnerHTML={{ __html: replaceUrl(description || '') }} />}
disabled={disableEdit}
key={param.jsonKey}
uiSchema={param.subParameters}
registerForm={(form: Field) => {
this.onRegisterForm(param.jsonKey, form);
}}
inline={inline}
{...init(param.jsonKey, {
initValue: initValue,
rules: [
{
validator: validator,
},
],
})}
/>
>
<K8sObjectsCode
{...init(param.jsonKey, {
initValue: initValue,
rules: [
{
required: param.validate.required,
message: 'Please enter a valid kubernetes resource yaml code',
},
],
})}
/>
</Form.Item>
);
}
return <div />;
case 'K8sObjectsCode':
return (
<Form.Item
required={required}
label={param.label}
help={param.description}
disabled={param.disable}
key={param.jsonKey}
>
<K8sObjectsCode
{...init(param.jsonKey, {
initValue: initValue,
rules: [
{
required: param.validate.required,
message: 'Please enter a valid kubernetes resource yaml code',
},
],
})}
/>
</Form.Item>
);
}
};
let colSpan = 24;
if (maxColSpan) {
colSpan = maxColSpan;
if (maxColSpan * uiSchema.length < 24) {
}
}
if (param.style?.colSpan) {
colSpan = param.style?.colSpan;
}
return (
<Col key={param.jsonKey} span={colSpan} style={{ padding: '0 4px' }}>
{item()}
</Col>
);
});
const formItemLayout = {
labelCol: {
fixedSpan: 10,
},
wrapperCol: {
span: 14,
},
};
return (
<Form field={this.form} className="ui-schema-container">
{items}
<If condition={disableRenderRow}>{items}</If>
<If condition={!disableRenderRow}>
<Row wrap={true}>{items}</Row>
<If condition={onlyShowRequired}>
<Divider />
<Form {...formItemLayout} style={{ width: '100%' }} fullWidth={true}>
<Form.Item
labelAlign="left"
colon={true}
label={<Translation>Advanced Parameters</Translation>}
labelWidth="200px"
>
<Switch onChange={this.onChangeAdvanced} size="small" checked={advanced} />
</Form.Item>
</Form>
</If>
</If>
</Form>
);
}

View File

@ -0,0 +1,49 @@
@primaryColor: #0064c8;
@whiteColor: white;
@blackColor: black;
.spection-group-container {
padding: 8px;
border: 1px solid #fff;
border-radius: 4px;
visibility: visible;
&:hover {
border: 1px solid @primaryColor;
}
.spection-group-title-container {
position: relative;
padding: 0 10px;
font-size: 14px;
background-color: transparent;
outline: none;
cursor: pointer;
transition: border-color 0.12s;
.icon-toggle {
position: absolute;
top: -2px;
right: 10px;
}
.icon-delete {
position: absolute;
right: 30px;
}
.next-icon-arrow-up {
&::before {
font-size: 12px !important;
}
}
}
.array-item-group-box {
padding: 16px;
background-color: #fff;
border: 1px solid #eee;
border-radius: 5px;
transition: height 0.3s linear;
}
.array-item-group-box.disable {
height: 0;
margin: 0;
padding: 0;
overflow: hidden;
visibility: hidden;
}
}

View File

@ -0,0 +1,71 @@
import React from 'react';
import { Icon, Loading, Grid } from '@b-design/ui';
import './index.less';
type Props = {
id: string;
children?: React.ReactNode;
loading?: boolean;
labelTitle: string | React.ReactElement;
delete: (id: string) => void;
};
type State = {
closed: boolean | undefined;
};
class ArrayItemGroup extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
closed: true,
};
}
toggleShowClass = () => {
const { closed } = this.state;
this.setState({
closed: !closed,
});
};
render() {
const { children, labelTitle, loading } = this.props;
const { closed } = this.state;
const { Col, Row } = Grid;
return (
<Loading visible={loading || false} style={{ width: '100%' }}>
<div className="spection-group-container">
<div className="spection-group-title-container">
<Row>
<Col span={'21'}>{labelTitle}</Col>
<Col span={'3'}>
<div>
<Icon
onClick={this.toggleShowClass}
className="icon-toggle"
type={closed ? 'arrow-down' : 'arrow-up'}
style={closed ? { top: '-2px' } : { top: '0' }}
/>
<Icon
type="delete"
size={'small'}
className="icon-delete"
onClick={() => {
if (this.props.delete) {
this.props.delete(this.props.id);
}
}}
/>
</div>
</Col>
</Row>
</div>
<div className={`array-item-group-box ${closed ? 'disable' : ''}`}>{children}</div>
</div>
</Loading>
);
}
}
export default ArrayItemGroup;

View File

@ -5,6 +5,7 @@ type Props = {
value: any;
id: string;
onChange: (value: any) => void;
disabled: boolean;
};
type State = {};
@ -27,7 +28,7 @@ class CPUNumber extends React.Component<Props, State> {
};
render() {
const { value, id } = this.props;
const { value, id, disabled } = this.props;
let initValue = undefined;
if (value) {
initValue = parseFloat(value);
@ -36,6 +37,7 @@ class CPUNumber extends React.Component<Props, State> {
return (
<Input
id={id}
disabled={disabled}
addonTextAfter="Core"
htmlType="number"
onChange={this.onChange}

View File

@ -0,0 +1,46 @@
import React from 'react';
import { Input } from '@b-design/ui';
type Props = {
id: string;
onChange: (value: any) => void;
value: any;
disabled: boolean;
};
type State = {};
class DiskNumber extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {};
}
onChange = (value: string) => {
const { onChange } = this.props;
if (onChange) {
onChange(value + 'Gi');
}
};
render() {
const { value, id, disabled } = this.props;
let initValue = undefined;
if (value) {
initValue = parseInt(value.replace('Gi', ''));
}
return (
<Input
id={id}
disabled={disabled}
min="0"
addonTextAfter="Gi"
htmlType="number"
onChange={this.onChange}
value={initValue}
/>
);
}
}
export default DiskNumber;

View File

@ -18,7 +18,12 @@
outline: none;
cursor: pointer;
transition: border-color 0.12s;
.group-title.required::before {
margin-right: 4px;
color: #c80000;
color: var(--form-error-color, #c80000);
content: '*';
}
.group-title-desc {
color: #a6a6a6;
font-weight: normal;
@ -35,11 +40,8 @@
}
}
.group-box {
position: relative;
margin-top: 10px;
margin-bottom: 10px;
padding: 20px;
background-color: #eee;
padding: 16px;
background-color: #fff;
border-radius: 5px;
transition: height 0.3s linear;
}

View File

@ -83,14 +83,13 @@ class Group extends React.Component<Props, State> {
disableAddon = false,
} = this.props;
const { closed, enable, checked } = this.state;
return (
<Loading visible={loading || false} style={{ width: '100%' }}>
<div className="group-container">
<div className="group-title-container">
<Row>
<Col span={'21'}>
{title}
<span className={`group-title ${required && 'required'}`}>{title}</span>
<div className="group-title-desc">{description}</div>
</Col>
<Col span={'3'} className="flexcenter">
@ -108,7 +107,7 @@ class Group extends React.Component<Props, State> {
type: 'confirm',
content: (
<Translation>
If Swtich is turned off, The configuration will be reset. Are you sure
If Switch is turned off, The configuration will be reset. Are you sure
you want to do this?
</Translation>
),
@ -132,11 +131,8 @@ class Group extends React.Component<Props, State> {
</Col>
</Row>
</div>
<If condition={enable && (!hasToggleIcon || !closed)}>
<div className="group-box">{children}</div>
</If>
<If condition={closed && enable}>
<div className="group-box disable">{children}</div>
<If condition={enable}>
<div className={`group-box ${closed ? 'disable' : ''}`}>{children}</div>
</If>
</div>
</Loading>

View File

@ -11,6 +11,7 @@ type Props = {
additionalParameter?: UIParam;
subParameters?: UIParam[];
id: string;
disabled: boolean;
};
function setValues(target: any, value: any, key: string, keys: string[]) {
@ -65,6 +66,7 @@ class HelmValues extends Component<Props> {
render() {
return (
<KV
disabled={this.props.disabled}
onChange={this.onChange}
value={this.renderValue()}
additional={this.props.additional}

View File

@ -2,12 +2,13 @@ import React from 'react';
import { Input } from '@b-design/ui';
import './index.less';
import { InputProps } from '@alifd/next/types/input';
import type { InputProps } from '@alifd/next/types/input';
interface Props extends InputProps {
value: any;
id: string;
onChange: (value: any) => void;
disabled: boolean;
}
type State = {};

View File

@ -1,13 +0,0 @@
.group-inner-container {
padding: 5px;
border-radius: 5px;
.ui-schema-container {
display: flex;
.next-select {
width: 100%;
}
.next-input {
width: 100%;
}
}
}

View File

@ -1,44 +0,0 @@
import React from 'react';
import type { UIParam } from '../../interface/application';
import UISchema from '../../components/UISchema';
import './index.less';
type Props = {
_key?: string;
title: string | React.ReactNode;
description?: string | React.ReactNode;
children?: React.ReactNode;
uiSchema: UIParam[] | undefined;
onChange?: (params: any) => void;
value: any;
};
type State = {};
class InnerGroup extends React.Component<Props, State> {
dom: any;
ref: React.RefObject<UISchema>;
constructor(props: Props) {
super(props);
this.state = {};
this.ref = React.createRef();
}
render() {
const { uiSchema, onChange, value } = this.props;
return (
<div className="group-inner-container">
<UISchema
key={value}
ref={this.ref}
uiSchema={uiSchema}
value={value}
inline
onChange={onChange}
/>
</div>
);
}
}
export default InnerGroup;

View File

@ -13,6 +13,7 @@ type Props = {
additionalParameter?: UIParam;
subParameters?: UIParam[];
id: string;
disabled: boolean;
};
type State = {
@ -136,6 +137,7 @@ class KV extends Component<Props, State> {
<Col span={10}>
<Form.Item>
<Input
disabled={this.props.disabled}
{...init(`envKey-${item.key}`)}
label={'Key'}
className="full-width"
@ -146,6 +148,7 @@ class KV extends Component<Props, State> {
<Col span={10}>
<Form.Item>
<Input
disabled={this.props.disabled}
htmlType={valueTypeNumber ? 'number' : ''}
{...init(`envValue-${item.key}`)}
label={'Value'}
@ -163,7 +166,12 @@ class KV extends Component<Props, State> {
);
})}
<div className="mb-20 flexright">
<Button size="small" type="secondary" onClick={this.addItem.bind(this)}>
<Button
disabled={this.props.disabled}
size="small"
type="secondary"
onClick={this.addItem.bind(this)}
>
Add
</Button>
</div>

View File

@ -5,6 +5,7 @@ type Props = {
id: string;
onChange: (value: any) => void;
value: any;
disabled: boolean;
};
type State = {};
@ -23,7 +24,7 @@ class MemoryNumber extends React.Component<Props, State> {
};
render() {
const { value, id } = this.props;
const { value, id, disabled } = this.props;
let initValue = undefined;
if (value) {
initValue = parseInt(value.replace('Mi', ''));
@ -32,6 +33,7 @@ class MemoryNumber extends React.Component<Props, State> {
<Input
id={id}
min="0"
disabled={disabled}
addonTextAfter="MB"
htmlType="number"
onChange={this.onChange}

View File

@ -1,12 +1,14 @@
import React from 'react';
import { Select } from '@b-design/ui';
import locale from '../../utils/locale';
import i18n from 'i18next';
type Props = {
onChange: (value: any) => void;
secretKeys?: string[];
value: any;
id: string;
disabled: boolean;
};
type State = {};
@ -22,9 +24,17 @@ class SecretKeySelect extends React.Component<Props, State> {
componentDidMount = async () => {};
render() {
const { onChange, value, secretKeys, id } = this.props;
const { onChange, value, secretKeys, id, disabled } = this.props;
return (
<Select locale={locale.Select} onChange={onChange} defaultValue={value} id={id} value={value}>
<Select
locale={locale.Select}
onChange={onChange}
defaultValue={value}
id={id}
disabled={disabled}
value={value}
placeholder={i18n.t('Please select the secret key').toString()}
>
{secretKeys?.map((item) => {
return (
<Select.Option key={item} value={item}>

View File

@ -4,6 +4,7 @@ import React from 'react';
import { listCloudResourceSecrets } from '../../api/observation';
import type { Secret } from '../../interface/observation';
import locale from '../../utils/locale';
import i18n from 'i18next';
type Props = {
onChange: (value: any) => void;
@ -11,6 +12,7 @@ type Props = {
value: any;
id: string;
appNamespace?: string;
disabled: boolean;
};
type State = {
@ -58,11 +60,18 @@ class SecretSelect extends React.Component<Props, State> {
}
};
render() {
const { value, id } = this.props;
const { value, id, disabled } = this.props;
const { secrets } = this.state;
const filters = secrets?.filter((secret) => secret.metadata.labels['app.oam.dev/sync-alias']);
return (
<Select locale={locale.Select} onChange={this.onChange} value={value} id={id}>
<Select
locale={locale.Select}
onChange={this.onChange}
value={value}
id={id}
disabled={disabled}
placeholder={i18n.t('Please select the secret').toString()}
>
{filters?.map((secret) => {
return (
<Select.Option

View File

@ -9,6 +9,7 @@ type Props = {
value: any;
id: string;
onChange: (value: any) => void;
disabled: boolean;
};
type InputParams = {
@ -19,6 +20,7 @@ type InputParams = {
value?: string;
onChange: () => {};
delete: (key: string) => {};
disabled: boolean;
};
type ListParams = {
@ -38,6 +40,7 @@ function InputItem(props: InputParams) {
onChange={props.onChange}
addonBefore={''}
className="input"
disabled={props.disabled}
value={props.value}
/>
<div className="remove-option-container">
@ -128,7 +131,7 @@ class Strings extends React.Component<Props, State> {
render() {
const { inputList } = this.state;
const { init } = this.field;
const { label } = this.props;
const { label, disabled } = this.props;
return (
<div className="strings-container">
{inputList.map((item) => (
@ -142,7 +145,7 @@ class Strings extends React.Component<Props, State> {
/>
))}
<div className="add-btn-container">
<Button size="small" onClick={this.addInputItem} ghost="light">
<Button disabled={disabled} size="small" onClick={this.addInputItem} ghost="light">
<Translation>Add</Translation>
</Button>
</div>

View File

@ -5,15 +5,13 @@
align-items: center;
margin-bottom: 8px;
padding: 10px 20px;
background-color: #e2e2e2;
border-radius: 24px;
background-color: #fff;
border: 1px solid #e2e2e2;
border-radius: 4px;
.struct-item-content {
width: 100%;
}
.struct-item-content > .next-form {
display: flex !important;
flex: 1 1 auto;
align-items: center;
.next-form-item {
display: flex;
margin-right: 12px;
@ -27,7 +25,7 @@
margin-right: 0;
}
.next-form-item-label {
flex: 0 0 55px;
flex: 0 0 85px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;

View File

@ -3,6 +3,7 @@ import { Form, Icon, Field, Button } from '@b-design/ui';
import { If } from 'tsx-control-statements/components';
import type { UIParam, GroupOption } from '../../interface/application';
import UISchema from '../../components/UISchema';
import ArrayItemGroup from '../ArrayItemGroup';
import type { Rule } from '@alifd/field';
import './index.less';
@ -14,6 +15,8 @@ type Props = {
registerForm: (form: Field) => void;
id: string;
value: any;
label: string;
mode: 'new' | 'edit';
};
type State = {
@ -25,8 +28,11 @@ type StructItemProps = {
param?: UIParam[];
id: string;
init: any;
labelTitle: string | React.ReactElement;
delete: (id: string) => void;
mode: 'new' | 'edit';
};
class StructItem extends React.Component<StructItemProps> {
uiRef: React.RefObject<UISchema>;
constructor(props: StructItemProps) {
@ -39,8 +45,23 @@ class StructItem extends React.Component<StructItemProps> {
validator = (rule: Rule, value: any, callback: (error?: string) => void) => {
this.uiRef.current?.validate(callback);
};
getParamCount = (params: UIParam[] | undefined) => {
let count = 0;
if (!params && !Array.isArray(params)) {
return count;
}
params.map((p) => {
if (!p.disable && p.uiType != 'Ignore' && p.uiType != 'InnerGroup') {
count += 1;
}
if (!p.disable && p.subParameters) {
count += this.getParamCount(p.subParameters);
}
});
return count;
};
render() {
const { option, param, id, init } = this.props;
const { option, param, id, init, labelTitle } = this.props;
let uiSchemas = param;
if (option && option.length > 0) {
const paramMap =
@ -51,34 +72,65 @@ class StructItem extends React.Component<StructItemProps> {
}, {});
uiSchemas = option.map((key: string) => paramMap[key]);
}
const paramCount = this.getParamCount(uiSchemas);
const itemCount = uiSchemas?.filter((p) => !p.disable).length || 1;
return (
<div className="struct-item-container">
<div className="struct-item-content">
<UISchema
{...init(`struct${id}`, {
rules: [
{
validator: this.validator,
message: 'please check config item',
},
],
})}
uiSchema={uiSchemas}
inline
ref={this.uiRef}
/>
</div>
<div className="remove-option-container">
<Icon
type="ashbin"
onClick={() => {
if (this.props.delete) {
this.props.delete(this.props.id);
}
}}
/>
</div>
<If condition={paramCount > 3}>
<div className="struct-item-content">
<ArrayItemGroup
id={id}
labelTitle={labelTitle}
delete={(structId: string) => {
this.props.delete(structId);
}}
>
<UISchema
{...init(`struct${id}`, {
rules: [
{
validator: this.validator,
message: 'please check config item',
},
],
})}
uiSchema={uiSchemas}
inline
ref={this.uiRef}
mode={this.props.mode}
/>
</ArrayItemGroup>
</div>
</If>
<If condition={paramCount <= 3}>
<div className="struct-item-content">
<UISchema
{...init(`struct${id}`, {
rules: [
{
validator: this.validator,
message: 'please check config item',
},
],
})}
uiSchema={uiSchemas}
maxColSpan={24 / itemCount}
inline
ref={this.uiRef}
mode={this.props.mode}
/>
</div>
<div className="remove-option-container">
<Icon
type="ashbin"
onClick={() => {
if (this.props.delete) {
this.props.delete(this.props.id);
}
}}
/>
</div>
</If>
</div>
);
}
@ -107,9 +159,13 @@ class Structs extends React.Component<Props, State> {
const { value, parameterGroupOption } = this.props;
if (value) {
const keyMap = new Map();
let firstOption: GroupOption | undefined = undefined;
if (parameterGroupOption) {
parameterGroupOption.map((item) => {
if (item && item.keys) {
if (!firstOption) {
firstOption = item;
}
keyMap.set(item.keys.sort().join(), item);
}
});
@ -124,7 +180,7 @@ class Structs extends React.Component<Props, State> {
const option = keyMap.get(valueKeys.sort().join());
structList.push({
key,
option: option?.keys,
option: option?.keys || firstOption?.keys,
value: value,
});
this.field.setValue('struct' + key, item);
@ -179,22 +235,36 @@ class Structs extends React.Component<Props, State> {
render() {
const { structList } = this.state;
const { param, parameterGroupOption = [] } = this.props;
const { param, parameterGroupOption = [], label } = this.props;
const { init } = this.field;
return (
<div className="struct-plan-container">
<div className="struct-plan-group">
<Form field={this.field}>
{structList.map((struct: any) => (
<StructItem
delete={this.removeStructPlanItem}
id={struct.key}
key={struct.key}
init={init}
option={struct.option}
param={param}
/>
))}
{structList.map((struct: any) => {
const fieldObj: any = this.field.getValues();
const name = fieldObj[`struct${struct.key}`]?.name || '';
let labelTitle: string | React.ReactElement = label;
if (name) {
labelTitle = (
<span>
{label}: <span style={{ marginLeft: '8px' }}>{name}</span>{' '}
</span>
);
}
return (
<StructItem
delete={this.removeStructPlanItem}
id={struct.key}
key={struct.key}
init={init}
option={struct.option}
param={param}
labelTitle={labelTitle}
mode={this.props.mode}
/>
);
})}
</Form>
</div>
<div className="struct-plan-option">

View File

@ -8,11 +8,12 @@ export interface Addon {
detail?: string;
tags?: string[];
createTime?: string;
deployTo?: { controlPlane: boolean; runtimeCluster: boolean };
deployTo?: { controlPlane: boolean; runtimeCluster: boolean; runtime_cluster: boolean };
dependencies?: { name: string }[];
definitions?: Definition[];
uiSchema?: UIParam[];
registryName?: string;
url?: string;
}
export interface AddonStatus {

View File

@ -57,6 +57,9 @@ export interface UIParam {
label: string;
sort: number;
uiType: string;
style?: {
colSpan: number;
};
disable?: boolean;
subParameterGroupOption?: GroupOption[];
additional?: boolean;
@ -79,6 +82,7 @@ export interface UIParamValidate {
pattern?: string;
defaultValue?: any;
options?: { label: string; value: string }[];
immutable?: boolean;
}
export interface ImageInfo {
@ -209,7 +213,7 @@ export interface Revisions {
export interface ApplicationStatistics {
envCount: number;
targetCount: number;
revisonCount: number;
revisionCount: number;
workflowCount: number;
}
@ -260,3 +264,10 @@ export interface Trigger {
createTime?: string;
updateTime?: string;
}
export interface WorkflowStep {
name: string;
alias: string;
description?: string;
type: string;
}

View File

@ -0,0 +1,20 @@
.deployConfig {
min-height: auto;
.next-radio-group {
width: 100%;
.next-radio-wrapper {
width: 100%;
margin-bottom: 8px;
padding: 16px 32px;
border: 1px solid #c0c6cc !important;
border-radius: 4px;
cursor: pointer;
.env {
float: right;
}
}
.next-radio-wrapper:hover {
border: 1px solid #0064c8 !important;
}
}
}

View File

@ -0,0 +1,89 @@
import React, { Component } from 'react';
import { withTranslation } from 'react-i18next';
import { Dialog, Radio } from '@b-design/ui';
import type { Workflow } from '../../../../interface/application';
import Translation from '../../../../components/Translation';
import locale from '../../../../utils/locale';
import './index.less';
const { Group: RadioGroup } = Radio;
interface Props {
onClose: () => void;
onOK: (workflowName?: string, force?: boolean) => void;
workflows?: Workflow[];
}
interface State {
loading: boolean;
workflowName: string;
}
class DeployConfigDialog extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
loading: false,
workflowName: '',
};
}
componentDidMount() {
const { workflows } = this.props;
workflows?.map((w) => {
if (w.default) {
this.setState({ workflowName: w.name });
}
});
}
onSubmit = () => {
if (this.state.workflowName) {
this.props.onOK(this.state.workflowName);
this.props.onClose();
}
};
onChange = (name: any) => {
this.setState({ workflowName: name });
};
render() {
const { workflows, onClose } = this.props;
const { workflowName } = this.state;
return (
<React.Fragment>
<Dialog
visible={true}
locale={locale.Dialog}
className={'commonDialog deployConfig'}
style={{ width: '600px' }}
isFullScreen={true}
footerActions={['cancel', 'ok']}
onClose={onClose}
onCancel={onClose}
onOk={this.onSubmit}
title={<Translation>Select Workflow</Translation>}
>
<RadioGroup value={workflowName} onChange={this.onChange}>
{workflows?.map((workflow) => {
return (
<Radio
id={workflow.name}
value={workflow.name}
onClick={() => {
this.onChange(workflow.name);
}}
>
{workflow.alias ? workflow.alias : workflow.name}
<span className="env">Env: {workflow.envName}</span>
</Radio>
);
})}
</RadioGroup>
</Dialog>
</React.Fragment>
);
}
}
export default withTranslation()(DeployConfigDialog);

View File

@ -31,6 +31,7 @@
.item {
color: #1b58f4;
}
border-color: #1b58f4 !important;
border-bottom: none !important;
}

View File

@ -1,14 +1,4 @@
import {
Grid,
Card,
Breadcrumb,
Button,
Message,
Icon,
Dropdown,
Menu,
Dialog,
} from '@b-design/ui';
import { Grid, Card, Breadcrumb, Button, Message, Dialog } from '@b-design/ui';
import { connect } from 'dva';
import React, { Component } from 'react';
import Translation from '../../../../components/Translation';
@ -32,6 +22,7 @@ import WorkflowSilder from '../WorkflowSilder';
import { If } from 'tsx-control-statements/components';
import Empty from '../../../../components/Empty';
import locale from '../../../../utils/locale';
import DeployConfig from '../DeployConfig';
const { Row, Col } = Grid;
@ -47,6 +38,7 @@ interface State {
loading: boolean;
statistics?: ApplicationStatistics;
records?: WorkflowBase[];
showDeployConfig: boolean;
}
@connect((store: any) => {
@ -59,10 +51,15 @@ class ApplicationHeader extends Component<Props, State> {
super(props);
this.state = {
loading: true,
showDeployConfig: false,
};
this.sync = true;
}
onDeployConfig = () => {
this.setState({ showDeployConfig: true });
};
onDeploy = (workflowName?: string, force?: boolean) => {
const { applicationDetail } = this.props;
if (applicationDetail) {
@ -140,7 +137,7 @@ class ApplicationHeader extends Component<Props, State> {
render() {
const { applicationDetail, currentPath, workflows, appName } = this.props;
const { statistics, records } = this.state;
const { statistics, records, showDeployConfig } = this.state;
const activeKey = currentPath.substring(currentPath.lastIndexOf('/') + 1);
const item = <Translation>{`app-${activeKey}`}</Translation>;
return (
@ -164,31 +161,13 @@ class ApplicationHeader extends Component<Props, State> {
type="notice"
title="Application configuration changes take effect only after deploy."
/>
<Button style={{ marginLeft: '16px' }} type="primary" onClick={() => this.onDeploy()}>
<Button
style={{ marginLeft: '16px' }}
type="primary"
onClick={() => this.onDeployConfig()}
>
<Translation>Deploy</Translation>
</Button>
<Dropdown
trigger={
<Button type="primary">
<Icon type="ellipsis-vertical" />
</Button>
}
>
<Menu>
{workflows?.map((workflow) => {
return (
<Menu.Item
onClick={() => {
this.onDeploy(workflow.name);
}}
key={workflow.name}
>
<Translation>Execute Workflow</Translation> {workflow.alias || workflow.name}
</Menu.Item>
);
})}
</Menu>
</Dropdown>
</Col>
</Row>
<Row wrap={true}>
@ -199,10 +178,10 @@ class ApplicationHeader extends Component<Props, State> {
<NumItem number={statistics?.envCount} title={'Env Count'} />
</Col>
<Col span={6} style={{ padding: '22px 0' }}>
<NumItem number={statistics?.targetCount} title={'target Count'} />
<NumItem number={statistics?.targetCount} title={'Target Count'} />
</Col>
<Col span={6} style={{ padding: '22px 0' }}>
<NumItem number={statistics?.revisonCount} title={'Revision Count'} />
<NumItem number={statistics?.revisionCount} title={'Revision Count'} />
</Col>
<Col span={6} style={{ padding: '22px 0' }}>
<NumItem number={statistics?.workflowCount} title={'Workflow Count'} />
@ -224,6 +203,15 @@ class ApplicationHeader extends Component<Props, State> {
</If>
</Col>
</Row>
<If condition={showDeployConfig}>
<DeployConfig
onClose={() => {
this.setState({ showDeployConfig: false });
}}
onOK={this.onDeploy}
workflows={workflows}
/>
</If>
</div>
);
}

View File

@ -90,3 +90,9 @@ a:hover {
}
}
}
.bottom {
position: absolute;
bottom: 10px;
padding-left: 32px;
}

View File

@ -5,6 +5,7 @@ import { Icon } from '@b-design/ui';
import Translation from '../../components/Translation';
import { getLeftSider } from './menu';
import './index.less';
import { version } from '../../../package.json';
const LeftMenu = (props: any) => {
const pathname = _.get(props, 'props.history.location.pathname');
@ -45,6 +46,9 @@ const LeftMenu = (props: any) => {
<div style={{ position: 'relative', height: '100%' }}>
<div className="slide-wraper">
<ul className="ul-wraper">{childrenSider}</ul>
<div className="bottom">
<div className="nav-container">Version {version}</div>
</div>
</div>
</div>
);

View File

@ -48,7 +48,7 @@ class TopBar extends Component<Props, State> {
}
componentDidMount() {
this.loadSystemInfo();
//this.loadSystemInfo();
}
loadSystemInfo = () => {
@ -183,13 +183,13 @@ class TopBar extends Component<Props, State> {
<Icon size={14} type="help1" />
</a>
</div>
<div className="vela-item">
{/* <div className="vela-item">
<Icon
onClick={this.showUserExperienceImprovementPlan}
size={14}
type="exclamation-circle"
/>
</div>
</div> */}
</div>
</Row>
<Dialog

View File

@ -63,7 +63,7 @@
.layout-navigation {
width: 240px;
min-width: 60px;
min-height: 100vh;
min-height: calc(100vh - 48px);
padding-top: 22px;
background-color: #252525;
}
@ -71,7 +71,7 @@
.layout-content {
flex: 1;
width: calc(100% - 240px);
min-height: 100vh;
min-height: calc(100vh - 48px);
padding: 15px;
background-color: #f7f7f7;
}

View File

@ -180,5 +180,7 @@
"Needs review before continuing": "需求审核后才能继续执行",
"Please select the action you want to perform": "请选择您期望的动作执行?",
"Continue": "继续",
"Termination": "终止"
}
"Termination": "终止",
"Container Image": "容器镜像",
"Which image would you like to use for your service": "请在部署之前完成容器镜像准备并上传到镜像仓库中。"
}

View File

@ -105,29 +105,34 @@ export default {
workflow.default = option.default;
const { nodes, edges } = data;
const nodeIndex = {};
let index = 0;
const steps = Object.keys(nodes).map((key) => {
Object.keys(nodes).map((key) => {
if (nodes[key]) {
nodeIndex[nodes[key].id] = index;
index++;
const nodeConfig = nodes[key].consumerData;
let nodeConfig = Object.assign({}, nodes[key].consumerData);
if (nodeConfig && nodeConfig.properties && typeof nodeConfig.properties != 'string') {
return Object.assign(nodeConfig, { properties: JSON.stringify(nodeConfig.properties) });
nodeConfig = Object.assign(nodeConfig, {
properties: JSON.stringify(nodeConfig.properties),
});
}
return nodeConfig;
nodeIndex[nodes[key].id] = nodeConfig;
}
});
let next = edges.prev;
let steps = [];
let srcMap = {};
Object.keys(edges).map((key) => {
const edge = edges[key];
if (nodeIndex[edge.src] > nodeIndex[edge.dest]) {
const i = steps[nodeIndex[edge.src]];
const oldIndex = nodeIndex[edge.src];
steps[nodeIndex[edge.src]] = steps[nodeIndex[edge.dest]];
steps[nodeIndex[edge.dest]] = i;
nodeIndex[edge.src] = nodeIndex[edge.dest];
nodeIndex[edge.dest] = oldIndex;
if (edges[key].src == 'prev') {
next = edges[key];
}
srcMap[edges[key].src] = edges[key];
});
while (next != undefined) {
if (next.dest && next.dest != 'next') {
steps.push(nodeIndex[next.dest]);
next = srcMap[next.dest];
} else {
next = undefined;
}
}
workflow.steps = steps;
yield call(createWorkFlow, workflow);
if (action.callback) {
@ -141,7 +146,7 @@ function transData(workflowList = [], appName) {
const newWorkflows = _.cloneDeep(workflowList);
if (newWorkflows && newWorkflows.length != 0) {
newWorkflows.forEach((workflow) => {
convertWorkflowStep(workflow, appName, 32);
convertWorkflowStep(workflow, appName, 0);
});
return newWorkflows;
} else {
@ -149,18 +154,39 @@ function transData(workflowList = [], appName) {
}
}
export function convertWorkflowStep(workflow, appName, initPosition = 32) {
const nodeWidth = 140;
const nodeHeight = 40;
const nodeInterval = 80;
export function convertWorkflowStep(workflow, appName, initPosition = 32, edit = false) {
const nodeWidth = 200;
const nodeHeight = 80;
const nodeInterval = 120;
const nodes = {};
const edges = {};
let position = initPosition;
if (edit) {
nodes.prev = {
id: 'prev',
typeId: 'prev',
diagramMakerData: {
position: {
x: position,
y: 130,
},
size: {
width: nodeWidth,
height: nodeHeight,
},
selected: false,
},
};
position += nodeWidth + nodeInterval;
}
if (workflow.steps) {
workflow.steps.forEach((item, index, array) => {
edges[item.name] = {};
edges[item.name].dest =
workflow.steps && workflow.steps[index + 1] && workflow.steps[index + 1].name;
if (workflow.steps && workflow.steps[index + 1]) {
edges[item.name].dest = workflow.steps[index + 1].name;
} else if (edit) {
edges[item.name].dest = 'next';
}
edges[item.name].diagramMakerData = {
selected: false,
};
@ -169,7 +195,7 @@ export function convertWorkflowStep(workflow, appName, initPosition = 32) {
nodes[item.name] = {};
nodes[item.name].id = item.name;
nodes[item.name].typeId = item.type;
nodes[item.name].typeId = 'common';
nodes[item.name].consumerData = {
alias: item.alias || '',
dependsOn: null,
@ -191,8 +217,32 @@ export function convertWorkflowStep(workflow, appName, initPosition = 32) {
};
position += nodeWidth + nodeInterval;
});
edges.prev = {
dest: workflow.steps[0].name,
src: 'prev',
id: 'prev',
diagramMakerData: {
selected: false,
},
};
}
if (edit) {
nodes.next = {
id: 'next',
typeId: 'next',
diagramMakerData: {
position: {
x: position,
y: 130,
},
size: {
width: nodeWidth,
height: nodeHeight,
},
selected: false,
},
};
}
workflow.appName = appName;
workflow.option = {
edit: false,

View File

@ -41,6 +41,19 @@ class CardContent extends React.Component<Props, State> {
render() {
const { Row, Col } = Grid;
const { addonLists, clickAddon, enabledAddons } = this.props;
const getTagColor = (tag: string) => {
switch (tag) {
case 'alpha':
return 'red';
case 'beta':
return 'red';
case 'GA':
return 'green';
default:
return '';
}
};
return (
<div>
<If condition={addonLists}>
@ -82,7 +95,15 @@ class CardContent extends React.Component<Props, State> {
<If condition={tags}>
<Row className="content-main-btn">
{tags?.map((tag: string) => {
return <Tag key={tag}>{tag}</Tag>;
return (
<Tag
style={{ marginRight: '8px' }}
color={getTagColor(tag)}
key={tag}
>
{tag}
</Tag>
);
})}
</Row>
</If>

View File

@ -38,6 +38,7 @@ type State = {
args?: any;
addonsStatus?: ApplicationStatus;
showStatusVisible: boolean;
mode: 'new' | 'edit';
};
class AddonDetailDialog extends React.Component<Props, State> {
@ -57,6 +58,7 @@ class AddonDetailDialog extends React.Component<Props, State> {
statusLoading: true,
upgradeLoading: false,
showStatusVisible: false,
mode: 'new',
};
this.form = new Field(this);
this.uiSchemaRef = React.createRef();
@ -98,6 +100,7 @@ class AddonDetailDialog extends React.Component<Props, State> {
}
if (res.args) {
this.form.setValue('properties', res.args);
this.setState({ mode: 'edit' });
}
this.setState({
status: res.phase,
@ -292,6 +295,7 @@ class AddonDetailDialog extends React.Component<Props, State> {
})}
ref={this.uiSchemaRef}
uiSchema={addonDetailInfo.uiSchema}
mode={this.state.mode}
/>
</Form>
</Group>

View File

@ -157,6 +157,7 @@ class EditProperties extends React.Component<Props, State> {
})}
uiSchema={definitionDetail && definitionDetail.uiSchema}
ref={this.uiSchemaRef}
mode="edit"
/>
</FormItem>
</If>

View File

@ -105,7 +105,7 @@ class TraitDialog extends React.Component<Props, State> {
transTraitDefinitions() {
const { traitDefinitions } = this.props;
return (traitDefinitions || []).map((item: { name: string }) => ({
lable: item.name,
label: item.name,
value: item.name,
}));
}
@ -249,6 +249,7 @@ class TraitDialog extends React.Component<Props, State> {
})}
uiSchema={definitionDetail && definitionDetail.uiSchema}
ref={this.uiSchemaRef}
mode={this.props.isEditTrait ? 'edit' : 'new'}
/>
</FormItem>
</If>

View File

@ -150,7 +150,17 @@ class PodDetail extends React.Component<Props, State> {
const percent = Number(useMemory).valueOf() / Number(requestMemory).valueOf();
return <Progress size="small" percent={percent * 100} />;
}
return <span>{record.resources?.usage?.memory}</span>;
if (record.resources?.usage?.memory) {
return <span>{record.resources?.usage?.memory}</span>;
}
if (record.resources?.requests?.memory) {
return (
<span>
<Translation>Request</Translation>: {record.resources?.requests?.memory}
</span>
);
}
return <span />;
},
},
{
@ -164,7 +174,17 @@ class PodDetail extends React.Component<Props, State> {
const percent = Number(usecpu).valueOf() / Number(requestcpu).valueOf();
return <Progress size="small" percent={percent * 100} />;
}
return <span>{record.resources?.usage?.cpu}</span>;
if (record.resources?.usage?.cpu) {
return <span>{record.resources?.usage?.cpu}</span>;
}
if (record.resources?.requests?.cpu) {
return (
<span>
<Translation>Request</Translation>: {record.resources?.requests?.cpu}
</span>
);
}
return <span />;
},
},
{

View File

@ -487,7 +487,7 @@ class ApplicationInstanceList extends React.Component<Props, State> {
}
if ((protocol === 'https' && port == 443) || (protocol === 'http' && port === 80)) {
port = '';
}else{
} else {
port = ':' + port;
}
return protocol + '://' + host + port + path;

View File

@ -44,6 +44,10 @@ type State = {
};
type Callback = (envName: string) => void;
type SelectGroupType = {
label: string;
children: { label: string; value: string }[];
}[];
@connect((store: any) => {
return { ...store.clusters };
@ -158,10 +162,44 @@ class AppDialog extends React.Component<Props, State> {
transComponentDefinitions() {
const { componentDefinitions } = this.props;
return (componentDefinitions || []).map((item: { name: string }) => ({
lable: item.name,
value: item.name,
}));
const defaultCoreDataSource = ['k8s-objects', 'task', 'webservice', 'worker'];
const cloud: SelectGroupType = [
{
label: 'Cloud',
children: [],
},
];
const core: SelectGroupType = [
{
label: 'Core',
children: [],
},
];
const custom: SelectGroupType = [
{
label: 'Custom',
children: [],
},
];
(componentDefinitions || []).map((item: { name: string; workloadType: string }) => {
if (item.workloadType === 'configurations.terraform.core.oam.dev') {
cloud[0].children.push({
label: item.name,
value: item.name,
});
} else if (defaultCoreDataSource.includes(item.name)) {
core[0].children.push({
label: item.name,
value: item.name,
});
} else {
custom[0].children.push({
label: item.name,
value: item.name,
});
}
});
return [...core, ...custom, ...cloud];
}
onDetailsComponeDefinition = (value: string) => {
@ -334,6 +372,7 @@ class AppDialog extends React.Component<Props, State> {
>
<Select
locale={locale.Select}
showSearch
className="select"
{...init(`componentType`, {
initValue: 'webservice',
@ -399,6 +438,7 @@ class AppDialog extends React.Component<Props, State> {
})}
uiSchema={definitionDetail && definitionDetail.uiSchema}
ref={this.uiSchemaRef}
mode="new"
/>
</FormItem>
</If>

View File

@ -2,31 +2,6 @@ import type { DiagramMakerNodes, DiagramMakerEdges } from 'diagram-maker';
export const WORFLOW_NODE_WIDTH = 120;
export const WORKFLOW_COMMON_PANNEL = {
p1: {
id: 'p1',
position: {
x: 10,
y: 10,
},
size: {
width: 230,
height: 280,
},
},
p2: {
id: 'p2',
position: {
x: 10,
y: 10,
},
size: {
width: 420,
height: 40,
},
},
};
export type WorkFlowOption = {
default: boolean;
edit: boolean;

View File

@ -1,13 +1,11 @@
import React, { Component } from 'react';
import { If } from 'tsx-control-statements/components';
import { Button } from '@b-design/ui';
import { connect } from 'dva';
import WrokflowComponent from './workflow-component';
import WorkflowComponent from './workflow-component';
import type { WorkFlowData } from './entity';
import { getWorkFlowDefinitions } from '../../api/workflows';
import './index.less';
import Translation from '../../components/Translation';
type Props = {
workflowList: WorkFlowData[];
@ -72,19 +70,19 @@ class Workflow extends Component<Props, State> {
return (
<div style={{ height: '100%' }} className="workflow-wraper">
<If condition={workflowList.length === 0}>
<div className="empty-container">
{/* <div className="empty-container">
<div className="empty-word">
<Translation>Please create workflow</Translation>
</div>
<Button type="primary" onClick={this.addWrokflow}>
<Translation>New Workflow</Translation>
</Button>
</div>
</div> */}
</If>
<If condition={workflowList.length > 0}>
<React.Fragment>
{workflowList.map((workflow: WorkFlowData) => (
<WrokflowComponent
<WorkflowComponent
key={workflow.name + params.appName + workflow.steps}
appName={params.appName}
data={workflow}

View File

@ -62,12 +62,11 @@ export type NodeItem = {
typeId: string;
};
class WorkFlowComponent extends Component<Props, State> {
class WorkflowComponent extends Component<Props, State> {
field;
workflowItem: any;
constructor(props: any) {
super(props);
this.state = {
errorFocus: false,
loading: false,
@ -115,33 +114,52 @@ class WorkFlowComponent extends Component<Props, State> {
}
const { data } = this.props;
const workflowData = this.workflowItem.getValues();
const { nodes } = workflowData;
if (nodes && Object.keys(nodes).length === 0) {
Message.error('plase add workflow item');
const { nodes, edges } = workflowData;
if (edges && !edges.prev) {
let next = undefined;
Object.keys(edges).map((key) => {
if (edges[key].src == 'prev') {
next = edges[key];
}
});
if (!next) {
Message.warning('Please specify the first step');
return;
}
}
const reallyNodes: any = {};
Object.keys(nodes).map((nodeKey) => {
if (nodes[nodeKey].typeId !== 'next' && nodes[nodeKey].typeId !== 'prev') {
reallyNodes[nodeKey] = nodes[nodeKey];
}
});
if (reallyNodes && Object.keys(reallyNodes).length === 0) {
Message.warning('Please add at least one workflow step');
this.setState({
errorFocus: true,
});
return;
}
const nodeArr: NodeItem[] = Object.values(nodes);
const find = nodeArr.find((item) => !item.consumerData);
const nodeArr: NodeItem[] = Object.values(reallyNodes);
const find = nodeArr.find((item) => !item.consumerData || item.consumerData.name == '');
if (find) {
return Message.error('please enter workflow step name and type');
return Message.warning('Please set the workflow step name and type');
}
this.setState({ loading: true });
const { name, alias, description } = values;
data.appName = data.appName || this.props.appName;
data.name = name;
data.alias = alias;
data.description = description;
data.data = workflowData;
data.data = Object.assign(workflowData, { nodes: reallyNodes });
this.props.dispatch({
type: 'workflow/saveWorkflow',
payload: data,
callback: () => {
Message.success('save workflow success');
Message.success('Save the workflow success');
this.props.getWorkflow();
this.setState({ loading: false });
},
@ -170,14 +188,14 @@ class WorkFlowComponent extends Component<Props, State> {
<Translation>{option.default ? 'Cancel Default' : 'Set As Default'}</Translation>
</Menu.Item>
</If>
<Menu.Item onClick={() => this.deleteWorkflow(data.name)}>
{/* <Menu.Item onClick={() => this.deleteWorkflow(data.name)}>
<Translation>Remove</Translation>
</Menu.Item>
</Menu.Item> */}
</Menu>
);
const { init } = this.field;
const newData = _.cloneDeep(data);
convertWorkflowStep(newData, data.appName, option.edit ? 250 : 32);
convertWorkflowStep(newData, data.appName, 32, option.edit);
const workflowData = newData.data || { nodes: {}, edges: {} };
return (
<Loading visible={loading} style={{ width: '100%' }}>
@ -266,13 +284,15 @@ class WorkFlowComponent extends Component<Props, State> {
</a>
</If>
<div className="option-item">
<Dropdown
trigger={<Icon type="ellipsis" className="options-icon" />}
triggerType={['click']}
className="workflow-more"
>
{menu}
</Dropdown>
<If condition={option.edit}>
<Dropdown
trigger={<Icon type="ellipsis" className="options-icon" />}
triggerType={['click']}
className="workflow-more"
>
{menu}
</Dropdown>
</If>
</div>
</div>
</div>
@ -292,4 +312,4 @@ class WorkFlowComponent extends Component<Props, State> {
}
}
export default WorkFlowComponent;
export default WorkflowComponent;

View File

@ -1,8 +1,8 @@
.workflow-edge-container {
// height: 10px;
// background-color: green;
// width: 10px;
// line-height: 10px;
color: white;
width: 14px;
height: 14px;
color: #fff;
line-height: 14px;
text-align: center;
background-color: #1b58f4;
}

View File

@ -1,10 +1,19 @@
import { Balloon } from '@b-design/ui';
import React, { Component } from 'react';
import Translation from '../../../components/Translation';
import './index.less';
export interface EdgeData {
dest: string;
id: string;
src: string;
}
type Props = {
id: string;
data?: any;
data: EdgeData;
editMode?: boolean;
addNode: () => void;
};
type State = {};
@ -12,14 +21,27 @@ type State = {};
class WorkFlowEdge extends Component<Props, State> {
constructor(props: any) {
super(props);
this.state = {};
}
componentDidMount() {}
render() {
return <div className="workflow-edge-container">{/* + */}</div>;
const { editMode } = this.props;
if (editMode) {
return (
<Balloon
trigger={
<div onClick={this.props.addNode} className="workflow-edge-container">
+
</div>
}
>
<Translation>Click to add Workflow Step</Translation>
</Balloon>
);
}
return <div />;
}
}

View File

@ -1,12 +1,8 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { v4 as uuid } from 'uuid';
import { Message } from '@b-design/ui';
import type {
DiagramMakerData,
DiagramMakerNode,
DiagramMakerEdge,
DiagramMakerPanel,
} from 'diagram-maker';
import type { DiagramMakerData, DiagramMakerNode, DiagramMakerEdge } from 'diagram-maker';
import {
DiagramMaker,
ConnectorPlacement,
@ -17,17 +13,15 @@ import {
NodeActions,
} from 'diagram-maker';
import WorkFlowNode from '../workflow-node';
import type { EdgeData } from '../workflow-edge';
import WorkFlowEdge from '../workflow-edge';
import WorkFlowPannel from '../workflow-panel';
import WorkFlowToolTip from '../workflow-tooltip';
import type { EdgesAndNodes, WorkFlowNodeType, WorkFlowEdgeType } from '../entity';
import { WORKFLOW_COMMON_PANNEL } from '../entity';
import WorkflowForm from './workflow-form';
import 'diagram-maker/dist/diagramMaker.css';
import './index.less';
import { If } from 'tsx-control-statements/components';
import type { NodeItem } from '../workflow-component';
import type { Definition } from '../../../interface/addon';
import type { WorkflowStep } from '../../../interface/application';
type WorkFlowItemProps = {
workflowId: string;
@ -57,9 +51,9 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
}
const { data, edit } = this.props;
const containerWidth = this.container.clientWidth;
const nodeWidth = Object.keys(data.nodes).length * 200 + 300;
const basicePlatformConfig = {
panels: WORKFLOW_COMMON_PANNEL,
const nodeWidth = Object.keys(data.nodes).length * 360;
const basicPlatformConfig = {
panels: {},
workspace: {
position: {
x: 0,
@ -82,20 +76,31 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
const initialData: DiagramMakerData<WorkFlowNodeType, WorkFlowEdgeType> = Object.assign(
{},
data,
basicePlatformConfig,
basicPlatformConfig,
);
const { workFlowDefinitions } = this.props;
const nodeTypeConfigs: any = {};
workFlowDefinitions.map((definition: Definition) => {
nodeTypeConfigs[definition.name] = {
const nodeTypeConfigs: any = {
prev: {
size: {
width: 140,
height: 40,
width: 200,
height: 80,
},
visibleConnectorTypes: VisibleConnectorTypes.OUTPUT_ONLY,
},
next: {
size: {
width: 200,
height: 80,
},
visibleConnectorTypes: VisibleConnectorTypes.INPUT_ONLY,
},
common: {
size: {
width: 200,
height: 80,
},
definition: definition,
visibleConnectorTypes: VisibleConnectorTypes.BOTH,
};
});
},
};
this.diagramMaker = new DiagramMaker(
this.container,
{
@ -108,24 +113,34 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
ReactDOM.render(
<WorkFlowNode
id={node.id}
data={node.consumerData}
data={node.consumerData as WorkflowStep}
selected={node.diagramMakerData.selected}
workflowId={this.props.workflowId}
showDetail={this.showStepDetail}
editMode={edit}
deleteNode={this.deleteNode}
typeId={node.typeId}
/>,
container,
);
},
edge: (edge: DiagramMakerEdge<{}>, container: HTMLElement) => {
ReactDOM.render(<WorkFlowEdge id={edge.id} data={edge.consumerData} />, container);
ReactDOM.render(
<WorkFlowEdge
editMode={edit}
id={edge.id}
addNode={() => {
this.newNode(edge.src);
}}
data={edge as EdgeData}
/>,
container,
);
},
destroy: (container: HTMLElement) => {
ReactDOM.unmountComponentAtNode(container);
},
panels: {
p1: this.renderLeftPannel,
p2: this.renderTopPannel,
},
panels: {},
},
nodeTypeConfig: nodeTypeConfigs,
actionInterceptor: (action: any, dispatch, getState) => {
@ -144,27 +159,34 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
}
}
}
dispatch(action);
if (action.type == 'NODE_CREATE') {
this.setState({
currentSelectedNodeData: Object.assign(action.payload, {
consumerData: { type: action.payload.typeId },
}),
visible: true,
});
if (action.type == NodeActions.NODE_DRAG) {
if (action.payload.workspaceRectangle.size.height > 400) {
this.diagramMaker.api.resetZoom();
}
}
if (action.type == 'DELETE_ITEMS') {
if (Array.isArray(action.payload.edgeIds) && action.payload.edgeIds.length > 0) {
Message.warning(
'If remove the edge and save the workflow, the nodes that after this edge will be removed.',
);
}
}
dispatch(action);
},
},
{
initialData,
consumerRootReducer: (state: any, action: any) => {
switch (action.type) {
case 'UPDATENODE':
case 'UPDATE_NODE':
const newNode: any = {};
newNode[action.payload.id] = action.payload;
const newNodes = Object.assign({}, state.nodes, newNode);
const newState = Object.assign({}, state, { nodes: newNodes });
return newState;
return Object.assign({}, state, { nodes: newNodes });
case 'UPDATE_NODES':
return Object.assign({}, state, { nodes: action.payload });
case 'UPDATE_EDGES':
return Object.assign({}, state, { edges: action.payload });
default:
return state;
}
@ -172,24 +194,6 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
eventListener: () => {},
},
);
this.diagramMaker.store.subscribe(() => {
const { nodes } = this.diagramMaker.store.getState();
let currentNode: any = null;
Object.keys(nodes).forEach((key) => {
const obj = nodes[key];
if (obj && obj.diagramMakerData && obj.diagramMakerData.selected) {
currentNode = obj;
}
});
const { visible } = this.state;
if (currentNode && !visible) {
this.setState({
currentSelectedNodeData: currentNode,
visible: true,
});
}
});
}
fit = () => {
@ -204,66 +208,6 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
});
};
undo = () => {
this.diagramMaker.api.undo();
};
redo = () => {
this.diagramMaker.api.redo();
};
zoomIn = () => {
this.diagramMaker.api.zoomIn();
};
zoomOut = () => {
this.diagramMaker.api.zoomOut();
};
renderLeftPannel = (
panel: DiagramMakerPanel,
state: DiagramMakerData<WorkFlowNodeType, WorkFlowEdgeType>,
diagramMakerContainer: HTMLElement,
) => {
const { workFlowDefinitions, edit } = this.props;
const parentContainer = diagramMakerContainer.parentElement;
if (parentContainer) {
parentContainer.style.display = 'block';
}
if (!edit && parentContainer) {
parentContainer.style.display = 'none';
return;
}
ReactDOM.render(
<WorkFlowPannel
definitions={workFlowDefinitions}
id={panel.id}
workflowId={this.props.workflowId}
/>,
diagramMakerContainer,
);
};
renderTopPannel = (
panel: DiagramMakerPanel,
state: DiagramMakerData<WorkFlowNodeType, WorkFlowEdgeType>,
diagramMakerContainer: HTMLElement,
) => {
ReactDOM.render(
<WorkFlowToolTip
id={panel.id}
edit={this.props.edit}
undo={this.undo}
redo={this.redo}
autoLayout={this.autoLayout}
zoomIn={this.zoomIn}
zoomOut={this.zoomOut}
fit={this.fit}
/>,
diagramMakerContainer,
);
};
componentWillReceiveProps = (nextProps: WorkFlowItemProps) => {
if (nextProps.edit !== this.props.edit) {
if (nextProps.edit) {
@ -278,6 +222,142 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
return { edges, nodes };
};
deleteNode = (id: string) => {
const { edges, nodes } = this.diagramMaker.store.getState();
const newNodes: any = {};
const deleteNode = nodes[id];
if (!deleteNode) {
return;
}
Object.keys(nodes).map((key: string) => {
if (key == id) {
return;
}
const step: NodeItem = nodes[key];
let stepNew: NodeItem = Object.assign({}, step);
if (stepNew.diagramMakerData.position.x >= deleteNode.diagramMakerData.position.x) {
stepNew = Object.assign(stepNew, {
diagramMakerData: {
selected: stepNew.diagramMakerData.selected,
size: stepNew.diagramMakerData.size,
position: {
x: stepNew.diagramMakerData.position.x - 320,
y: stepNew.diagramMakerData.position.y,
},
},
});
}
newNodes[stepNew.id] = stepNew;
});
const newEdges: any = {};
let preEdge: EdgeData = { id: '', dest: '', src: '' };
let dest = '';
Object.keys(edges).map((key: string) => {
const edge: EdgeData = edges[key];
if (edge.src == id) {
dest = edge.dest;
return;
}
if (edge.dest == id) {
preEdge = Object.assign({}, edge);
return;
}
newEdges[key] = edge;
});
preEdge.dest = dest;
newEdges[preEdge.id] = preEdge;
this.diagramMaker.store.dispatch({
type: 'UPDATE_NODES',
payload: newNodes,
});
this.diagramMaker.store.dispatch({
type: 'UPDATE_EDGES',
payload: newEdges,
});
};
newNode = (source: string) => {
const { nodes, edges } = this.diagramMaker.store.getState();
const sourceNode = nodes[source];
let newNode: NodeItem = Object.assign({}, sourceNode);
newNode = Object.assign(newNode, {
consumerData: { name: '', properties: '', type: '' },
id: uuid(),
typeId: 'common',
diagramMakerData: {
selected: newNode.diagramMakerData.selected,
size: newNode.diagramMakerData.size,
position: {
x: newNode.diagramMakerData.position.x + 320,
y: newNode.diagramMakerData.position.y,
},
},
});
const newNodes: any = {};
Object.keys(nodes).map((key: string) => {
const step: NodeItem = nodes[key];
let stepNew: NodeItem = Object.assign({}, step);
if (stepNew.diagramMakerData.position.x >= newNode.diagramMakerData.position.x) {
stepNew = Object.assign(stepNew, {
diagramMakerData: {
selected: stepNew.diagramMakerData.selected,
size: stepNew.diagramMakerData.size,
position: {
x: stepNew.diagramMakerData.position.x + 320,
y: stepNew.diagramMakerData.position.y,
},
},
});
}
newNodes[stepNew.id] = stepNew;
});
newNodes[newNode.id] = newNode;
const newEdges: any = {};
Object.keys(edges).map((key: string) => {
const edge: EdgeData = edges[key];
if (edge.src == source) {
const pre = Object.assign({}, edge);
const next = Object.assign({}, edge);
pre.dest = newNode.id;
pre.id = pre.src;
next.src = newNode.id;
next.id = next.src;
newEdges[pre.id] = pre;
newEdges[next.id] = next;
return;
}
newEdges[key] = edge;
});
this.diagramMaker.store.dispatch({
type: 'UPDATE_NODES',
payload: newNodes,
});
this.diagramMaker.store.dispatch({
type: 'UPDATE_EDGES',
payload: newEdges,
});
this.setState({
currentSelectedNodeData: newNode,
visible: true,
});
};
showStepDetail = (id: string) => {
const { edit } = this.props;
if (!edit) {
return;
}
const { nodes } = this.diagramMaker.store.getState();
const consumerData: NodeItem = nodes[id];
this.setState({
currentSelectedNodeData: consumerData,
visible: true,
});
};
openDrawer = () => {
this.setState({
visible: true,
@ -296,20 +376,24 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
createOrUpdateNode = (values: any) => {
const { nodes } = this.diagramMaker.store.getState();
const { currentSelectedNodeData } = this.state;
let consumerData: NodeItem = nodes[currentSelectedNodeData.id];
consumerData = Object.assign({}, consumerData, {
consumerData: values,
diagramMakerData: {
selected: false,
size: consumerData.diagramMakerData.size,
position: consumerData.diagramMakerData.position,
},
});
this.diagramMaker.store.dispatch({
type: 'UPDATENODE',
payload: consumerData,
});
this.closeDrawer();
if (currentSelectedNodeData) {
let consumerData: NodeItem = nodes[currentSelectedNodeData.id];
if (consumerData) {
consumerData = Object.assign({}, consumerData, {
consumerData: values,
diagramMakerData: {
selected: false,
size: consumerData.diagramMakerData.size,
position: consumerData.diagramMakerData.position,
},
});
this.diagramMaker.store.dispatch({
type: 'UPDATE_NODE',
payload: consumerData,
});
this.closeDrawer();
}
}
};
onDelete = (values: NodeItem) => {
@ -335,7 +419,6 @@ class WorkFlowItem extends Component<WorkFlowItemProps, State> {
/>
<If condition={visible}>
<WorkflowForm
onDelete={() => this.onDelete(currentSelectedNodeData)}
createOrUpdateNode={this.createOrUpdateNode}
data={currentSelectedNodeData}
checkStepName={(name: string) => {

View File

@ -21,13 +21,12 @@ import locale from '../../../utils/locale';
type Props = {
createOrUpdateNode: (data: any) => void;
data: DiagramMakerNode<WorkFlowNodeType>;
data?: DiagramMakerNode<WorkFlowNodeType>;
workFlowDefinitions: [];
appName?: string;
componentName?: string;
closeDrawer: () => void;
checkStepName: (name: string) => boolean;
onDelete: () => void;
dispatch?: ({}) => {};
t: (key: string) => {};
};
@ -56,14 +55,16 @@ class WorkflowForm extends Component<Props, State> {
}
componentDidMount = () => {
const { consumerData } = this.props.data;
this.field.setValues(consumerData || '');
let properties = consumerData && consumerData.properties;
if (properties && typeof properties === 'string') {
properties = JSON.parse(properties);
if (this.props.data) {
const { consumerData } = this.props.data;
this.field.setValues(consumerData || '');
let properties = consumerData && consumerData.properties;
if (properties && typeof properties === 'string') {
properties = JSON.parse(properties);
}
this.field.setValues({ properties: properties });
this.onDetailsComponeDefinition((consumerData && consumerData.type) || '');
}
this.field.setValues({ properties: properties });
this.onDetailsComponeDefinition((consumerData && consumerData.type) || '');
};
setValues = (values: any | null) => {
@ -83,13 +84,6 @@ class WorkflowForm extends Component<Props, State> {
});
};
onDelete = () => {
const { onDelete } = this.props;
if (onDelete) {
onDelete();
}
};
transDefinitions() {
const { workFlowDefinitions } = this.props;
return (workFlowDefinitions || []).map((item: { name: string }) => ({
@ -129,20 +123,22 @@ class WorkflowForm extends Component<Props, State> {
};
const checkWorkflowStepName = (rule: Rule, value: any, callback: (error?: string) => void) => {
const { consumerData } = this.props.data;
if (checkStepName(value)) {
if (consumerData?.name && value == consumerData?.name) {
callback();
return;
if (data) {
const { consumerData } = data;
if (checkStepName(value)) {
if (consumerData?.name && value == consumerData?.name) {
callback();
return;
}
callback('name is exist');
}
callback('name is exist');
}
callback();
};
const edit = data && data.consumerData?.name != undefined && data.consumerData?.name != '';
return (
<DrawerWithFooter
title={<Translation>Edit Workflow Step</Translation>}
title={<Translation>{edit ? 'Edit Workflow Step' : 'Add Workflow Step'}</Translation>}
placement="right"
width={800}
onClose={closeDrawer}
@ -151,26 +147,12 @@ class WorkflowForm extends Component<Props, State> {
<Button key={'cancel'} style={{ marginRight: '16px' }} onClick={closeDrawer}>
Cancel
</Button>,
<Button
key={'delete'}
style={{ marginRight: '16px' }}
onClick={() => {
this.onDelete();
}}
title="Delete this step"
>
Delete
</Button>,
]}
>
<Form field={this.field}>
<Row>
<Col span={24} style={{ padding: '0 8px' }}>
<FormItem
label={<Translation>Workflow Type</Translation>}
required
disabled={this.field.getValue('type')}
>
<FormItem label={<Translation>Workflow Type</Translation>} required disabled={edit}>
<Select
locale={locale.Select}
className="select"
@ -202,7 +184,7 @@ class WorkflowForm extends Component<Props, State> {
maxLength={32}
placeholder={t('Please enter').toString()}
{...init('name', {
initValue: data.consumerData?.type,
initValue: data && data.consumerData?.type,
rules: [
{
required: true,
@ -279,6 +261,7 @@ class WorkflowForm extends Component<Props, State> {
})}
uiSchema={definitionDetail && definitionDetail.uiSchema}
ref={this.uiSchemaRef}
mode={this.props.data ? 'edit' : 'new'}
/>
</FormItem>
</If>

View File

@ -1,63 +1,27 @@
@primarycolor: #1b58f4;
.workflow-node-container {
width: auto;
height: 40px;
width: 200px;
height: 80px;
padding: 16px;
color: black;
line-height: 40px;
white-space: nowrap;
text-align: center;
text-overflow: ellipsis;
border: 1px solid #ccc;
border-radius: 12px;
cursor: pointer;
&.active {
border: 1px solid @primarycolor;
}
}
.workflow-switch-node-container {
position: relative;
width: 80px;
height: 80px;
border: none;
&.active {
border: none;
.rhombus-container {
border: 1px solid @primarycolor;
.workflow-step-type {
padding-top: 8px;
color: #a6a6a6;
}
.workflow-step-delete {
position: absolute;
top: 8px;
right: 8px;
:hover {
color: @primarycolor;
}
}
.rhombus-container {
position: absolute;
top: 11px;
left: 11px;
width: 57px;
height: 57px;
border: 1px solid #ccc;
transform: rotate(45deg);
}
.content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.start-connector {
position: absolute;
top: 31px;
left: -4px;
width: 14px;
height: 14px;
background-color: blue;
border-radius: 50%;
}
.end-connector {
position: absolute;
top: 31px;
right: -4px;
width: 14px;
height: 14px;
background-color: blue;
border-radius: 50%;
}
}

View File

@ -1,15 +1,20 @@
import React, { Component } from 'react';
import { DiagramMakerComponents } from 'diagram-maker';
import { Icon } from '@b-design/ui';
import './index.less';
import type { WorkflowStep } from '../../../interface/application';
import { Balloon, Icon } from '@b-design/ui';
import { If } from 'tsx-control-statements/components';
import Translation from '../../../components/Translation';
type Props = {
id: string;
typeId?: string;
workflowId: string;
selected?: boolean;
data?: any;
showDetail: (id: string) => void;
deleteNode: (id: string) => void;
data?: WorkflowStep;
editMode?: boolean;
};
type State = {};
@ -17,69 +22,66 @@ type State = {};
class WorkFlowNode extends Component<Props, State> {
constructor(props: any) {
super(props);
this.state = {};
}
componentDidMount() {}
render() {
const { data = {}, selected, id, workflowId, typeId } = this.props;
const { data, selected, id, workflowId, editMode, typeId } = this.props;
const showName =
data && data.name ? (
data.alias || data.name
) : (
<Translation>Click to config Workflow Step</Translation>
);
if (typeId == 'prev' || typeId == 'next') {
return <React.Fragment />;
}
return (
<React.Fragment>
<If condition={typeId !== 'switch'}>
<div
className={selected ? 'workflow-node-container active' : 'workflow-node-container'}
id={id}
workflow-id={workflowId}
>
{data.alias || data.name || <Translation>Click Edit</Translation>}
<div
data-event-target="true"
data-dropzone="true"
data-type={DiagramMakerComponents.NODE_CONNECTOR}
data-id={id}
/>
<div
data-event-target="true"
data-draggable="true"
data-type={DiagramMakerComponents.NODE_CONNECTOR}
data-id={id}
/>
</div>
</If>
<If condition={typeId === 'switch'}>
<div
className={
selected
? 'workflow-node-container workflow-switch-node-container active'
: 'workflow-node-container workflow-switch-node-container'
}
id={id}
workflow-id={workflowId}
title={data.text}
>
<div className="rhombus-container" />
<div className="content">
{data.text || <Translation>Click Edit</Translation>}
<Icon type="ashbin1" />
<div
className={selected ? 'workflow-node-container active' : 'workflow-node-container'}
id={id}
style={{
cursor: editMode ? 'pointer' : 'auto',
}}
onClick={() => this.props.showDetail(id)}
workflow-id={workflowId}
>
<If condition={editMode}>
<div className="workflow-step-delete">
<Balloon
trigger={
<Icon
size={14}
onClick={(event: any) => {
event.stopPropagation();
this.props.deleteNode(id);
}}
type="delete"
/>
}
>
<Translation>Click to remove Workflow Step</Translation>
</Balloon>
</div>
<div
className="start-connector"
data-event-target="true"
data-dropzone="true"
data-type={DiagramMakerComponents.NODE_CONNECTOR}
data-id={id}
/>
<div
className="end-connector"
data-event-target="true"
data-draggable="true"
data-type={DiagramMakerComponents.NODE_CONNECTOR}
data-id={id}
/>
</div>
</If>
</If>
{showName}
<div className="workflow-step-type">{data?.type}</div>
<div
data-event-target="true"
data-dropzone="false"
data-type={DiagramMakerComponents.NODE_CONNECTOR}
data-id={id}
/>
<div
data-event-target="true"
data-draggable="false"
data-type={DiagramMakerComponents.NODE_CONNECTOR}
data-id={id}
/>
</div>
</React.Fragment>
);
}

View File

@ -1,6 +1,6 @@
.workflow-pannel-container {
height: 230px;
padding: 20px;
padding: 16px;
overflow: auto;
border: 1px solid #eee;
.hl {

View File

@ -86,19 +86,23 @@ class AddClustDialog extends React.Component<Props, State> {
dashboardURL: values.dashboardURL,
kubeConfig: values.kubeConfig,
labels: cluster.labels,
}).then(() => {
Message.success(<Translation>cluster update success</Translation>);
this.resetField();
this.props.onOK();
}).then((re: any) => {
if (re) {
Message.success(<Translation>cluster update success</Translation>);
this.resetField();
this.props.onOK();
}
});
} else {
this.props.dispatch({
type: 'clusters/createCluster',
payload: values,
callback: () => {
Message.success(<Translation>cluster add success</Translation>);
this.resetField();
this.props.onOK();
callback: (re: any) => {
if (re) {
Message.success(<Translation>cluster add success</Translation>);
this.resetField();
this.props.onOK();
}
},
});
}
@ -164,7 +168,7 @@ class AddClustDialog extends React.Component<Props, State> {
onOk={this.onOk}
onCancel={this.onClose}
onClose={this.onClose}
footerActions={['ok', 'cancel']}
footerActions={['cancel', 'ok']}
footerAlign="center"
>
<Loading visible={editClusterName && !editMode ? true : false} style={{ width: '100%' }}>

View File

@ -14,7 +14,8 @@ type Props = {
setVisible: (visible: boolean) => void;
setCloudService: (isCloudService: boolean) => void;
t: (key: string) => string;
dispatch: ({}) => void;
onPropsOK: () => void;
dispatch: ({ }) => void;
};
type State = {
@ -76,6 +77,7 @@ class CloudServiceDialog extends React.Component<Props, State> {
.then((re) => {
if (re) {
this.setState({ choseInput: false, cloudClusters: re.clusters, btnLoading: false });
this.props.onPropsOK();
}
})
.catch((err) => {
@ -122,7 +124,7 @@ class CloudServiceDialog extends React.Component<Props, State> {
});
}
})
.then(() => {});
.then(() => { });
};
render() {

View File

@ -9,12 +9,14 @@ import { Button, Message } from '@b-design/ui';
import { deleteCluster } from '../../api/cluster';
import Translation from '../../components/Translation';
import { If } from 'tsx-control-statements/components';
import { getEnabledAddons, getAddonsList } from '../../api/addons';
import { Addon } from '../../interface/addon';
type Props = {
clusterList: [];
defaultCluster: string;
cloudClusters: [];
dispatch: ({}) => {};
dispatch: ({ }) => {};
};
type State = {
@ -24,6 +26,8 @@ type State = {
showAddCluster: boolean;
showAddCloudCluster: boolean;
editClusterName: string;
addonMessage: { name: string; path: string }[];
isShowAddonMessage: boolean;
};
@connect((store: any) => {
@ -39,6 +43,8 @@ class Cluster extends React.Component<Props, State> {
showAddCluster: false,
showAddCloudCluster: false,
editClusterName: '',
addonMessage: [],
isShowAddonMessage: false,
};
}
@ -78,10 +84,66 @@ class Cluster extends React.Component<Props, State> {
});
};
onGetEnabledAddon = async () => {
getEnabledAddons({}).then((res) => {
this.onGetAddonsList(res.enabledAddons);
});
};
onGetAddonsList = async (enableAddon: { name: string; phase: boolean }[]) => {
getAddonsList({}).then((res) => {
const addonMessage: { name: string; path: string }[] = [];
const addonList: Addon[] = res.addons;
(enableAddon || []).forEach((ele: { name: string; phase: boolean }) => {
addonList.forEach((item: Addon) => {
const isMatchName = ele.name === item.name;
const deploy = item.deployTo || { runtimeCluster: false, runtime_cluster: false };
if (isMatchName && (deploy.runtimeCluster || deploy.runtime_cluster)) {
addonMessage.push({ name: item.name, path: item.url || '' });
}
});
});
if (addonMessage && addonMessage.length !== 0) {
this.setState({
isShowAddonMessage: true,
addonMessage: addonMessage,
});
}
});
};
showAddonMessage() {
const { addonMessage } = this.state;
return (addonMessage || []).map((item, index, arr) => {
const lastIndex = arr.length - 1;
const showSymbol = index === lastIndex ? '' : '、';
return (
<span>
<a href={item.path}>
{item.name}
{showSymbol}
</a>
</span>
);
});
}
handleHiddenAddonMessage = () => {
this.setState({ isShowAddonMessage: false });
};
render() {
const { clusterList = [], dispatch } = this.props;
const { page, pageSize, query, showAddCluster, showAddCloudCluster, editClusterName } =
this.state;
const {
page,
pageSize,
query,
showAddCluster,
showAddCloudCluster,
editClusterName,
isShowAddonMessage,
addonMessage,
} = this.state;
return (
<div>
<Title
@ -103,6 +165,14 @@ class Cluster extends React.Component<Props, State> {
</Button>,
]}
/>
<If condition={isShowAddonMessage && addonMessage.length != 0}>
<Message type="notice" closeable onClose={this.handleHiddenAddonMessage}>
Connect Cluster Success! Please upgrade {this.showAddonMessage()} addons, make them take
effect in the new cluster.
</Message>
</If>
<SelectSearch
dispatch={dispatch}
getChildCompentQuery={(q: string): any => {
@ -127,6 +197,9 @@ class Cluster extends React.Component<Props, State> {
setCloudService={(visible) => {
this.setState({ showAddCloudCluster: visible });
}}
onPropsOK={() => {
this.onGetEnabledAddon();
}}
dispatch={dispatch}
/>
</If>
@ -143,6 +216,7 @@ class Cluster extends React.Component<Props, State> {
}}
onOK={() => {
this.getClusterList();
this.onGetEnabledAddon();
this.setState({ showAddCluster: false, editClusterName: '' });
}}
dispatch={dispatch}

View File

@ -0,0 +1,21 @@
.namespaceDialogWraper {
width: 800px;
height: 300px;
.next-dialog-footer {
display: flex;
justify-content: center !important;
.next-btn {
display: block;
flex: none !important;
width: 100px !important;
}
}
.next-dialog-body {
width: 100% !important;
max-height: none !important;
}
.basic {
width: 100%;
padding: 0 16px;
}
}

View File

@ -1,9 +1,10 @@
import React from 'react';
import { Input, Select, Button, Message } from '@b-design/ui';
import { Input, Select, Button, Message, Dialog, Form, Field, Grid } from '@b-design/ui';
import { If } from 'tsx-control-statements/components';
import Translation from '../../../../components/Translation';
import { createClusterNamespace } from '../../../../api/cluster';
import locale from '../../../../utils/locale';
import './index.less';
type Props = {
cluster?: string;
@ -13,6 +14,8 @@ type Props = {
loadNamespaces: (cluster: string) => void;
disableNew?: boolean;
disabled?: boolean;
createNamespaceDialog?: boolean;
targetField: Field;
};
export interface NamespaceItem {
@ -21,101 +24,137 @@ export interface NamespaceItem {
}
type State = {
showNameSpaceInput: boolean;
inputNamespaceParam: string;
loading: boolean;
createNamespace?: string;
visible: boolean;
};
class Namespace extends React.Component<Props, State> {
field: Field;
constructor(props: any) {
super(props);
this.state = {
showNameSpaceInput: false,
inputNamespaceParam: '',
loading: false,
visible: false,
};
this.field = new Field(this);
}
openNamespaceInput = () => {
this.setState({
showNameSpaceInput: true,
});
};
closeNamespaceInput = () => {
this.setState({
showNameSpaceInput: false,
});
const { createNamespaceDialog } = this.props;
if (createNamespaceDialog) {
this.field.setValue('namespace', '');
this.setState({
visible: true,
});
}
};
createNamespace = () => {
const { cluster, loadNamespaces } = this.props;
const { cluster, loadNamespaces, targetField } = this.props;
if (!cluster) {
Message.warning('Please select a cluster first');
return;
}
const { createNamespace } = this.state;
const namespace = createNamespace || this.field.getValue('namespace');
this.setState({ loading: true });
if (cluster && createNamespace) {
createClusterNamespace({ cluster: cluster, namespace: createNamespace }).then((re) => {
if (cluster && namespace) {
createClusterNamespace({ cluster: cluster, namespace: namespace }).then((re) => {
if (re) {
Message.success('create namespace success');
loadNamespaces(cluster);
}
targetField.setValue('runtimeNamespace', namespace);
this.setState({
loading: false,
showNameSpaceInput: false,
visible: false,
});
});
}
};
onClose = () => {
this.setState({
visible: false,
});
};
render() {
const { disableNew, onChange, namespaces, value, disabled } = this.props;
const { showNameSpaceInput, loading } = this.state;
const { visible } = this.state;
const { Col, Row } = Grid;
const init = this.field.init;
const formItemLayout = {
labelCol: {
fixedSpan: 10,
},
wrapperCol: {
span: 14,
},
};
return (
<div>
<If condition={!showNameSpaceInput}>
<div className="cluster-container">
<Select
locale={locale.Select}
className="cluster-params-input"
mode="single"
disabled={disabled}
dataSource={namespaces}
onChange={onChange}
placeholder={''}
value={value}
/>
<If condition={!disableNew}>
<Button
className="cluster-option-btn"
type="secondary"
disabled={disabled}
onClick={this.openNamespaceInput}
>
<Translation>New</Translation>
</Button>
</If>
</div>
</If>
<If condition={showNameSpaceInput}>
<div className="cluster-container">
<Input
onChange={(v) => this.setState({ createNamespace: v })}
className="cluster-params-input"
/>
<div className="cluster-container">
<Select
locale={locale.Select}
className="cluster-params-input"
mode="single"
disabled={disabled}
dataSource={namespaces}
onChange={onChange}
placeholder={''}
value={value}
/>
<If condition={!disableNew}>
<Button
loading={loading}
className="cluster-option-btn"
type="secondary"
onClick={this.createNamespace}
disabled={disabled}
onClick={this.openNamespaceInput}
>
<Translation>Submit</Translation>
<Translation>New</Translation>
</Button>
</div>
</If>
</If>
</div>
<Dialog
locale={locale.Dialog}
className={'namespaceDialogWraper'}
title={<Translation>Create Namespace</Translation>}
autoFocus={true}
isFullScreen={true}
visible={visible}
onOk={this.createNamespace}
onCancel={this.onClose}
onClose={this.onClose}
footerActions={['cancel', 'ok']}
footerAlign="center"
>
<Form field={this.field} {...formItemLayout}>
<Row>
<Col span={24} style={{ padding: '0 8px' }}>
<Form.Item label={<Translation>namespace</Translation>} required>
<Input
name="namespace"
placeholder={'Please enter'}
{...init('namespace', {
rules: [
{
required: true,
message: 'Please select namesapce',
},
],
})}
/>
</Form.Item>
</Col>
</Row>
</Form>
</Dialog>
</div>
);
}

View File

@ -312,6 +312,8 @@ class DeliveryDialog extends React.Component<Props, State> {
namespaces={namespaces}
loadNamespaces={this.loadNamespaces}
cluster={cluster}
createNamespaceDialog={true}
targetField={this.field}
/>
</FormItem>
</Col>

View File

@ -1993,6 +1993,11 @@
resolved "https://registry.nlark.com/@types/unist/download/@types/unist-2.0.6.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Funist%2Fdownload%2F%40types%2Funist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
integrity sha1-JQp7FsO5H2cqJFUuxkZ47rHToI0=
"@types/uuid@^8.3.4":
version "8.3.4"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
"@types/webpack-sources@*":
version "3.2.0"
resolved "https://registry.nlark.com/@types/webpack-sources/download/@types/webpack-sources-3.2.0.tgz?cache=0&sync_timestamp=1629709718286&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fwebpack-sources%2Fdownload%2F%40types%2Fwebpack-sources-3.2.0.tgz#16d759ba096c289034b26553d2df1bf45248d38b"
@ -12210,6 +12215,11 @@ uuid@^3.3.2, uuid@^3.4.0:
resolved "https://registry.npmmirror.com/uuid/download/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha1-sj5DWK+oogL+ehAK8fX4g/AgB+4=
uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
uvu@^0.5.0:
version "0.5.2"
resolved "https://registry.npmmirror.com/uvu/download/uvu-0.5.2.tgz#c145e7f4b5becf80099cf22fd8a4a05f0112b2c0"