Compare commits

...

9 Commits
main ... v1.5.8

Author SHA1 Message Date
barnettZQG be7a7ba470
Fix: support to open the tcp address directly (#623)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2022-10-24 11:03:52 +08:00
github-actions[bot] 9a30505e4e
[Backport release-1.5] Fix: enhance the drawer page of the addon (#621)
* Fix: enhance the drawer page of the addon

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 389d9265f1)

* Fix: reset the schema after version changed

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 780db24a22)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-10-22 13:51:10 +08:00
barnettZQG 56c4dc9ed5
Fix: can not show the detail of the first revision (#612) (#613)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>

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

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2022-08-31 19:11:48 +08:00
github-actions[bot] 7d64df983d
Fix: miss the form items after turning off the switch (#611)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 9d1af0b5ce)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-08-24 14:07:04 +08:00
github-actions[bot] 430a9c36ad
Fix: set the locale (#610)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit c1257adf6f)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-08-24 10:29:13 +08:00
github-actions[bot] d007fc3f35
[Backport release-1.5] Feat: show the endpoints in the addon detail page (#607)
* Feat: show the endpoints in the addon detail page

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 030d70df0c)

* Fix: js/unsafe-external-link

Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit e24e4662b1)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-08-02 18:30:49 +08:00
github-actions[bot] b9f1663867
Fix: hidden cloudshell dialog scroll bar (#605)
Signed-off-by: Zhiyu Wang <cloudsky.newbis@gmail.com>
(cherry picked from commit 0d0fc6597a)

Co-authored-by: Zhiyu Wang <cloudsky.newbis@gmail.com>
2022-08-01 18:20:30 +08:00
github-actions[bot] a18675b341
Fix: incorrect dex redirect URL (#603)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit a1d3910e99)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-07-31 17:07:53 +08:00
barnettZQG e431a48460
Fix: the email can not edit (#600) (#602)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
2022-07-31 17:07:42 +08:00
26 changed files with 369 additions and 171 deletions

View File

@ -1,6 +1,7 @@
import { post, get, rdelete, put } from './request'; import { post, get, rdelete, put } from './request';
import { addons, addonRegistries, enabledAddon } from './productionLink'; import { addons, addonRegistries, enabledAddon } from './productionLink';
import { getDomain } from '../utils/common'; import { getDomain } from '../utils/common';
import type { EnableAddonRequest } from '../interface/addon';
const baseURLOject = getDomain(); const baseURLOject = getDomain();
const base = baseURLOject.MOCK || baseURLOject.APIBASE; const base = baseURLOject.MOCK || baseURLOject.APIBASE;
@ -23,7 +24,16 @@ export function getAddonsList(params: any) {
export function getAddonsDetails(params: { name: string; version?: string }) { export function getAddonsDetails(params: { name: string; version?: string }) {
const gurl = `${base + addons}/${params.name}`; const gurl = `${base + addons}/${params.name}`;
return get(gurl, params).then((res) => res); return get(
gurl,
params.version
? {
params: {
version: params.version,
},
}
: {},
).then((res) => res);
} }
export function disableAddon(params: { name: string }) { export function disableAddon(params: { name: string }) {
@ -31,12 +41,7 @@ export function disableAddon(params: { name: string }) {
return post(gurl, params).then((res) => res); return post(gurl, params).then((res) => res);
} }
export function enableAddon(params: { export function enableAddon(params: EnableAddonRequest) {
name: string;
version: string;
clusters?: string[];
properties: any;
}) {
const gurl = `${base + addons}/${params.name}/enable`; const gurl = `${base + addons}/${params.name}/enable`;
const req: any = { args: params.properties, version: params.version }; const req: any = { args: params.properties, version: params.version };
if (params.clusters) { if (params.clusters) {
@ -45,12 +50,7 @@ export function enableAddon(params: {
return post(gurl, req).then((res) => res); return post(gurl, req).then((res) => res);
} }
export function upgradeAddon(params: { export function upgradeAddon(params: EnableAddonRequest) {
name: string;
version: string;
clusters?: string[];
properties: any;
}) {
const gurl = `${base + addons}/${params.name}/update`; const gurl = `${base + addons}/${params.name}/update`;
const req: any = { args: params.properties, version: params.version }; const req: any = { args: params.properties, version: params.version };
if (params.clusters) { if (params.clusters) {

View File

@ -470,15 +470,6 @@ a {
display: inline-block; display: inline-block;
} }
.next-drawer-close {
right: 24px;
.next-drawer-close-icon.next-icon::before {
width: 16px !important;
height: 16px !important;
font-size: 16px !important;
}
}
.next-btn.danger-btn { .next-btn.danger-btn {
color: @dangerColor !important; color: @dangerColor !important;
border: @dangerColor 1px solid !important; border: @dangerColor 1px solid !important;

View File

@ -8,3 +8,46 @@
height: 60px; height: 60px;
background: #fff; background: #fff;
} }
.next-drawer {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
.next-drawer-header {
position: absolute;
top: 0;
z-index: 1000;
display: flex;
flex: 0;
align-items: center;
width: 100%;
height: 55px;
padding: 16px 24px;
font-size: 16px;
line-height: 22px;
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}
.next-drawer-body {
flex: 1;
min-width: 0;
min-height: 0;
margin-top: 71px;
padding: 24px;
overflow: auto;
}
}
.next-drawer-close {
right: 26px !important;
.next-drawer-close-icon.next-icon::before {
color: #000;
font-weight: 800 !important;
font-size: 24px !important;
line-height: 24px !important;
&:hover {
color: var(--primary-color);
}
}
}

View File

@ -288,9 +288,6 @@ class UISchema extends Component<Props, State> {
if (!uiSchema || enableCodeEdit) { if (!uiSchema || enableCodeEdit) {
return this.renderCodeEdit(); return this.renderCodeEdit();
} }
if (mode == 'edit' && value === undefined) {
return <div />;
}
let onlyShowRequired = false; let onlyShowRequired = false;
let couldShowParamCount = 0; let couldShowParamCount = 0;
uiSchema.map((param) => { uiSchema.map((param) => {
@ -359,7 +356,7 @@ class UISchema extends Component<Props, State> {
if (initValue === undefined) { if (initValue === undefined) {
initValue = param.validate?.defaultValue; initValue = param.validate?.defaultValue;
} }
const disableEdit = (param.validate?.immutable && this.props.mode == 'edit') || false; const disableEdit = (param.validate?.immutable && mode == 'edit') || false;
const getGroup = (children: React.ReactNode) => { const getGroup = (children: React.ReactNode) => {
return ( return (
<Group <Group

View File

@ -120,8 +120,8 @@ class Group extends React.Component<Props, State> {
type: 'confirm', type: 'confirm',
content: ( content: (
<Translation> <Translation>
If Switch is turned off, The configuration will be reset. Are you The configuration will be reset if the switch is turned off. Are you
sure you want to do this? sure want to do this?
</Translation> </Translation>
), ),
onOk: () => { onOk: () => {

View File

@ -16,6 +16,10 @@ export interface Addon {
registryName?: string; registryName?: string;
availableVersions?: string[]; availableVersions?: string[];
url?: string; url?: string;
system?: {
vela?: string;
kubernetes?: string;
};
} }
export interface AddonStatus { export interface AddonStatus {
@ -69,3 +73,10 @@ export interface AddonBaseStatus {
name: string; name: string;
phase: 'disabled' | 'enabled' | 'enabling' | 'suspend' | 'disabling'; phase: 'disabled' | 'enabled' | 'enabling' | 'suspend' | 'disabling';
} }
export interface EnableAddonRequest {
name: string;
version: string;
properties: Record<string, any>;
clusters?: string[];
}

View File

@ -9,6 +9,7 @@
.next-drawer-body { .next-drawer-body {
height: 100%; height: 100%;
padding: 0; padding: 0;
overflow: hidden;
background: rgb(59, 59, 59); background: rgb(59, 59, 59);
.prepare { .prepare {
display: flex; display: flex;

View File

@ -2,9 +2,10 @@ import React, { Component, Fragment } from 'react';
import { Grid, Form, Input, Field, Button, Message, Icon, Dialog } from '@b-design/ui'; import { Grid, Form, Input, Field, Button, Message, Icon, Dialog } from '@b-design/ui';
import { updateUser } from '../../../../api/users'; import { updateUser } from '../../../../api/users';
import type { LoginUserInfo } from '../../../../interface/user'; import type { LoginUserInfo } from '../../../../interface/user';
import { checkUserPassword, checkUserEmail } from '../../../../utils/common'; import { checkUserPassword } from '../../../../utils/common';
import Translation from '../../../../components/Translation'; import Translation from '../../../../components/Translation';
import i18n from '../../../../i18n'; import i18n from '../../../../i18n';
import locale from '../../../../utils/locale';
type Props = { type Props = {
userInfo?: LoginUserInfo; userInfo?: LoginUserInfo;
@ -96,6 +97,7 @@ class EditPlatFormUserDialog extends Component<Props, State> {
title={this.showTitle()} title={this.showTitle()}
style={{ width: '600px' }} style={{ width: '600px' }}
onOk={this.onUpdateUser} onOk={this.onUpdateUser}
locale={locale().Dialog}
footerActions={['ok']} footerActions={['ok']}
> >
<Form {...formItemLayout} field={this.field}> <Form {...formItemLayout} field={this.field}>
@ -143,7 +145,7 @@ class EditPlatFormUserDialog extends Component<Props, State> {
rules: [ rules: [
{ {
required: true, required: true,
pattern: checkUserEmail, format: 'email',
message: <Translation>Please input a valid email</Translation>, message: <Translation>Please input a valid email</Translation>,
}, },
], ],

View File

@ -1,3 +1,7 @@
:root {
--primary-color: #1b58f4;
--warning-color: var(--message-warning-color-icon-inline, #fac800);
}
@border-radius-8: 8px; @border-radius-8: 8px;
@F7F7F7: #f7f7f7; @F7F7F7: #f7f7f7;
@F9F8FF: #f9f8ff; @F9F8FF: #f9f8ff;

View File

@ -342,7 +342,7 @@
"Instructions for assessing whether the container is alive.": "容器健康状态探针", "Instructions for assessing whether the container is alive.": "容器健康状态探针",
"Specify the annotations in the workload": "指定工作负载的注释", "Specify the annotations in the workload": "指定工作负载的注释",
"Specify the labels in the workload": "指定工作负载的标签", "Specify the labels in the workload": "指定工作负载的标签",
"If Switch is turned off, The configuration will be reset. Are you sure you want to do this?": "如果关闭,配置将重置。确定操作吗?", "The configuration will be reset if the switch is turned off. Are you sure want to do this?": "如果关闭,配置将重置。确定操作吗?",
"Mount PVC type volume": "挂载 PVC 数据卷", "Mount PVC type volume": "挂载 PVC 数据卷",
"Mount HostPath type volume": "挂载 HostPath 数据卷", "Mount HostPath type volume": "挂载 HostPath 数据卷",
"Mount EmptyDir type volume": "挂载 EmptyDir 数据卷", "Mount EmptyDir type volume": "挂载 EmptyDir 数据卷",

View File

@ -146,7 +146,12 @@ class CardContent extends React.Component<Props, State> {
<Row className="content-main-btn"> <Row className="content-main-btn">
{tags?.map((tag: string) => { {tags?.map((tag: string) => {
return ( return (
<Tag style={{ marginRight: '8px' }} color={getTagColor(tag)} key={tag}> <Tag
title={tag}
style={{ marginRight: '8px' }}
color={getTagColor(tag)}
key={tag}
>
{tag} {tag}
</Tag> </Tag>
); );

View File

@ -13,13 +13,18 @@
img { img {
width: 100%; width: 100%;
} }
ol, ul { ol,
ul {
list-style: decimal; list-style: decimal;
} }
p { p {
font-size: 14px; font-size: 14px;
} }
} }
.addon-readme li>p { .addon-readme li > p {
font-size: 14px; font-size: 14px;
} }
.warning-text {
color: var(--warning-color);
}

View File

@ -1,4 +1,4 @@
import React, { Fragment } from 'react'; import React from 'react';
import { import {
Form, Form,
Button, Button,
@ -10,6 +10,10 @@ import {
Message, Message,
Select, Select,
Checkbox, Checkbox,
Grid,
Dropdown,
Menu,
Icon,
} from '@b-design/ui'; } from '@b-design/ui';
import type { Rule } from '@alifd/field'; import type { Rule } from '@alifd/field';
import { If } from 'tsx-control-statements/components'; import { If } from 'tsx-control-statements/components';
@ -24,18 +28,23 @@ import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
import Empty from '../../../../components/Empty'; import Empty from '../../../../components/Empty';
import DrawerWithFooter from '../../../../components/Drawer'; import DrawerWithFooter from '../../../../components/Drawer';
import Group from '../../../../extends/Group';
import Translation from '../../../../components/Translation'; import Translation from '../../../../components/Translation';
import UISchema from '../../../../components/UISchema'; import UISchema from '../../../../components/UISchema';
import type { Addon, AddonStatus } from '../../../../interface/addon'; import type { Addon, AddonStatus, EnableAddonRequest } from '../../../../interface/addon';
import locale from '../../../../utils/locale'; import locale from '../../../../utils/locale';
import StatusShow from '../../../../components/StatusShow'; import StatusShow from '../../../../components/StatusShow';
import type { ApplicationStatus } from '../../../../interface/application'; import type { ApplicationStatus, UIParam } from '../../../../interface/application';
import i18n from '../../../../i18n'; import i18n from '../../../../i18n';
import type { NameAlias } from '../../../../interface/env'; import type { NameAlias } from '../../../../interface/env';
import Permission from '../../../../components/Permission'; import Permission from '../../../../components/Permission';
import 'github-markdown-css/github-markdown-light.css'; import 'github-markdown-css/github-markdown-light.css';
import './index.less'; import './index.less';
import { Link } from 'dva/router';
import { listApplicationServiceEndpoints } from '../../../../api/observation';
import type { Endpoint } from '../../../../interface/observation';
import { getLink } from '../../../../utils/utils';
const { Col, Row } = Grid;
type Props = { type Props = {
addonName: string; addonName: string;
@ -58,17 +67,19 @@ type State = {
clusters?: string[]; clusters?: string[];
allClusters?: NameAlias[]; allClusters?: NameAlias[];
enabledClusters?: string[]; enabledClusters?: string[];
endpoints?: Endpoint[];
propertiesMode: 'code' | 'native';
schema?: UIParam[];
}; };
class AddonDetailDialog extends React.Component<Props, State> { class AddonDetailDialog extends React.Component<Props, State> {
timer?: number;
readonly refreshTime = 1000;
form: Field; form: Field;
statusLoop: boolean; statusLoop: boolean;
uiSchemaRef: React.RefObject<UISchema>; uiSchemaRef: React.RefObject<UISchema>;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
propertiesMode: 'native',
addonDetailInfo: { addonDetailInfo: {
name: '', name: '',
}, },
@ -86,12 +97,7 @@ class AddonDetailDialog extends React.Component<Props, State> {
componentDidMount() { componentDidMount() {
this.loadAddonDetail(); this.loadAddonDetail();
this.loadAddonStatus(); this.loadAddonStatus();
} this.loadAddonEndpoints();
componentWillUnmount() {
if (this.timer) {
window.clearInterval(this.timer);
}
} }
loadAddonDetail = async () => { loadAddonDetail = async () => {
@ -100,7 +106,7 @@ class AddonDetailDialog extends React.Component<Props, State> {
getAddonsDetails({ name: this.props.addonName, version: version }) getAddonsDetails({ name: this.props.addonName, version: version })
.then((res: Addon) => { .then((res: Addon) => {
if (res) { if (res) {
this.setState({ addonDetailInfo: res, loading: false }); this.setState({ addonDetailInfo: res, schema: res.uiSchema, loading: false });
if (!this.state.version && res.version) { if (!this.state.version && res.version) {
this.setState({ version: res.version }); this.setState({ version: res.version });
} }
@ -116,7 +122,7 @@ class AddonDetailDialog extends React.Component<Props, State> {
getAddonsStatus({ name: this.props.addonName }) getAddonsStatus({ name: this.props.addonName })
.then((res: AddonStatus) => { .then((res: AddonStatus) => {
if (!res) return; if (!res) return;
if (res.phase == 'enabling' && !this.statusLoop) { if ((res.phase == 'enabling' || res.phase === 'disabling') && !this.statusLoop) {
this.statusLoop = true; this.statusLoop = true;
setTimeout(() => { setTimeout(() => {
this.statusLoop = false; this.statusLoop = false;
@ -158,26 +164,40 @@ class AddonDetailDialog extends React.Component<Props, State> {
}); });
}; };
handleSubmit = () => { loadAddonEndpoints = () => {
const { status } = this.state; // TODO: the app name and namespace should get from the api server.
if (status === 'enabled') { const appName = 'addon-' + this.props.addonName;
Dialog.confirm({ listApplicationServiceEndpoints({
content: appName: appName,
'Please make sure that the Addon is no longer in use and the related application has been recycled.', appNs: 'vela-system',
onOk: this.disableAddon, }).then((re) => {
locale: locale().Dialog, if (re && re.endpoints) {
}); this.setState({ endpoints: re.endpoints });
return; } else {
} this.setState({ endpoints: [] });
if (status === 'disabled') { }
this.form.validate((errors: any, values: any) => { });
if (errors) {
return;
}
this.enableAddon(values.properties);
});
}
}; };
onDisable = () => {
Dialog.confirm({
content:
'Please make sure that the Addon is no longer in use and the related application has been recycled.',
onOk: this.disableAddon,
locale: locale().Dialog,
});
return;
};
onEnable = () => {
this.form.validate((errors: any, values: any) => {
if (errors) {
return;
}
this.enableAddon(values.properties);
});
};
onUpgrade = () => { onUpgrade = () => {
this.form.validate((errors: any, values: any) => { this.form.validate((errors: any, values: any) => {
if (errors) { if (errors) {
@ -195,17 +215,21 @@ class AddonDetailDialog extends React.Component<Props, State> {
return; return;
} }
this.setState({ upgradeLoading: true }); this.setState({ upgradeLoading: true });
upgradeAddon({ const params: EnableAddonRequest = {
name: this.props.addonName, name: this.props.addonName,
version: this.state.version, version: this.state.version,
clusters: this.state.clusters,
properties: values.properties, properties: values.properties,
}).then(() => { };
if (this.state.addonDetailInfo?.deployTo?.runtimeCluster) {
params.clusters = this.state.clusters;
}
upgradeAddon(params).then(() => {
this.loadAddonStatus(); this.loadAddonStatus();
this.setState({ upgradeLoading: false }); this.setState({ upgradeLoading: false });
}); });
}); });
}; };
enableAddon = async (properties: any) => { enableAddon = async (properties: any) => {
if (!this.state.version) { if (!this.state.version) {
Message.warning(i18n.t('Please firstly select want to enable version')); Message.warning(i18n.t('Please firstly select want to enable version'));
@ -220,12 +244,15 @@ class AddonDetailDialog extends React.Component<Props, State> {
} }
this.setState({ statusLoading: true }, () => { this.setState({ statusLoading: true }, () => {
if (this.state.version) { if (this.state.version) {
enableAddon({ const params: EnableAddonRequest = {
name: this.props.addonName, name: this.props.addonName,
version: this.state.version, version: this.state.version,
clusters: this.state.clusters,
properties: properties, properties: properties,
}).then(() => { };
if (this.state.addonDetailInfo?.deployTo?.runtimeCluster) {
params.clusters = this.state.clusters;
}
enableAddon(params).then(() => {
this.loadAddonStatus(); this.loadAddonStatus();
}); });
} }
@ -253,7 +280,9 @@ class AddonDetailDialog extends React.Component<Props, State> {
}; };
changeVersion = (version: string) => { changeVersion = (version: string) => {
this.setState({ version: version }, () => { this.setState({ version: version }, () => {
this.loadAddonDetail(); this.setState({ schema: undefined }, () => {
this.loadAddonDetail();
});
}); });
}; };
@ -278,6 +307,9 @@ class AddonDetailDialog extends React.Component<Props, State> {
clusters, clusters,
allClusters, allClusters,
enabledClusters, enabledClusters,
endpoints,
propertiesMode,
schema,
} = this.state; } = this.state;
const { showAddon, addonName } = this.props; const { showAddon, addonName } = this.props;
const validator = (rule: Rule, value: any, callback: (error?: string) => void) => { const validator = (rule: Rule, value: any, callback: (error?: string) => void) => {
@ -293,32 +325,30 @@ class AddonDetailDialog extends React.Component<Props, State> {
item.uiType = 'Password'; item.uiType = 'Password';
} }
}); });
const buttons = [ const buttons = [];
<Fragment> if (status === 'enabled' || status === 'enabling' || status === 'disabling') {
<Button type="secondary" onClick={this.onClose} style={{ marginRight: '16px' }}> buttons.push(
<Translation>Cancel</Translation>
</Button>
<Permission <Permission
request={{ request={{
resource: `addon:${addonName}`, resource: `addon:${addonName}`,
action: status === 'enabled' ? 'disable' : 'enable', action: 'disable',
}} }}
project={''} project={''}
> >
<Button <Button
type="primary" type="secondary"
onClick={this.handleSubmit} onClick={this.onDisable}
warning={status === 'enabled'}
title={status} title={status}
style={{ backgroundColor: status === 'enabled' ? 'red' : '' }} className="danger-btn"
loading={statusLoading || loading || status == 'enabling' || status == 'disabling'} loading={status === 'disabling'}
disabled={status == 'enabling' || status == 'disabling' || version == ''} disabled={status === 'disabling'}
> >
<Translation>{status === 'enabled' ? 'Disable' : 'Enable'}</Translation> <Translation>Disable</Translation>
</Button> </Button>
</Permission> </Permission>,
</Fragment>, );
]; }
if (status == 'enabled' || status == 'suspend') { if (status == 'enabled' || status == 'suspend') {
buttons.push( buttons.push(
<Permission request={{ resource: `addon:${addonName}`, action: 'update' }} project={''}> <Permission request={{ resource: `addon:${addonName}`, action: 'update' }} project={''}>
@ -334,6 +364,21 @@ class AddonDetailDialog extends React.Component<Props, State> {
); );
} }
if (status === 'disabled' || status === 'enabling') {
buttons.push(
<Permission request={{ resource: `addon:${addonName}`, action: 'enable' }} project={''}>
<Button
loading={status === 'enabling'}
type="primary"
onClick={this.onEnable}
style={{ marginLeft: '16px' }}
>
<Translation>Enable</Translation>
</Button>
</Permission>,
);
}
const getAppStatusShowType = (statusInfo: string | undefined) => { const getAppStatusShowType = (statusInfo: string | undefined) => {
if (!statusInfo) { if (!statusInfo) {
return 'notice'; return 'notice';
@ -364,6 +409,8 @@ class AddonDetailDialog extends React.Component<Props, State> {
}; };
}); });
const outerEndpoint = endpoints?.filter((end) => !end.endpoint.inner);
return ( return (
<div className="basic"> <div className="basic">
<DrawerWithFooter <DrawerWithFooter
@ -383,16 +430,52 @@ class AddonDetailDialog extends React.Component<Props, State> {
</If> </If>
<If condition={addonsStatus && addonsStatus.status}> <If condition={addonsStatus && addonsStatus.status}>
<Message <Row>
type={getAppStatusShowType(addonsStatus?.status)} <Col span={16}>
size="medium" <Message
style={{ padding: '8px', marginBottom: '10px' }} type={getAppStatusShowType(addonsStatus?.status)}
> size="medium"
{`${i18n.t('Addon status is ')}${addonsStatus?.status || 'Initing'}`} style={{ padding: '8px', marginBottom: '10px' }}
<a style={{ marginLeft: '16px' }} onClick={() => this.updateStatusShow(true)}> >
<Translation>Check the details</Translation> {`${i18n.t('Addon status is ')}${addonsStatus?.status || 'Init'}`}
</a> <Link
</Message> style={{ marginLeft: '16px' }}
to={`/applications/addon-${addonDetailInfo?.name}`}
>
<Translation>Check the details</Translation>
</Link>
</Message>
</Col>
<If condition={outerEndpoint && outerEndpoint?.length > 0}>
<Col span={8} className={'flexright'}>
<Dropdown
trigger={
<Button style={{ marginLeft: '16px' }} type="secondary">
<Translation>Endpoints</Translation>
</Button>
}
>
<Menu>
{outerEndpoint?.map((item) => {
const linkURL = getLink(item);
return (
<Menu.Item key={linkURL}>
<a
style={{ color: '#1b58f4' }}
target="_blank"
href={linkURL}
rel="noopener noreferrer"
>
{linkURL}
</a>
</Menu.Item>
);
})}
</Menu>
</Dropdown>
</Col>
</If>
</Row>
</If> </If>
{/* select the addon version */} {/* select the addon version */}
@ -405,6 +488,18 @@ class AddonDetailDialog extends React.Component<Props, State> {
value={version} value={version}
/> />
</Form.Item> </Form.Item>
{addonDetailInfo?.system && (
<span className="warning-text">
This version requirements: (
{addonDetailInfo?.system.vela
? `KubeVela: ${addonDetailInfo?.system.vela}`
: ''}
{addonDetailInfo?.system.kubernetes
? ` Kubernetes: ${addonDetailInfo?.system.kubernetes}`
: ''}
)
</span>
)}
</If> </If>
<If condition={addonDetailInfo?.deployTo?.runtimeCluster}> <If condition={addonDetailInfo?.deployTo?.runtimeCluster}>
@ -419,34 +514,69 @@ class AddonDetailDialog extends React.Component<Props, State> {
</If> </If>
</Card> </Card>
<If condition={addonDetailInfo?.uiSchema}> <If condition={schema}>
<Group <Card
title={<Translation>Properties</Translation>} contentHeight={'auto'}
description={<Translation>Set the addon configuration parameters</Translation>} className="withActions"
required={true} title="Properties"
closed={status === 'enabled'} subTitle={
alwaysShow={true} schema
disableAddon={true} ? [
hasToggleIcon={true} <Button
style={{ marginTop: '-12px' }}
onClick={() => {
if (propertiesMode === 'native') {
this.setState({ propertiesMode: 'code' });
} else {
this.setState({ propertiesMode: 'native' });
}
}}
>
<If condition={propertiesMode === 'native'}>
<Icon
style={{ color: '#1b58f4' }}
type={'display-code'}
title={'Switch to the coding mode'}
/>
</If>
<If condition={propertiesMode === 'code'}>
<Icon
style={{ color: '#1b58f4' }}
type={'laptop'}
title={'Switch to the native mode'}
/>
</If>
</Button>,
]
: []
}
> >
<Form field={this.form}> <Row>
{this.state.mode && ( <If condition={schema}>
<UISchema {this.state.mode && (
{...this.form.init('properties', { <UISchema
rules: [ {...this.form.init(`properties`, {
{ rules: [
validator: validator, {
message: 'Please check addon properties', validator: validator,
}, message: i18n.t('Please check the addon properties'),
], },
})} ],
ref={this.uiSchemaRef} })}
uiSchema={addonDetailInfo?.uiSchema} enableCodeEdit={propertiesMode === 'code'}
mode={this.state.mode} uiSchema={schema}
/> definition={{
)} name: addonDetailInfo?.name || '',
</Form> type: 'addon',
</Group> description: addonDetailInfo?.description || '',
}}
ref={this.uiSchemaRef}
mode={this.state.mode}
/>
)}
</If>
</Row>
</Card>
</If> </If>
<If condition={addonDetailInfo?.dependencies}> <If condition={addonDetailInfo?.dependencies}>
<Card <Card

View File

@ -7,7 +7,6 @@ import {
Button, Button,
Input, Input,
Select, Select,
Divider,
Icon, Icon,
Card, Card,
Loading, Loading,
@ -246,14 +245,12 @@ class ComponentDialog extends React.Component<Props, State> {
return ( return (
<div> <div>
<span>{alias ? `${alias}(${name})` : name}</span> <span>{alias ? `${alias}(${name})` : name}</span>
<Divider />
</div> </div>
); );
} else { } else {
return ( return (
<div> <div>
<span>{i18n.t('New Component')} </span> <span>{i18n.t('New Component')} </span>
<Divider />
</div> </div>
); );
} }

View File

@ -289,7 +289,12 @@ class Header extends Component<Props, State> {
if (item && !item.endpoint.inner) { if (item && !item.endpoint.inner) {
return ( return (
<Menu.Item key={linkURL}> <Menu.Item key={linkURL}>
<a style={{ color: '#1b58f4' }} target="_blank" href={linkURL}> <a
style={{ color: '#1b58f4' }}
target="_blank"
href={linkURL}
rel="noopener noreferrer"
>
{linkURL} {linkURL}
</a> </a>
</Menu.Item> </Menu.Item>

View File

@ -71,8 +71,8 @@ class PodDetail extends React.Component<Props, State> {
} }
}; };
onOpenCloudShell = () => { onOpenCloudShell = (containerName: string) => {
const { env } = this.props; const { env, pod } = this.props;
if (!checkEnabledAddon('cloudshell', this.props.enabledAddons)) { if (!checkEnabledAddon('cloudshell', this.props.enabledAddons)) {
Dialog.alert({ Dialog.alert({
title: i18n.t('CloudShell feature is not enabled'), title: i18n.t('CloudShell feature is not enabled'),
@ -97,13 +97,15 @@ class PodDetail extends React.Component<Props, State> {
}); });
return; return;
} }
const shellScript = `vela exec ${env?.appDeployName} -e ${env?.appDeployNamespace} -- bash`; const shellScript = `vela exec ${env?.appDeployName} -n ${env?.appDeployNamespace} --component ${pod.component} --cluster ${pod.cluster} --pod ${pod.metadata.name} --container ${containerName} -- bash`;
Dialog.show({ Dialog.show({
footer: false, footer: false,
style: { width: 400 }, style: { width: 600 },
content: ( content: (
<div> <div>
<h5>1. Copy the command:</h5> <h5>
1. <Translation>Copy the command</Translation>:
</h5>
<code className="code"> <code className="code">
{shellScript}{' '} {shellScript}{' '}
<CopyToClipboard <CopyToClipboard
@ -115,7 +117,9 @@ class PodDetail extends React.Component<Props, State> {
<AiOutlineCopy size={14} /> <AiOutlineCopy size={14} />
</CopyToClipboard> </CopyToClipboard>
</code> </code>
<h5>2. Open Cloud Shell:</h5> <h5>
2. <Translation>Open Cloud Shell</Translation>:
</h5>
<div> <div>
<Button <Button
size="small" size="small"
@ -128,7 +132,7 @@ class PodDetail extends React.Component<Props, State> {
} }
}} }}
> >
Open Cloud Shell <Translation>Open Cloud Shell</Translation>
</Button> </Button>
</div> </div>
</div> </div>
@ -264,7 +268,7 @@ class PodDetail extends React.Component<Props, State> {
</a> </a>
<a <a
title="Console Shell" title="Console Shell"
onClick={() => this.onOpenCloudShell()} onClick={() => this.onOpenCloudShell(record.name)}
className="actionIcon" className="actionIcon"
> >
<AiOutlineCode size={20} /> <AiOutlineCode size={20} />

View File

@ -43,7 +43,7 @@ export const ShowRevision = (props: RevisionProps) => {
React.useEffect(() => { React.useEffect(() => {
loadRevisionDetail(props.appName, props.revision, setDetail); loadRevisionDetail(props.appName, props.revision, setDetail);
}, [props.appName, props.revision]); }, [props.appName, props.revision]);
const containerId = props.revision.version; const containerId = props.revision.version + 'detail';
return ( return (
<React.Fragment> <React.Fragment>

View File

@ -37,7 +37,7 @@ export default class CallBackPage extends React.Component<Props> {
if (res && res.accessToken) { if (res && res.accessToken) {
localStorage.setItem('token', res.accessToken); localStorage.setItem('token', res.accessToken);
localStorage.setItem('refreshToken', res.refreshToken); localStorage.setItem('refreshToken', res.refreshToken);
this.props.history.push('/'); this.props.history.push('/applications');
} }
}) })
.catch((err) => { .catch((err) => {

View File

@ -1,6 +1,6 @@
.dialog-cluoudService-wraper { .dialog-cloudService-wrapper {
width: 1000px; width: 1000px;
.cloud-server-wraper { .cloud-server-wrapper {
.next-select-trigger { .next-select-trigger {
width: 100%; width: 100%;
} }

View File

@ -271,7 +271,7 @@ class CloudServiceDialog extends React.Component<Props, State> {
<React.Fragment> <React.Fragment>
<Dialog <Dialog
locale={locale().Dialog} locale={locale().Dialog}
className="dialog-cluoudService-wraper" className="dialog-cloudService-wrapper"
title={<Translation>Connect Kubernetes Cluster From Cloud</Translation>} title={<Translation>Connect Kubernetes Cluster From Cloud</Translation>}
autoFocus={true} autoFocus={true}
visible={visible} visible={visible}
@ -294,7 +294,7 @@ class CloudServiceDialog extends React.Component<Props, State> {
available permission set available permission set
</Translation> </Translation>
</Message> </Message>
<Form {...formItemLayout} field={this.field} className="cloud-server-wraper"> <Form {...formItemLayout} field={this.field} className="cloud-server-wrapper">
<FormItem label={<Translation>Provider</Translation>} required={true}> <FormItem label={<Translation>Provider</Translation>} required={true}>
<Select <Select
locale={locale().Select} locale={locale().Select}

View File

@ -25,7 +25,7 @@ type Props = {
}; };
type State = { type State = {
dexConfig: DexConfig; dexConfig?: DexConfig;
loginType: string; loginType: string;
loginErrorMessage: string; loginErrorMessage: string;
loginLoading: boolean; loginLoading: boolean;
@ -36,19 +36,12 @@ export default class LoginPage extends Component<Props, State> {
super(props); super(props);
this.field = new Field(this); this.field = new Field(this);
this.state = { this.state = {
dexConfig: {
clientID: '',
clientSecret: '',
issuer: '',
redirectURL: '',
},
loginType: '', loginType: '',
loginErrorMessage: '', loginErrorMessage: '',
loginLoading: false, loginLoading: false,
}; };
} }
componentDidMount() { componentDidMount() {
this.ontDexConfig();
this.onGetLoginType(); this.onGetLoginType();
} }
onGetLoginType = () => { onGetLoginType = () => {
@ -62,7 +55,7 @@ export default class LoginPage extends Component<Props, State> {
() => { () => {
const { loginType } = this.state; const { loginType } = this.state;
if (loginType === 'dex') { if (loginType === 'dex') {
this.onGetDexCode(); this.ontDexConfig();
} }
}, },
); );
@ -74,9 +67,14 @@ export default class LoginPage extends Component<Props, State> {
getDexConfig() getDexConfig()
.then((res) => { .then((res) => {
if (res) { if (res) {
this.setState({ this.setState(
dexConfig: res, {
}); dexConfig: res,
},
() => {
this.onGetDexCode();
},
);
} }
}) })
.catch(); .catch();
@ -116,10 +114,12 @@ export default class LoginPage extends Component<Props, State> {
}); });
}; };
onGetDexCode = () => { onGetDexCode = () => {
const { clientID, issuer, redirectURL } = this.state.dexConfig; if (this.state.dexConfig) {
const newRedirectURl = encodeURIComponent(redirectURL); const { clientID, issuer, redirectURL } = this.state.dexConfig;
const dexClientURL = `${issuer}/auth?client_id=${clientID}&redirect_uri=${newRedirectURl}&response_type=code&scope=openid+profile+email+offline_access&state=velaux`; const newRedirectURl = encodeURIComponent(redirectURL);
window.location.href = dexClientURL; const dexClientURL = `${issuer}/auth?client_id=${clientID}&redirect_uri=${newRedirectURl}&response_type=code&scope=openid+profile+email+offline_access&state=velaux`;
window.location.href = dexClientURL;
}
}; };
render() { render() {
const FormItem = Form.Item; const FormItem = Form.Item;

View File

@ -1,9 +1,4 @@
import React from 'react'; import React from 'react';
import Group from '../../extends/Group';
export default function NotFound() { export default function NotFound() {
return ( return <div>The route is not exist(404)</div>;
<div>
<Group title="envplan" description="envplan" />
</div>
);
} }

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Grid, Form, Input, Field, Button, Message, Select } from '@b-design/ui'; import { Grid, Form, Input, Field, Button, Message, Select } from '@b-design/ui';
import DrawerWithFooter from '../../../../components/Drawer'; import DrawerWithFooter from '../../../../components/Drawer';
import { checkUserPassword, checkUserEmail } from '../../../../utils/common'; import { checkUserPassword } from '../../../../utils/common';
import Translation from '../../../../components/Translation'; import Translation from '../../../../components/Translation';
import { createUser, updateUser } from '../../../../api/users'; import { createUser, updateUser } from '../../../../api/users';
import { checkName } from '../../../../utils/common'; import { checkName } from '../../../../utils/common';
@ -231,7 +231,7 @@ class CreateUser extends React.Component<Props, State> {
rules: [ rules: [
{ {
required: true, required: true,
pattern: checkUserEmail, format: 'email',
message: <Translation>Please input a valid email</Translation>, message: <Translation>Please input a valid email</Translation>,
}, },
], ],

View File

@ -203,7 +203,6 @@ export const replaceUrl = function (text: string) {
}; };
export const checkUserPassword = /^(?=.*[0-9])(?=.*[a-zA-Z])([a-zA-Z0-9]{8,16})$/; export const checkUserPassword = /^(?=.*[0-9])(?=.*[a-zA-Z])([a-zA-Z0-9]{8,16})$/;
export const checkUserEmail = /^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$/;
export function isMatchBusinessCode(businessCode: number) { export function isMatchBusinessCode(businessCode: number) {
const tokenExpiredList = [12002, 12010]; const tokenExpiredList = [12002, 12010];

View File

@ -1,5 +1,6 @@
import { Dialog } from '@b-design/ui'; import { Dialog } from '@b-design/ui';
import i18n from '../i18n'; import i18n from '../i18n';
import locale from './locale';
class ResetLogin { class ResetLogin {
private static singleton: ResetLogin; private static singleton: ResetLogin;
@ -14,6 +15,7 @@ class ResetLogin {
content: i18n.t('Authentication failed, please log in again'), content: i18n.t('Authentication failed, please log in again'),
closeable: true, closeable: true,
closeMode: [], closeMode: [],
locale: locale().Dialog,
footerActions: ['ok'], footerActions: ['ok'],
onOk: () => { onOk: () => {
localStorage.removeItem('token'); localStorage.removeItem('token');

View File

@ -110,6 +110,13 @@ export function getLink(endpointObj: Endpoint) {
if (appProtocol && appProtocol !== '') { if (appProtocol && appProtocol !== '') {
protocol = appProtocol; protocol = appProtocol;
} }
// Support to open this address in a new window directly.
if (protocol == 'tcp') {
protocol = 'http';
}
if (host == '') {
return path;
}
if (path === '/') { if (path === '/') {
path = ''; path = '';
} }