Compare commits

...

4 Commits
main ... v1.5.0

Author SHA1 Message Date
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
11 changed files with 171 additions and 93 deletions

View File

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

View File

@ -2,7 +2,7 @@ import React, { Component, Fragment } from 'react';
import { Grid, Form, Input, Field, Button, Message, Icon, Dialog } from '@b-design/ui';
import { updateUser } from '../../../../api/users';
import type { LoginUserInfo } from '../../../../interface/user';
import { checkUserPassword, checkUserEmail } from '../../../../utils/common';
import { checkUserPassword } from '../../../../utils/common';
import Translation from '../../../../components/Translation';
import i18n from '../../../../i18n';
@ -143,7 +143,7 @@ class EditPlatFormUserDialog extends Component<Props, State> {
rules: [
{
required: true,
pattern: checkUserEmail,
format: 'email',
message: <Translation>Please input a valid email</Translation>,
},
],

View File

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

View File

@ -1,4 +1,4 @@
import React, { Fragment } from 'react';
import React from 'react';
import {
Form,
Button,
@ -10,6 +10,9 @@ import {
Message,
Select,
Checkbox,
Grid,
Dropdown,
Menu,
} from '@b-design/ui';
import type { Rule } from '@alifd/field';
import { If } from 'tsx-control-statements/components';
@ -36,6 +39,12 @@ import type { NameAlias } from '../../../../interface/env';
import Permission from '../../../../components/Permission';
import 'github-markdown-css/github-markdown-light.css';
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 = {
addonName: string;
@ -58,11 +67,10 @@ type State = {
clusters?: string[];
allClusters?: NameAlias[];
enabledClusters?: string[];
endpoints?: Endpoint[];
};
class AddonDetailDialog extends React.Component<Props, State> {
timer?: number;
readonly refreshTime = 1000;
form: Field;
statusLoop: boolean;
uiSchemaRef: React.RefObject<UISchema>;
@ -86,12 +94,7 @@ class AddonDetailDialog extends React.Component<Props, State> {
componentDidMount() {
this.loadAddonDetail();
this.loadAddonStatus();
}
componentWillUnmount() {
if (this.timer) {
window.clearInterval(this.timer);
}
this.loadAddonEndpoints();
}
loadAddonDetail = async () => {
@ -116,7 +119,7 @@ class AddonDetailDialog extends React.Component<Props, State> {
getAddonsStatus({ name: this.props.addonName })
.then((res: AddonStatus) => {
if (!res) return;
if (res.phase == 'enabling' && !this.statusLoop) {
if ((res.phase == 'enabling' || res.phase === 'disabling') && !this.statusLoop) {
this.statusLoop = true;
setTimeout(() => {
this.statusLoop = false;
@ -158,26 +161,40 @@ class AddonDetailDialog extends React.Component<Props, State> {
});
};
handleSubmit = () => {
const { status } = this.state;
if (status === 'enabled') {
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;
}
if (status === 'disabled') {
this.form.validate((errors: any, values: any) => {
if (errors) {
return;
}
this.enableAddon(values.properties);
});
}
loadAddonEndpoints = () => {
// TODO: the app name and namespace should get from the api server.
const appName = 'addon-' + this.props.addonName;
listApplicationServiceEndpoints({
appName: appName,
appNs: 'vela-system',
}).then((re) => {
if (re && re.endpoints) {
this.setState({ endpoints: re.endpoints });
} else {
this.setState({ endpoints: [] });
}
});
};
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 = () => {
this.form.validate((errors: any, values: any) => {
if (errors) {
@ -278,6 +295,7 @@ class AddonDetailDialog extends React.Component<Props, State> {
clusters,
allClusters,
enabledClusters,
endpoints,
} = this.state;
const { showAddon, addonName } = this.props;
const validator = (rule: Rule, value: any, callback: (error?: string) => void) => {
@ -293,32 +311,30 @@ class AddonDetailDialog extends React.Component<Props, State> {
item.uiType = 'Password';
}
});
const buttons = [
<Fragment>
<Button type="secondary" onClick={this.onClose} style={{ marginRight: '16px' }}>
<Translation>Cancel</Translation>
</Button>
const buttons = [];
if (status === 'enabled' || status === 'enabling' || status === 'disabling') {
buttons.push(
<Permission
request={{
resource: `addon:${addonName}`,
action: status === 'enabled' ? 'disable' : 'enable',
action: 'disable',
}}
project={''}
>
<Button
type="primary"
onClick={this.handleSubmit}
warning={status === 'enabled'}
type="secondary"
onClick={this.onDisable}
title={status}
style={{ backgroundColor: status === 'enabled' ? 'red' : '' }}
loading={statusLoading || loading || status == 'enabling' || status == 'disabling'}
disabled={status == 'enabling' || status == 'disabling' || version == ''}
className="danger-btn"
loading={status === 'disabling'}
disabled={status === 'disabling'}
>
<Translation>{status === 'enabled' ? 'Disable' : 'Enable'}</Translation>
<Translation>Disable</Translation>
</Button>
</Permission>
</Fragment>,
];
</Permission>,
);
}
if (status == 'enabled' || status == 'suspend') {
buttons.push(
<Permission request={{ resource: `addon:${addonName}`, action: 'update' }} project={''}>
@ -334,6 +350,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) => {
if (!statusInfo) {
return 'notice';
@ -364,6 +395,8 @@ class AddonDetailDialog extends React.Component<Props, State> {
};
});
const outerEndpoint = endpoints?.filter((end) => !end.endpoint.inner);
return (
<div className="basic">
<DrawerWithFooter
@ -383,16 +416,52 @@ class AddonDetailDialog extends React.Component<Props, State> {
</If>
<If condition={addonsStatus && addonsStatus.status}>
<Message
type={getAppStatusShowType(addonsStatus?.status)}
size="medium"
style={{ padding: '8px', marginBottom: '10px' }}
>
{`${i18n.t('Addon status is ')}${addonsStatus?.status || 'Initing'}`}
<a style={{ marginLeft: '16px' }} onClick={() => this.updateStatusShow(true)}>
<Translation>Check the details</Translation>
</a>
</Message>
<Row>
<Col span={16}>
<Message
type={getAppStatusShowType(addonsStatus?.status)}
size="medium"
style={{ padding: '8px', marginBottom: '10px' }}
>
{`${i18n.t('Addon status is ')}${addonsStatus?.status || 'Init'}`}
<Link
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>
{/* select the addon version */}

View File

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

View File

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

View File

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

View File

@ -25,7 +25,7 @@ type Props = {
};
type State = {
dexConfig: DexConfig;
dexConfig?: DexConfig;
loginType: string;
loginErrorMessage: string;
loginLoading: boolean;
@ -36,19 +36,12 @@ export default class LoginPage extends Component<Props, State> {
super(props);
this.field = new Field(this);
this.state = {
dexConfig: {
clientID: '',
clientSecret: '',
issuer: '',
redirectURL: '',
},
loginType: '',
loginErrorMessage: '',
loginLoading: false,
};
}
componentDidMount() {
this.ontDexConfig();
this.onGetLoginType();
}
onGetLoginType = () => {
@ -62,7 +55,7 @@ export default class LoginPage extends Component<Props, State> {
() => {
const { loginType } = this.state;
if (loginType === 'dex') {
this.onGetDexCode();
this.ontDexConfig();
}
},
);
@ -74,9 +67,14 @@ export default class LoginPage extends Component<Props, State> {
getDexConfig()
.then((res) => {
if (res) {
this.setState({
dexConfig: res,
});
this.setState(
{
dexConfig: res,
},
() => {
this.onGetDexCode();
},
);
}
})
.catch();
@ -116,10 +114,12 @@ export default class LoginPage extends Component<Props, State> {
});
};
onGetDexCode = () => {
const { clientID, issuer, redirectURL } = this.state.dexConfig;
const newRedirectURl = encodeURIComponent(redirectURL);
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;
if (this.state.dexConfig) {
const { clientID, issuer, redirectURL } = this.state.dexConfig;
const newRedirectURl = encodeURIComponent(redirectURL);
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() {
const FormItem = Form.Item;

View File

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

View File

@ -1,7 +1,7 @@
import React from 'react';
import { Grid, Form, Input, Field, Button, Message, Select } from '@b-design/ui';
import DrawerWithFooter from '../../../../components/Drawer';
import { checkUserPassword, checkUserEmail } from '../../../../utils/common';
import { checkUserPassword } from '../../../../utils/common';
import Translation from '../../../../components/Translation';
import { createUser, updateUser } from '../../../../api/users';
import { checkName } from '../../../../utils/common';
@ -231,7 +231,7 @@ class CreateUser extends React.Component<Props, State> {
rules: [
{
required: true,
pattern: checkUserEmail,
format: 'email',
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 checkUserEmail = /^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$/;
export function isMatchBusinessCode(businessCode: number) {
const tokenExpiredList = [12002, 12010];