Compare commits

...

4 Commits

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 { .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,7 +2,7 @@ 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';
@ -143,7 +143,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

@ -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

@ -1,4 +1,4 @@
import React, { Fragment } from 'react'; import React from 'react';
import { import {
Form, Form,
Button, Button,
@ -10,6 +10,9 @@ import {
Message, Message,
Select, Select,
Checkbox, Checkbox,
Grid,
Dropdown,
Menu,
} 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';
@ -36,6 +39,12 @@ 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,11 +67,10 @@ type State = {
clusters?: string[]; clusters?: string[];
allClusters?: NameAlias[]; allClusters?: NameAlias[];
enabledClusters?: string[]; enabledClusters?: string[];
endpoints?: Endpoint[];
}; };
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>;
@ -86,12 +94,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 () => {
@ -116,7 +119,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 +161,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) {
@ -278,6 +295,7 @@ class AddonDetailDialog extends React.Component<Props, State> {
clusters, clusters,
allClusters, allClusters,
enabledClusters, enabledClusters,
endpoints,
} = 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 +311,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 +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) => { const getAppStatusShowType = (statusInfo: string | undefined) => {
if (!statusInfo) { if (!statusInfo) {
return 'notice'; return 'notice';
@ -364,6 +395,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 +416,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 */}

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

@ -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

@ -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];