Compare commits

...

15 Commits
main ... v1.4.6

Author SHA1 Message Date
github-actions[bot] 6567afcd1d
Fix: the integration name is required (#576)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 5cb8f78ac2)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-07-08 09:39:59 +08:00
github-actions[bot] 7266e11d3d
[Backport release-1.4] Fix: don't load provider list with default project (#573)
* Fix: don't load provider list with default project

Signed-off-by: qiaozp <chivalry.pp@gmail.com>
(cherry picked from commit db6a807204)

* fix

Signed-off-by: qiaozp <chivalry.pp@gmail.com>
(cherry picked from commit ba05078e58)

Co-authored-by: qiaozp <chivalry.pp@gmail.com>
2022-07-07 14:41:48 +08:00
github-actions[bot] 403f37da8e
Fix: no namespace show undefined (#570)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit dfb8dd16ab)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-07-06 09:53:31 +08:00
github-actions[bot] c95e4a463f
Fix: show the error message of querying the logs (#563)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit d240d47042)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-07-01 20:52:36 +08:00
github-actions[bot] 1bf3b192e5
Feat: support to view the image info (#556)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 2c9e42ae4d)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-06-21 15:00:30 +08:00
github-actions[bot] 44c43de6ec
Feat: reference the github markdown style for the addon readme (#554)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 55ae7a4864)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-06-17 15:46:41 +08:00
github-actions[bot] ed6d93c5cc
[Backport release-1.4] Feat: show the kind of the resource (#553)
* Feat: show the kind of the resource

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

* Feat: show the kind in the cluster and pod node

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

* Fix: the less code style

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

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-06-17 15:44:37 +08:00
github-actions[bot] 510bc7d8ec
Feat: support edit the component/trait/workflow properties via the code (#546)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 46e2c81e50)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-06-10 13:09:13 +08:00
github-actions[bot] 2e0b738321
Chore: get version from env (#541)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 7e5d474c5d)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-05-31 18:27:05 +08:00
github-actions[bot] 8dfeae2cc6
Fix: get the region from status (#539)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit d4c42ed147)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-05-31 17:45:15 +08:00
github-actions[bot] 171c490d7c
Fix: optimize the permission of the definition management page (#537)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit aab99402b7)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-05-31 15:30:23 +08:00
github-actions[bot] 328a212da8
Feat: default show the overview tab on the application status page (#534)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 2ea7749295)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-05-30 14:48:45 +08:00
github-actions[bot] c9006c5faf
Feat: change the title to Balloon (#532)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit a77ea37867)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-05-29 12:46:44 +08:00
github-actions[bot] 44facaa92a
Feat: Optimize the kubernetes object editing component (#530)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 0036d125dd)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-05-28 20:50:51 +08:00
github-actions[bot] 0275634270
Fix: fail to set the policy value (#528)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit f707d87cb2)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-05-27 20:12:54 +08:00
68 changed files with 1503 additions and 532 deletions

View File

@ -40,6 +40,7 @@ jobs:
push: true push: true
build-args: | build-args: |
GITVERSION=git-${{ steps.vars.outputs.git_revision }} GITVERSION=git-${{ steps.vars.outputs.git_revision }}
VERSION=${{ steps.get_version.outputs.VERSION }}
tags: |- tags: |-
acr.kubevela.net/oamdev/velaux:${{ steps.get_version.outputs.VERSION }} acr.kubevela.net/oamdev/velaux:${{ steps.get_version.outputs.VERSION }}
oamdev/velaux:${{ steps.get_version.outputs.VERSION }} oamdev/velaux:${{ steps.get_version.outputs.VERSION }}

View File

@ -1,6 +1,8 @@
FROM node:16-alpine as builder FROM node:16-alpine as builder
ARG VERSION
WORKDIR /app/velaux WORKDIR /app/velaux
ADD . . ADD . .
ENV VERSION=${VERSION}
RUN apk add --no-cache git && yarn install && yarn build RUN apk add --no-cache git && yarn install && yarn build
RUN rm -rf /app/velaux/build/mock RUN rm -rf /app/velaux/build/mock

View File

@ -89,6 +89,9 @@ function getClientEnvironment(publicUrl) {
// which is why it's disabled by default. // which is why it's disabled by default.
// It is defined here so it is available in the webpackHotDevClient. // It is defined here so it is available in the webpackHotDevClient.
FAST_REFRESH: process.env.FAST_REFRESH !== 'false', FAST_REFRESH: process.env.FAST_REFRESH !== 'false',
// The release version
VERSION: process.env.VERSION,
}, },
); );
// Stringify all values so we can feed into webpack DefinePlugin // Stringify all values so we can feed into webpack DefinePlugin

View File

@ -1,6 +1,6 @@
{ {
"name": "valaux", "name": "valaux",
"version": "1.4.0-beta.2", "version": "1.4.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node scripts/start.js", "start": "node scripts/start.js",
@ -52,6 +52,7 @@
"dva": "2.4.1", "dva": "2.4.1",
"dva-core": "2.0.4", "dva-core": "2.0.4",
"dva-loading": "3.0.22", "dva-loading": "3.0.22",
"github-markdown-css": "^5.1.0",
"i18next": "^19.8.2", "i18next": "^19.8.2",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"lodash": "4.17.21", "lodash": "4.17.21",
@ -67,6 +68,7 @@
"react-dnd-html5-backend": "^7.2.0", "react-dnd-html5-backend": "^7.2.0",
"react-dom": "^16.3.0", "react-dom": "^16.3.0",
"react-i18next": "11.13.0", "react-i18next": "11.13.0",
"react-icons": "^4.4.0",
"react-markdown": "7.1.0", "react-markdown": "7.1.0",
"react-refresh": "^0.10.0", "react-refresh": "^0.10.0",
"redux": "4.1.2", "redux": "4.1.2",
@ -86,7 +88,6 @@
"@pmmmwh/react-refresh-webpack-plugin": "0.5.1", "@pmmmwh/react-refresh-webpack-plugin": "0.5.1",
"@testing-library/react": "^12.1.2", "@testing-library/react": "^12.1.2",
"@types/chai": "^4.2.11", "@types/chai": "^4.2.11",
"dagre-compound": "0.0.11",
"@types/js-yaml": "^4.0.1", "@types/js-yaml": "^4.0.1",
"@types/lodash": "^4.14.176", "@types/lodash": "^4.14.176",
"@types/mocha": "^8.2.1", "@types/mocha": "^8.2.1",
@ -104,6 +105,7 @@
"case-sensitive-paths-webpack-plugin": "2.3.0", "case-sensitive-paths-webpack-plugin": "2.3.0",
"chai": "^4.2.0", "chai": "^4.2.0",
"css-loader": "5.2.7", "css-loader": "5.2.7",
"dagre-compound": "0.0.11",
"dotenv": "10.0.0", "dotenv": "10.0.0",
"dotenv-expand": "5.1.0", "dotenv-expand": "5.1.0",
"eslint": "^7.11.0", "eslint": "^7.11.0",

View File

@ -3,7 +3,6 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/icon.png" /> <link rel="icon" href="%PUBLIC_URL%/icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"/>
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta <meta
name="description" name="description"

View File

@ -116,6 +116,11 @@ export function getPolicyDetail(params: { appName: string; policyName: string })
return get(gurl, params).then((res) => res); return get(gurl, params).then((res) => res);
} }
export function deletePolicy(params: { appName: string; policyName: string }) {
const gurl = `${application}/${params.appName}/policies/${params.policyName}`;
return rdelete(gurl, params).then((res) => res);
}
export function createApplicationTemplate(params: any) { export function createApplicationTemplate(params: any) {
const gurl = isMock const gurl = isMock
? `${createApplicationTemplate_mock}` ? `${createApplicationTemplate_mock}`

View File

@ -45,3 +45,13 @@ export function getChartRepos(params: { project?: string }) {
} }
return get(url, { params: params }).then((res) => res); return get(url, { params: params }).then((res) => res);
} }
export function getImageRepos(params: { project: string }) {
const url = baseURL + repository + '/image/repos';
return get(url, { params: params }).then((res) => res);
}
export function getImageInfo(params: { project: string; secretName?: string; name: string }) {
const url = baseURL + repository + '/image/info';
return get(url, { params: params }).then((res) => res);
}

View File

@ -485,8 +485,8 @@ a {
border: @dangerColor 1px solid !important; border: @dangerColor 1px solid !important;
} }
.danger-btn:hover { .danger-btn:hover {
background: @dangerColor !important;
color: #fff !important; color: #fff !important;
background: @dangerColor !important;
} }
.danger-icon:hover { .danger-icon:hover {
color: @dangerColor !important; color: @dangerColor !important;

View File

@ -9,9 +9,9 @@ type Props = {
language: string; language: string;
fileUrl?: string; fileUrl?: string;
runtime?: any; runtime?: any;
'data-meta'?: string;
id?: string; id?: string;
onChange?: (params: any) => void; onChange?: (params: any) => void;
onBlurEditor?: (value: string) => void;
style?: React.CSSProperties; style?: React.CSSProperties;
}; };
@ -25,6 +25,7 @@ class DefinitionCode extends React.Component<Props> {
language, language,
readOnly, readOnly,
onChange, onChange,
onBlurEditor,
fileUrl = `//b.txt`, fileUrl = `//b.txt`,
} = this.props; } = this.props;
const container: any = document.getElementById(containerId); const container: any = document.getElementById(containerId);
@ -55,6 +56,12 @@ class DefinitionCode extends React.Component<Props> {
if (onChange) { if (onChange) {
this.editor.onDidChangeModelContent(() => onChange(textModel.getValue())); this.editor.onDidChangeModelContent(() => onChange(textModel.getValue()));
} }
if (onBlurEditor) {
this.editor.onDidBlurEditorText(() => {
onBlurEditor(textModel.getValue());
});
}
monaco.editor.setModelLanguage(textModel, language); monaco.editor.setModelLanguage(textModel, language);
} }
} }

View File

@ -1,4 +1,4 @@
.title-wraper { .title-wrapper {
height: 45px; height: 45px;
.title { .title {
font-weight: 500; font-weight: 500;
@ -14,7 +14,7 @@
} }
@media (max-width: 719px) and (min-width: 480px) { @media (max-width: 719px) and (min-width: 480px) {
.title-wraper { .title-wrapper {
.subTitle { .subTitle {
display: none; display: none;
} }

View File

@ -17,8 +17,8 @@ export default function (props: Props) {
return ( return (
<div> <div>
<Row className="title-wraper"> <Row className="title-wrapper" wrap={true}>
<Col span="15"> <Col xl={15} xs={24}>
<span className="title font-size-20"> <span className="title font-size-20">
<Translation>{title}</Translation> <Translation>{title}</Translation>
</span> </span>
@ -26,7 +26,7 @@ export default function (props: Props) {
<Translation>{subTitle}</Translation> <Translation>{subTitle}</Translation>
</span> </span>
</Col> </Col>
<Col span="9"> <Col xl={9} xs={24}>
<div className="float-right"> <div className="float-right">
{extButtons && {extButtons &&
extButtons.map((item) => { extButtons.map((item) => {

View File

@ -15,7 +15,6 @@ const Permission = (props: Props) => {
if (!props.userInfo) { if (!props.userInfo) {
return null; return null;
} }
console.log(props.request);
if (!checkPermission(props.request, props.project, props.userInfo)) { if (!checkPermission(props.request, props.project, props.userInfo)) {
return null; return null;
} }

View File

@ -53,7 +53,7 @@ class StatusShow extends React.Component<Props> {
width="150px" width="150px"
title={<Translation>Namespace/Name</Translation>} title={<Translation>Namespace/Name</Translation>}
cell={(v: string, i: number, row: Resource) => { cell={(v: string, i: number, row: Resource) => {
return `${row.namespace}/${row.name}`; return `${row.namespace || '-'}/${row.name}`;
}} }}
/> />
<Table.Column <Table.Column

View File

@ -19,14 +19,18 @@
.name { .name {
display: flex; display: flex;
flex: 1 1; flex-direction: column;
flex-wrap: wrap;
justify-content: flex-start; justify-content: flex-start;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
line-height: 40px; line-height: 20px;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
.kind {
margin: 0;
color: #a6a6a6;
font-size: 12px;
}
} }
.icon { .icon {
@ -57,15 +61,17 @@
} }
.additional { .additional {
position: absolute; position: absolute;
right: 8px;
bottom: -12px; bottom: -12px;
display: block; display: block;
right:8px
} }
} }
.graph-node-app { .graph-node-app {
padding-left: 5em; padding-left: 5em;
.name {
line-height: 40px;
}
.icon { .icon {
top: -10px; top: -10px;
left: -10px; left: -10px;
@ -87,7 +93,13 @@
.graph-node-pod { .graph-node-pod {
.icon { .icon {
display: flex;
flex-direction: column;
height: 60px; height: 60px;
span {
color: #a6a6a6;
font-size: 12px;
}
} }
.name { .name {
line-height: 30px; line-height: 30px;
@ -116,7 +128,7 @@
transition: all 0.2s linear; transition: all 0.2s linear;
&:last-child { &:last-child {
&:after { &::after {
position: absolute; position: absolute;
top: -10px; top: -10px;
color: #a3a3a3; color: #a3a3a3;
@ -127,4 +139,9 @@
} }
} }
} }
.line {
margin: 0;
padding: 0;
line-height: 16px;
}
} }

View File

@ -20,7 +20,7 @@ import pod from '../../assets/resources/pod.svg';
import kubevela from '../../assets/KubeVela-01.svg'; import kubevela from '../../assets/KubeVela-01.svg';
import { Link } from 'dva/router'; import { Link } from 'dva/router';
import { Dropdown, Icon, Menu, Tag } from '@b-design/ui'; import { Dropdown, Icon, Menu, Tag, Balloon } from '@b-design/ui';
import i18n from '../../i18n'; import i18n from '../../i18n';
import { If } from 'tsx-control-statements/components'; import { If } from 'tsx-control-statements/components';
@ -60,7 +60,7 @@ export interface TreeNode {
function renderResourceNode(props: TreeGraphProps, id: string, node: GraphNode) { function renderResourceNode(props: TreeGraphProps, id: string, node: GraphNode) {
const fullName = nodeKey(node); const fullName = nodeKey(node);
return ( const graphNode = (
<div <div
key={id} key={id}
onClick={() => props.onNodeClick && props.onNodeClick(fullName)} onClick={() => props.onNodeClick && props.onNodeClick(fullName)}
@ -68,7 +68,6 @@ function renderResourceNode(props: TreeGraphProps, id: string, node: GraphNode)
'error-status': node.resource.healthStatus?.statusCode == 'UnHealthy', 'error-status': node.resource.healthStatus?.statusCode == 'UnHealthy',
'warning-status': node.resource.healthStatus?.statusCode == 'Progressing', 'warning-status': node.resource.healthStatus?.statusCode == 'Progressing',
})} })}
title={describeNode(node)}
style={{ style={{
left: node.x, left: node.x,
top: node.y, top: node.y,
@ -81,7 +80,8 @@ function renderResourceNode(props: TreeGraphProps, id: string, node: GraphNode)
<ResourceIcon kind={node.resource.kind || ''} /> <ResourceIcon kind={node.resource.kind || ''} />
</div> </div>
<div className={classNames('name')}> <div className={classNames('name')}>
<span>{node.resource.name}</span> <div>{node.resource.name}</div>
<div className="kind">{node.resource.kind}</div>
</div> </div>
<div className={classNames('actions')}> <div className={classNames('actions')}>
<Dropdown trigger={<Icon type="ellipsis-vertical" />}> <Dropdown trigger={<Icon type="ellipsis-vertical" />}>
@ -99,17 +99,25 @@ function renderResourceNode(props: TreeGraphProps, id: string, node: GraphNode)
</If> </If>
</div> </div>
); );
return (
<Balloon trigger={graphNode}>
<div>
{describeNode(node).map((line) => {
return <p className="line">{line}</p>;
})}
</div>
</Balloon>
);
} }
function renderAppNode(props: TreeGraphProps, id: string, node: GraphNode) { function renderAppNode(props: TreeGraphProps, id: string, node: GraphNode) {
const fullName = nodeKey(node); const fullName = nodeKey(node);
return ( const graphNode = (
<div <div
key={id} key={id}
onClick={() => props.onNodeClick && props.onNodeClick(fullName)} onClick={() => props.onNodeClick && props.onNodeClick(fullName)}
className={classNames('graph-node', 'graph-node-app')} className={classNames('graph-node', 'graph-node-app')}
title={describeNode(node)}
style={{ style={{
left: node.x, left: node.x,
top: node.y, top: node.y,
@ -126,12 +134,21 @@ function renderAppNode(props: TreeGraphProps, id: string, node: GraphNode) {
</div> </div>
</div> </div>
); );
return (
<Balloon trigger={graphNode}>
<div>
{describeNode(node).map((line) => {
return <p className="line">{line}</p>;
})}
</div>
</Balloon>
);
} }
function renderPodNode(props: TreeGraphProps, id: string, node: GraphNode) { function renderPodNode(props: TreeGraphProps, id: string, node: GraphNode) {
const fullName = nodeKey(node); const fullName = nodeKey(node);
const { appName, envName } = props; const { appName, envName } = props;
return ( const graphNode = (
<div <div
key={id} key={id}
onClick={() => props.onNodeClick && props.onNodeClick(fullName)} onClick={() => props.onNodeClick && props.onNodeClick(fullName)}
@ -139,7 +156,6 @@ function renderPodNode(props: TreeGraphProps, id: string, node: GraphNode) {
'error-status': node.resource.healthStatus?.statusCode == 'UnHealthy', 'error-status': node.resource.healthStatus?.statusCode == 'UnHealthy',
'warning-status': node.resource.healthStatus?.statusCode == 'Progressing', 'warning-status': node.resource.healthStatus?.statusCode == 'Progressing',
})} })}
title={describeNode(node)}
style={{ style={{
left: node.x, left: node.x,
top: node.y, top: node.y,
@ -150,6 +166,7 @@ function renderPodNode(props: TreeGraphProps, id: string, node: GraphNode) {
> >
<div className={classNames('icon')}> <div className={classNames('icon')}>
<img src={pod} /> <img src={pod} />
<span>Pod</span>
</div> </div>
<div className={classNames('name')}> <div className={classNames('name')}>
<Link <Link
@ -172,16 +189,24 @@ function renderPodNode(props: TreeGraphProps, id: string, node: GraphNode) {
</div> </div>
</div> </div>
); );
return (
<Balloon trigger={graphNode}>
<div>
{describeNode(node).map((line) => {
return <p className="line">{line}</p>;
})}
</div>
</Balloon>
);
} }
function renderClusterNode(props: TreeGraphProps, id: string, node: GraphNode) { function renderClusterNode(props: TreeGraphProps, id: string, node: GraphNode) {
const fullName = nodeKey(node); const fullName = nodeKey(node);
return ( const graphNode = (
<div <div
onClick={() => props.onNodeClick && props.onNodeClick(fullName)} onClick={() => props.onNodeClick && props.onNodeClick(fullName)}
className={classNames('graph-node', 'graph-node-cluster')} className={classNames('graph-node', 'graph-node-cluster')}
title={describeCluster(node)}
style={{ style={{
left: node.x, left: node.x,
top: node.y, top: node.y,
@ -194,10 +219,20 @@ function renderClusterNode(props: TreeGraphProps, id: string, node: GraphNode) {
<img src={kubernetes} /> <img src={kubernetes} />
</div> </div>
<div className={classNames('name')}> <div className={classNames('name')}>
<span>{node.resource.name}</span> <div>{node.resource.name}</div>
<div className="kind">Cluster</div>
</div> </div>
</div> </div>
); );
return (
<Balloon trigger={graphNode}>
<div>
{describeCluster(node).map((line) => {
return <p className="line">{line}</p>;
})}
</div>
</Balloon>
);
} }
function setNode(graph: dagre.graphlib.Graph<GraphNode, GraphEdge>, node: TreeNode) { function setNode(graph: dagre.graphlib.Graph<GraphNode, GraphEdge>, node: TreeNode) {

View File

@ -50,7 +50,7 @@ export function describeNode(node: GraphNode) {
if (node.resource.healthStatus?.reason) { if (node.resource.healthStatus?.reason) {
lines.push(`Reason: ${node.resource.healthStatus?.reason}`); lines.push(`Reason: ${node.resource.healthStatus?.reason}`);
} }
if (node.resource.kind === 'Service') { if (node.resource.kind === 'Service' && node.resource.additionalInfo?.EIP) {
lines.push(`EIP: ${node.resource.additionalInfo?.EIP}`); lines.push(`EIP: ${node.resource.additionalInfo?.EIP}`);
} }
if (node.resource.kind === 'Pod') { if (node.resource.kind === 'Pod') {
@ -58,12 +58,12 @@ export function describeNode(node: GraphNode) {
lines.push(`Ready: ${node.resource.additionalInfo?.Ready}`); lines.push(`Ready: ${node.resource.additionalInfo?.Ready}`);
lines.push(`Restarts: ${node.resource.additionalInfo?.Restarts}`); lines.push(`Restarts: ${node.resource.additionalInfo?.Restarts}`);
} }
return lines.join('\n'); return lines;
} }
export function describeCluster(node: GraphNode) { export function describeCluster(node: GraphNode) {
const lines = [`Cluster: ${node.resource.name}`]; const lines = [`Cluster: ${node.resource.name}`];
return lines.join('\n'); return lines;
} }
export function treeNodeKey(node: TreeNode & { uid?: string }) { export function treeNodeKey(node: TreeNode & { uid?: string }) {
@ -160,6 +160,7 @@ export const ResourceIcon = ({
const style: React.CSSProperties = { const style: React.CSSProperties = {
display: 'inline-block', display: 'inline-block',
verticalAlign: 'middle', verticalAlign: 'middle',
overflow: 'hidden',
padding: `${n <= 2 ? 2 : 0}px 4px`, padding: `${n <= 2 ? 2 : 0}px 4px`,
width: '32px', width: '32px',
height: '32px', height: '32px',

View File

@ -25,6 +25,10 @@ import i18n from 'i18next';
import { getValue } from '../../utils/utils'; import { getValue } from '../../utils/utils';
import HelmRepoSelect from '../../extends/HelmRepoSelect'; import HelmRepoSelect from '../../extends/HelmRepoSelect';
import PolicySelect from '../../extends/PolicySelect'; import PolicySelect from '../../extends/PolicySelect';
import { v4 as uuid } from 'uuid';
import DefinitionCode from '../DefinitionCode';
import * as yaml from 'js-yaml';
import type { Definition } from '../../interface/addon';
const { Col, Row } = Grid; const { Col, Row } = Grid;
@ -32,6 +36,7 @@ type Props = {
inline?: boolean; inline?: boolean;
id?: string; id?: string;
value?: any; value?: any;
enableCodeEdit?: boolean;
uiSchema?: UIParam[]; uiSchema?: UIParam[];
maxColSpan?: number; maxColSpan?: number;
onChange?: (params: any) => void; onChange?: (params: any) => void;
@ -39,6 +44,7 @@ type Props = {
disableRenderRow?: boolean; disableRenderRow?: boolean;
mode: 'new' | 'edit'; mode: 'new' | 'edit';
advanced?: boolean; advanced?: boolean;
definition?: Definition;
}; };
function convertRule(validate?: UIParamValidate) { function convertRule(validate?: UIParamValidate) {
@ -88,6 +94,8 @@ function convertRule(validate?: UIParamValidate) {
type State = { type State = {
secretKeys?: string[]; secretKeys?: string[];
advanced: boolean; advanced: boolean;
codeError?: string;
codeID: string;
}; };
class UISchema extends Component<Props, State> { class UISchema extends Component<Props, State> {
@ -118,6 +126,7 @@ class UISchema extends Component<Props, State> {
this.state = { this.state = {
secretKeys: [], secretKeys: [],
advanced: props.advanced || false, advanced: props.advanced || false,
codeID: uuid(),
}; };
} }
@ -145,11 +154,16 @@ class UISchema extends Component<Props, State> {
validate = (callback: (error?: string) => void) => { validate = (callback: (error?: string) => void) => {
this.form.validate((errors) => { this.form.validate((errors) => {
const { codeError } = this.state;
if (errors) { if (errors) {
console.log(errors); console.log(errors);
callback('ui schema validate failure'); callback('ui schema validate failure');
return; return;
} }
if (codeError) {
callback('ui schema validate failure');
return;
}
callback(); callback();
}); });
}; };
@ -200,11 +214,74 @@ class UISchema extends Component<Props, State> {
return false; return false;
}; };
renderDocumentURL = () => {
const { definition } = this.props;
if (definition) {
switch (definition.type) {
case 'component':
return 'https://kubevela.net/docs/end-user/components/references#' + definition.name;
case 'trait':
return 'https://kubevela.net/docs/end-user/traits/references#' + definition.name;
case 'policy':
return 'https://kubevela.net/docs/end-user/policies/references#' + definition.name;
case 'workflowstep':
return (
'https://kubevela.net/docs/end-user/workflow/built-in-workflow-defs#' + definition.name
);
}
}
};
renderCodeEdit = () => {
const { value, onChange, definition } = this.props;
const { codeError, codeID } = this.state;
let yamlValue = yaml.dump(value);
if (yamlValue == '{}\n') {
yamlValue = '';
}
return (
<div style={{ width: '100%' }}>
<If condition={codeError}>
<span style={{ color: 'red' }}>{codeError}</span>
</If>
<If condition={definition}>
<p>
Refer to the document:
<a style={{ marginLeft: '8px' }} target="_blank" href={this.renderDocumentURL()}>
click here
</a>
</p>
</If>
<div id={codeID} className="guide-code">
<DefinitionCode
value={yamlValue}
onBlurEditor={(v) => {
if (onChange) {
try {
const valueObj = yaml.load(v);
onChange(valueObj);
this.setState({ codeError: '' });
} catch (err) {
this.setState({ codeError: 'Please input a valid yaml config:' + err });
}
}
}}
id={codeID + '-code'}
containerId={codeID}
language={'yaml'}
readOnly={false}
/>
</div>
</div>
);
};
render() { render() {
const { advanced } = this.state; const { advanced } = this.state;
const { uiSchema, inline, maxColSpan, disableRenderRow, value, mode } = this.props; const { uiSchema, inline, maxColSpan, disableRenderRow, value, mode, enableCodeEdit } =
if (!uiSchema) { this.props;
return <div />; if (!uiSchema || enableCodeEdit) {
return this.renderCodeEdit();
} }
if (mode == 'edit' && value === undefined) { if (mode == 'edit' && value === undefined) {
return <div />; return <div />;
@ -405,14 +482,16 @@ class UISchema extends Component<Props, State> {
</Form.Item> </Form.Item>
); );
case 'ImageInput': case 'ImageInput':
const imagePullSecretsValue = value && value.imagePullSecrets;
const initResult = init('imagePullSecrets', {
initValue: imagePullSecretsValue,
rules: [],
});
return ( return (
<Form.Item
required={required}
label={label}
help={<div dangerouslySetInnerHTML={{ __html: replaceUrl(description || '') }} />}
key={param.jsonKey}
>
<ImageInput <ImageInput
label={label}
key={param.jsonKey}
required={required || false}
disabled={disableEdit} disabled={disableEdit}
{...init(param.jsonKey, { {...init(param.jsonKey, {
initValue: initValue, initValue: initValue,
@ -424,8 +503,10 @@ class UISchema extends Component<Props, State> {
}, },
], ],
})} })}
secretValue={initResult.value}
onSecretChange={initResult.onChange}
secretID={initResult.id}
/> />
</Form.Item>
); );
case 'HelmChartSelect': case 'HelmChartSelect':
return ( return (

View File

@ -4,7 +4,7 @@ import { Loading, Select } from '@b-design/ui';
import i18n from '../../i18n'; import i18n from '../../i18n';
import locale from '../../utils/locale'; import locale from '../../utils/locale';
import { connect } from 'dva'; import { connect } from 'dva';
import type { HelmRepo } from '../../interface/application'; import type { HelmRepo } from '../../interface/repository';
type Props = { type Props = {
value?: any; value?: any;

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { getChartVersions } from '../../api/repository'; import { getChartVersions } from '../../api/repository';
import { Loading, Select } from '@b-design/ui'; import { Loading, Select } from '@b-design/ui';
import type { ChartVersion, HelmRepo } from '../../interface/application'; import type { ChartVersion, HelmRepo } from '../../interface/repository';
import i18n from '../../i18n'; import i18n from '../../i18n';
import locale from '../../utils/locale'; import locale from '../../utils/locale';
import { connect } from 'dva'; import { connect } from 'dva';

View File

@ -3,7 +3,7 @@ import { Loading, Select } from '@b-design/ui';
import i18n from '../../i18n'; import i18n from '../../i18n';
import locale from '../../utils/locale'; import locale from '../../utils/locale';
import { getChartRepos } from '../../api/repository'; import { getChartRepos } from '../../api/repository';
import type { HelmRepo } from '../../interface/application'; import type { HelmRepo } from '../../interface/repository';
import { connect } from 'dva'; import { connect } from 'dva';
import _ from 'lodash'; import _ from 'lodash';

View File

@ -1,7 +1,8 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
import _ from 'lodash'; import _ from 'lodash';
import type { HelmRepo, UIParam } from '../../interface/application'; import type { UIParam } from '../../interface/application';
import type { HelmRepo } from '../../interface/repository';
import KV from '../KV'; import KV from '../KV';
import { getChartValues } from '../../api/repository'; import { getChartValues } from '../../api/repository';
import { Loading } from '@b-design/ui'; import { Loading } from '@b-design/ui';

View File

@ -1,3 +1,51 @@
@import '../../lib.less';
.image-input-container { .image-input-container {
display: flex; display: flex;
} }
.image-info {
width: 100%;
min-height: 40px;
padding: 8px;
overflow-x: hidden;
background: rgba(171, 205, 239, 0.2);
.container-base {
display: flex;
padding: 8px;
.docker-base {
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.docker-logo {
width: 50px;
margin: 0 16px 0 0;
}
.name {
display: flex;
flex-direction: column;
font-weight: 500;
font-size: 14px;
.desc {
padding: 0 8px;
}
}
.desc {
margin-top: 8px;
font-size: 14px;
}
}
.image-item {
display: flex;
padding: 8px;
svg {
margin-right: 6px;
font-size: 24px;
line-height: 60px;
}
}
.message {
color: @dangerColor;
}
}

View File

@ -1,31 +1,200 @@
import React from 'react'; import React from 'react';
import { Input } from '@b-design/ui'; import { Input, Form, Loading, Grid, Tag } from '@b-design/ui';
import './index.less'; import './index.less';
import type { InputProps } from '@alifd/next/types/input'; import type { InputProps } from '@alifd/next/types/input';
import { getImageInfo } from '../../api/repository';
import { connect } from 'dva';
import { Link } from 'dva/router';
import type { ImageInfo } from '../../interface/repository';
import { If } from 'tsx-control-statements/components';
import { AiOutlineHdd, AiOutlineExport } from 'react-icons/ai';
import { TbBrandDocker } from 'react-icons/tb';
import dockerLogo from '../../assets/docker.svg';
import { beautifyTime, beautifyBinarySize } from '../../utils/common';
import ImageSecretSelect from '../ImageSecretSelect';
const { Col, Row } = Grid;
interface Props extends InputProps { interface Props extends InputProps {
value: any; value: any;
key: string;
id: string; id: string;
label: string;
required: boolean;
onChange: (value: any) => void; onChange: (value: any) => void;
disabled: boolean; disabled: boolean;
project?: string;
secretValue?: any;
onSecretChange: (value?: string[]) => void;
secretID: string;
} }
type State = {}; type State = {
imageInfo?: ImageInfo;
imageName: string;
loading: boolean;
};
@connect((store: any) => {
return { ...store.uischema };
})
class ImageInput extends React.Component<Props, State> { class ImageInput extends React.Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {
dataSource: [], imageName: '',
loading: false,
}; };
} }
componentDidMount = async () => {}; componentDidMount = async () => {};
loadImageInfo = () => {
const { project, onChange, onSecretChange } = this.props;
const { imageName } = this.state;
if (project && imageName) {
onChange(imageName);
this.setState({ loading: true });
getImageInfo({ project: project, name: imageName })
.then((res: ImageInfo) => {
if (res) {
this.setState({ imageInfo: res });
if (res.secretNames) {
onSecretChange(res.secretNames);
} else {
onSecretChange(undefined);
}
}
})
.finally(() => {
this.setState({ loading: false });
});
}
};
render() { render() {
const { value } = this.props; const { value, id, required, label, key, onSecretChange, secretValue, disabled, secretID } =
return <Input {...this.props} defaultValue={value} />; this.props;
const { loading, imageInfo } = this.state;
if (!this.state.imageName && value) {
this.setState({ imageName: value }, () => {
this.loadImageInfo();
});
}
let secrets = secretValue;
let secretDisabled = disabled;
if (imageInfo && imageInfo.secretNames) {
secrets = imageInfo.secretNames;
secretDisabled = true;
}
return (
<div>
<Row>
<Col span={16} style={{ padding: '0 16px 0 0' }}>
<Form.Item
required={required}
label={label}
key={key}
help={
<span>
To deploy from a private registry, you need to{' '}
<Link to="/integrations/config-image-registry/config">
create a integration configure
</Link>
</span>
}
>
<Input
id={id}
onChange={(name: string) => {
this.setState({ imageName: name });
}}
disabled={disabled}
defaultValue={value}
onBlur={this.loadImageInfo}
/>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label={'Secret'}>
<ImageSecretSelect
id={secretID}
disabled={secretDisabled}
onChange={onSecretChange}
value={secrets}
/>
</Form.Item>
</Col>
</Row>
<Loading visible={loading} style={{ width: '100%' }}>
<If condition={imageInfo}>
<div className="image-info">
<If condition={imageInfo?.info}>
<Row>
<Col xl={24} className="container-base">
<img className="docker-logo" src={dockerLogo} />
<div className="docker-base">
<div>
<span className="name">{imageInfo?.name}</span>
</div>
<div className="desc">
<span title={imageInfo?.info?.created}>
{beautifyTime(imageInfo?.info?.created)}
</span>
<span style={{ marginLeft: '8px' }}>
{beautifyBinarySize(imageInfo?.size || 0)}
</span>
<If condition={imageInfo?.info?.architecture}>
<span style={{ marginLeft: '8px' }}>{imageInfo?.info?.architecture}</span>
</If>
</div>
</div>
</Col>
</Row>
<Row>
<Col className="image-item" xl={8}>
<div className="image-item-icon">
<AiOutlineHdd title="Volume" />
</div>
<div className="name">
{imageInfo?.info?.config?.Volumes
? Object.keys(imageInfo?.info?.config?.Volumes).map((path) => {
return <Tag color="green">{path}</Tag>;
})
: 'No default Volume config'}
</div>
</Col>
<Col className="image-item" xl={8}>
<div className="image-item-icon">
<AiOutlineExport title="ExposedPorts" />
</div>
<div className="name">
{imageInfo?.info?.config?.ExposedPorts
? Object.keys(imageInfo?.info?.config?.ExposedPorts).map((port) => {
return <Tag color="blue">{port}</Tag>;
})
: 'No default Port config'}
</div>
</Col>
<Col className="image-item" xl={8}>
<div className="image-item-icon">
<TbBrandDocker />
</div>
<div className="name">
<Tag title={imageInfo?.registry}>{imageInfo?.registry}</Tag>
</div>
</Col>
</Row>
</If>
<If condition={imageInfo?.message}>
<div className="message">{imageInfo?.message}</div>
</If>
</div>
</If>
</Loading>
</div>
);
} }
} }

View File

@ -0,0 +1,101 @@
import React, { Component } from 'react';
import { Loading, Select } from '@b-design/ui';
import i18n from '../../i18n';
import locale from '../../utils/locale';
import { getImageRepos } from '../../api/repository';
import type { ImageRegistry } from '../../interface/repository';
import { connect } from 'dva';
type Props = {
value?: any;
onChange: (value: any) => void;
id: string;
disabled: boolean;
dispatch?: ({}) => {};
project?: string;
imageSecret?: string;
};
type State = {
registries: ImageRegistry[];
inputRepo: string;
loading: boolean;
values?: string[];
};
@connect((store: any) => {
return { ...store.uischema };
})
class ImageSecretSelect extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
loading: false,
registries: [],
inputRepo: '',
};
}
componentDidMount() {
this.onLoadRepos();
}
onLoadRepos = () => {
const { project } = this.props;
if (project) {
this.setState({ loading: true });
getImageRepos({ project: project })
.then((res) => {
if (res) {
this.setState({
registries: res.registries,
});
}
})
.finally(() => {
this.setState({ loading: false });
});
}
};
onSearch = (value: string) => {
this.setState({ inputRepo: value });
};
convertImageRegistryOptions(data: ImageRegistry[]): { label: string; value: string }[] {
return (data || []).map((item: ImageRegistry) => {
let label = item.secretName;
if (item.domain) {
label = `${item.secretName}(${item.domain})`;
}
return { label: label, value: item.secretName };
});
}
render() {
const { disabled, onChange, value } = this.props;
const { registries, loading, inputRepo } = this.state;
const dataSource = registries;
if (inputRepo) {
dataSource.unshift({ secretName: inputRepo, name: inputRepo });
}
const transDataSource = this.convertImageRegistryOptions(dataSource);
return (
<Loading visible={loading} style={{ width: '100%' }}>
<Select
placeholder={i18n.t('Please select or input your owner image registry secret')}
inputMode="url"
mode="multiple"
onChange={onChange}
disabled={disabled}
showSearch={true}
onSearch={this.onSearch}
followTrigger={true}
value={value}
dataSource={transDataSource}
locale={locale().Select}
/>
</Loading>
);
}
}
export default ImageSecretSelect;

View File

@ -4,7 +4,9 @@ import { Upload, Button, Icon, Message } from '@b-design/ui';
import DefinitionCode from '../../components/DefinitionCode'; import DefinitionCode from '../../components/DefinitionCode';
import Translation from '../../components/Translation'; import Translation from '../../components/Translation';
import * as yaml from 'js-yaml'; import * as yaml from 'js-yaml';
import { v4 as uuid } from 'uuid';
import { If } from 'tsx-control-statements/components'; import { If } from 'tsx-control-statements/components';
import type { KubernetesObject } from './objects';
type Props = { type Props = {
value: any; value: any;
@ -15,6 +17,7 @@ type Props = {
type State = { type State = {
message: string; message: string;
containerId: string; containerId: string;
showButton: boolean;
}; };
class K8sObjectsCode extends React.Component<Props, State> { class K8sObjectsCode extends React.Component<Props, State> {
@ -23,7 +26,8 @@ class K8sObjectsCode extends React.Component<Props, State> {
super(props); super(props);
this.state = { this.state = {
message: '', message: '',
containerId: Date.now().toString(), containerId: uuid(),
showButton: false,
}; };
this.form = new Field(this, { this.form = new Field(this, {
onChange: () => { onChange: () => {
@ -37,17 +41,20 @@ class K8sObjectsCode extends React.Component<Props, State> {
const { value } = this.props; const { value } = this.props;
this.setValues(value); this.setValues(value);
}; };
componentWillReceiveProps(nextProps: Props) {
const { value } = nextProps;
if (value !== this.props.value) {
this.setValues(value);
}
}
setValues = (value: any) => { setValues = (value: KubernetesObject[]) => {
if (value) { if (value) {
try { try {
const code = yaml.dump(value); let code = '---\n';
if (value instanceof Array) {
value.map((res) => {
if (res) {
code = code + yaml.dump(res) + '---\n';
}
});
} else {
code = yaml.dump(value) + '---\n';
}
this.form.setValues({ code: code }); this.form.setValues({ code: code });
} catch {} } catch {}
} }
@ -57,10 +64,11 @@ class K8sObjectsCode extends React.Component<Props, State> {
const { onChange, value } = this.props; const { onChange, value } = this.props;
if (onChange) { if (onChange) {
try { try {
let object = yaml.load(v); let object: any = yaml.load(v);
if (!(object instanceof Array)) { if (!(object instanceof Array)) {
object = [object]; object = [object];
} }
object = object.filter((ob: any) => ob != null);
if (yaml.dump(value) != v) { if (yaml.dump(value) != v) {
onChange(object); onChange(object);
} }
@ -68,11 +76,14 @@ class K8sObjectsCode extends React.Component<Props, State> {
} catch (error: any) { } catch (error: any) {
if ((error.message = 'expected a single document in the stream, but found more')) { if ((error.message = 'expected a single document in the stream, but found more')) {
try { try {
const objects = yaml.loadAll(v); let objects = yaml.loadAll(v);
if (yaml.dump(value) != v) { if (yaml.dump(value) != v) {
objects = objects.filter((ob: any) => ob != null);
onChange(objects); onChange(objects);
} }
this.setState({ message: '' }); this.setState({
message: '',
});
} catch (err: any) { } catch (err: any) {
this.setState({ message: err.message }); this.setState({ message: err.message });
} }
@ -96,15 +107,25 @@ class K8sObjectsCode extends React.Component<Props, State> {
}; };
}; };
onConvert2WebService = () => {};
render() { render() {
const { id } = this.props; const { id } = this.props;
const { init } = this.form; const { init } = this.form;
const { message, containerId } = this.state; const { message, containerId, showButton } = this.state;
return ( return (
<div id={id}> <div id={id}>
<If condition={message}> <If condition={message}>
<span style={{ color: 'red' }}>{message}</span> <span style={{ color: 'red' }}>{message}</span>
</If> </If>
<Message type="notice" style={{ marginTop: '16px' }}>
<Translation>
The input data will be automatically formatted. Ensure that the input data is a valid
k8s resource YAML.
</Translation>
</Message>
<Upload request={this.customRequest}> <Upload request={this.customRequest}>
<Button text type="normal" className="padding-left-0"> <Button text type="normal" className="padding-left-0">
<Icon type="cloudupload" /> <Icon type="cloudupload" />
@ -120,12 +141,19 @@ class K8sObjectsCode extends React.Component<Props, State> {
{...init('code')} {...init('code')}
/> />
</div> </div>
<Message type="notice" style={{ marginTop: '16px' }}>
<If condition={showButton}>
<div style={{ marginTop: '16px' }}>
<span style={{ fontSize: '14px', color: '#000', marginRight: '16px' }}>
<Translation> <Translation>
The input data will be automatically formatted. Ensure that the input data is a valid Convert the kubernetes resource component to the webservice component?
k8s resource YAML.
</Translation> </Translation>
</Message> </span>
<Button type="secondary" onClick={this.onConvert2WebService}>
Yes
</Button>
</div>
</If>
</div> </div>
); );
} }

View File

@ -0,0 +1,27 @@
import type { ResourceObject } from '../../interface/observation';
export interface KubernetesObject extends ResourceObject {
apiVersion: string;
kind: string;
spec?: Record<string, any>;
}
export function isDeployment(object: KubernetesObject): boolean {
return object.kind === 'Deployment';
}
export function isShowConvertButton(objects: KubernetesObject[]): boolean {
return objects.filter((ob) => isDeployment(ob)).length > 0;
}
export function buildWebServiceBaseDeployment(object: KubernetesObject): Record<string, any> {
return {
image: object.spec?.containers[0].image,
ports: [],
readinessProbe: {},
livenessProbe: {},
env: [],
memory: '',
cpu: '',
};
}

View File

@ -42,9 +42,12 @@ class PolicySelect extends React.Component<Props, State> {
}) })
.then((res) => { .then((res) => {
if (res && res.policies) { if (res && res.policies) {
const policyListData = (res.policies || []).map( const policyListData = (res.policies || []).map((item: ApplicationPolicyBase) => {
(item: ApplicationPolicyBase) => `${item.name}(${item.type})`, return {
); label: `${item.name}(${item.type})`,
value: item.name,
};
});
this.setState({ this.setState({
policySelectDataSource: policyListData, policySelectDataSource: policyListData,
}); });

View File

@ -41,7 +41,11 @@ export interface ApplicationBase {
} }
export interface DefinitionDetail { export interface DefinitionDetail {
name: string;
description: string;
uiSchema: UIParam[]; uiSchema: UIParam[];
labels: Record<string, string>;
status: string;
} }
export interface UIParam { export interface UIParam {
@ -86,20 +90,6 @@ export interface UIParamValidate {
immutable?: boolean; immutable?: boolean;
} }
export interface ImageInfo {
imageURL: string;
imageName: string;
tag: string;
repoHost: string;
namespace: string;
ports: number[];
volumes: ImageVolume[];
}
export interface ImageVolume {
mountPath: string;
}
export interface ApplicationDeployRequest { export interface ApplicationDeployRequest {
appName: string; appName: string;
workflowName?: string; workflowName?: string;
@ -324,26 +314,6 @@ export interface TraitDefinitionSpec {
podDisruptive?: boolean; podDisruptive?: boolean;
appliesToWorkloads?: string[]; appliesToWorkloads?: string[];
} }
export interface ChartVersion {
name: string;
version: string;
description?: string;
apiVersion: string;
appVersion: string;
type?: string;
urls: string[];
created: string;
digest: string;
icon?: string;
}
export interface HelmRepo {
url: string;
type: string;
secretName?: string;
}
export interface ApplicationQuery { export interface ApplicationQuery {
query?: string; query?: string;
project?: string; project?: string;

View File

@ -93,6 +93,8 @@ export interface Metadata {
resourceVersion: string; resourceVersion: string;
selfLink: string; selfLink: string;
uid: string; uid: string;
annotations?: Record<string, string>;
labels?: Record<string, string>;
} }
export interface ManagedFields { export interface ManagedFields {
@ -111,28 +113,22 @@ export interface CloudResource {
status: string; status: string;
} }
export interface Configuration { export interface Configuration extends ResourceObject {
apiVersion: string; apiVersion: string;
kind: string; kind: string;
metadata: {
name: string;
namespace: string;
creationTimestamp: string;
annotations: any;
labels: any;
};
spec: { spec: {
region: string;
providerRef: { providerRef: {
name: string; name: string;
namespace: string; namespace: string;
}; };
region?: string;
}; };
status?: { status?: {
apply?: { apply?: {
outputs?: any; outputs?: any;
state?: string; state?: string;
message?: string; message?: string;
region?: string;
}; };
}; };
} }

View File

@ -0,0 +1,57 @@
export interface HelmRepo {
url: string;
type: string;
secretName?: string;
}
export interface ChartVersion {
name: string;
version: string;
description?: string;
apiVersion: string;
appVersion: string;
type?: string;
urls: string[];
created: string;
digest: string;
icon?: string;
}
export interface ImageInfo {
info?: {
architecture: string;
config?: {
ArgsEscaped: boolean;
Cmd: string[];
Env: string[];
Labels: Record<string, string>;
Volumes: Record<string, any>;
ExposedPorts: Record<string, any>;
};
manifest?: {
config: {
size: number;
mediaType: string;
};
layers: {
mediaType: string;
size: number;
}[];
};
created: string;
os: string;
docker_version: string;
author?: string;
};
size: number;
name: string;
secretNames?: string[];
registry: string;
message?: string;
}
export interface ImageRegistry {
name?: string;
secretName: string;
domain?: string;
}

View File

@ -50,6 +50,13 @@ class TabsContent extends Component<Props, State> {
payload: { appName: appName }, payload: { appName: appName },
}); });
}; };
loadApplicationPolicies = async () => {
const { appName } = this.props;
this.props.dispatch({
type: 'application/getApplicationPolicies',
payload: { appName: appName },
});
};
render() { render() {
const { activeKey, applicationDetail, envbinding } = this.props; const { activeKey, applicationDetail, envbinding } = this.props;
const { visibleEnvPlan } = this.state; const { visibleEnvPlan } = this.state;
@ -128,6 +135,7 @@ class TabsContent extends Component<Props, State> {
onOK={() => { onOK={() => {
this.loadEnvbinding(); this.loadEnvbinding();
this.loadApplicationWorkflows(); this.loadApplicationWorkflows();
this.loadApplicationPolicies();
this.setState({ visibleEnvPlan: false }); this.setState({ visibleEnvPlan: false });
}} }}
/> />

View File

@ -65,6 +65,8 @@ const LeftMenu = (props: Props) => {
return null; return null;
}); });
const releaseVersion = process.env.VERSION || version;
return ( return (
<div style={{ position: 'relative', height: '100%' }}> <div style={{ position: 'relative', height: '100%' }}>
<div className="slide-wrapper"> <div className="slide-wrapper">
@ -74,7 +76,7 @@ const LeftMenu = (props: Props) => {
align="t" align="t"
trigger={ trigger={
<div className="nav-container"> <div className="nav-container">
{version}-{__COMMIT_HASH__} {releaseVersion}-{__COMMIT_HASH__}
</div> </div>
} }
> >

View File

@ -75,7 +75,7 @@ export function getLeftSlider(pathname) {
link: '/definitions', link: '/definitions',
iconType: 'database-set', iconType: 'database-set',
navName: 'Definitions', navName: 'Definitions',
permission: { resource: 'definition:*', action: 'update' }, permission: { resource: 'definition:*', action: 'list' },
}, },
], ],
}, },

View File

@ -77,8 +77,8 @@
background-color: #f7f7f7; background-color: #f7f7f7;
} }
@media screen and (max-width: 768px) { @media (max-width: 980px) {
.layout-navigation { .layout-navigation {
min-width: 180px; width: 180px;
} }
} }

View File

@ -552,5 +552,7 @@
"Your component type does not support the image field, and the image update cannot be performed": "选择的组件不存在 image 字段,无法进行镜像更新", "Your component type does not support the image field, and the image update cannot be performed": "选择的组件不存在 image 字段,无法进行镜像更新",
"Please select a component": "请选择一个组件", "Please select a component": "请选择一个组件",
"Please select a workflow": "请选择一个工作流", "Please select a workflow": "请选择一个工作流",
"Please select a payloadType": "请选择请求体类型,不同的外部系统请求参数类型具有差异" "Please select a payloadType": "请选择请求体类型,不同的外部系统请求参数类型具有差异",
"Resource Graph": "资源拓扑图",
"The ImagePullSecrets property has been automatically assigned": "发现镜像仓库配置,已自动为 ImagePullSecrets 字段赋值"
} }

View File

@ -54,6 +54,17 @@ class CardContent extends React.Component<Props, State> {
return ''; return '';
} }
}; };
const nameUpper = (name: string) => {
return name
.split('-')
.map((sep) => {
if (sep.length > 0) {
return sep.toUpperCase()[0];
}
})
.toString()
.replace(',', '');
};
return ( return (
<div> <div>
<If condition={addonLists}> <If condition={addonLists}>
@ -72,7 +83,23 @@ class CardContent extends React.Component<Props, State> {
<img src={icon} /> <img src={icon} />
</If> </If>
<If condition={!icon || icon === 'none'}> <If condition={!icon || icon === 'none'}>
<img /> <div
style={{
display: 'inline-block',
verticalAlign: 'middle',
padding: `2px 4px`,
width: '60px',
height: '60px',
borderRadius: '50%',
backgroundColor: '#fff',
textAlign: 'center',
lineHeight: '60px',
}}
>
<span style={{ color: '#1b58f4', fontSize: `2em` }}>
{nameUpper(name)}
</span>
</div>
</If> </If>
</div> </div>
</a> </a>

View File

@ -7,3 +7,19 @@
width: 25%; width: 25%;
} }
} }
.addon-readme {
font-size: 14px !important;
img {
width: 100%;
}
ol, ul {
list-style: decimal;
}
p {
font-size: 14px;
}
}
.addon-readme li>p {
font-size: 14px;
}

View File

@ -22,7 +22,6 @@ import {
} from '../../../../api/addons'; } from '../../../../api/addons';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
import './index.less';
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 Group from '../../../../extends/Group';
@ -35,6 +34,8 @@ import type { ApplicationStatus } 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 './index.less';
type Props = { type Props = {
addonName: string; addonName: string;
@ -515,11 +516,12 @@ class AddonDetailDialog extends React.Component<Props, State> {
<Card <Card
contentHeight="auto" contentHeight="auto"
locale={locale().Card} locale={locale().Card}
title={<Translation>Readme</Translation>} title={<Translation>README</Translation>}
style={{ marginTop: '16px' }} style={{ marginTop: '16px' }}
> >
<If condition={addonDetailInfo?.detail}> <If condition={addonDetailInfo?.detail}>
<ReactMarkdown <ReactMarkdown
className="markdown-body addon-readme"
children={addonDetailInfo?.detail || ''} children={addonDetailInfo?.detail || ''}
remarkPlugins={[remarkGfm]} remarkPlugins={[remarkGfm]}
/> />

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Grid, Field, Form, Message, Button, Input, Select, Divider } from '@b-design/ui'; import { Grid, Field, Form, Message, Button, Input, Select, Divider, Icon } from '@b-design/ui';
import type { Rule } from '@alifd/field'; import type { Rule } from '@alifd/field';
import { withTranslation } from 'react-i18next'; import { withTranslation } from 'react-i18next';
import { import {
@ -50,6 +50,7 @@ type State = {
isUpdateComponentLoading: boolean; isUpdateComponentLoading: boolean;
editComponent?: ApplicationComponent; editComponent?: ApplicationComponent;
loading: boolean; loading: boolean;
propertiesMode: string;
}; };
@connect() @connect()
@ -63,6 +64,7 @@ class ComponentDialog extends React.Component<Props, State> {
isCreateComponentLoading: false, isCreateComponentLoading: false,
isUpdateComponentLoading: false, isUpdateComponentLoading: false,
loading: true, loading: true,
propertiesMode: 'native',
}; };
this.uiSchemaRef = React.createRef(); this.uiSchemaRef = React.createRef();
} }
@ -336,7 +338,7 @@ class ComponentDialog extends React.Component<Props, State> {
const FormItem = Form.Item; const FormItem = Form.Item;
const { Row, Col } = Grid; const { Row, Col } = Grid;
const { isEditComponent, componentDefinitions, onComponentClose } = this.props; const { isEditComponent, componentDefinitions, onComponentClose } = this.props;
const { definitionDetail, loading } = this.state; const { definitionDetail, loading, propertiesMode } = this.state;
const validator = (rule: Rule, value: any, callback: (error?: string) => void) => { const validator = (rule: Rule, value: any, callback: (error?: string) => void) => {
this.uiSchemaRef.current?.validate(callback); this.uiSchemaRef.current?.validate(callback);
}; };
@ -487,10 +489,43 @@ class ComponentDialog extends React.Component<Props, State> {
</Row> </Row>
</Group> </Group>
<section className="title"> <section className="title">
<Title title={i18n.t('Deployment Properties')} actions={[]} /> <Title
title={i18n.t('Deployment Properties')}
actions={
definitionDetail && definitionDetail.uiSchema
? [
<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 code mode'}
/>
</If>
<If condition={propertiesMode === 'code'}>
<Icon
style={{ color: '#1b58f4' }}
type={'laptop'}
title={'Switch to native mode'}
/>
</If>
</Button>,
]
: []
}
/>
</section> </section>
<Row> <Row>
<If condition={definitionDetail && definitionDetail.uiSchema}> <If condition={definitionDetail}>
<UISchema <UISchema
{...init(`properties`, { {...init(`properties`, {
rules: [ rules: [
@ -500,7 +535,13 @@ class ComponentDialog extends React.Component<Props, State> {
}, },
], ],
})} })}
enableCodeEdit={propertiesMode === 'code'}
uiSchema={definitionDetail && definitionDetail.uiSchema} uiSchema={definitionDetail && definitionDetail.uiSchema}
definition={{
name: definitionDetail?.name || '',
type: 'component',
description: definitionDetail?.description || '',
}}
ref={this.uiSchemaRef} ref={this.uiSchemaRef}
mode={isEditComponent ? 'edit' : 'new'} mode={isEditComponent ? 'edit' : 'new'}
/> />

View File

@ -1,4 +1,3 @@
.cloud-component-show { .cloud-component-show {
border-top: #1b58f4 2px solid; border-top: #1b58f4 2px solid;
} }
@ -7,11 +6,11 @@
justify-content: space-between; justify-content: space-between;
padding: 4px 0; padding: 4px 0;
.components-list-title { .components-list-title {
display: flex;
height: 24px; height: 24px;
color: #1b58f4; color: #1b58f4;
font-size: 16px; font-size: 16px;
cursor: pointer; cursor: pointer;
display: flex;
} }
} }
.components-list-operation { .components-list-operation {
@ -33,6 +32,7 @@
line-height: 37px; line-height: 37px;
} }
.trait-icon { .trait-icon {
position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: start; justify-content: start;
@ -41,16 +41,15 @@
border: 1px solid rgba(0, 0, 0, 0.1); border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
position: relative;
.trait-actions { .trait-actions {
background: rgba(166, 166, 166, 0.9);
display:none;
color: #1b58f4;
position: absolute; position: absolute;
right: 0; right: 0;
padding: 9.5px; display: none;
width: 40px; width: 40px;
padding: 9.5px;
color: #1b58f4;
text-align: center; text-align: center;
background: rgba(166, 166, 166, 0.9);
border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0;
} }
} }
@ -64,4 +63,3 @@
width: 20px; width: 20px;
margin-right: 4px; margin-right: 4px;
} }

View File

@ -17,4 +17,3 @@
font-size: 16px; font-size: 16px;
} }
} }

View File

@ -1,5 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Card, Dialog, Grid } from '@b-design/ui'; import { Card, Dialog, Grid, Icon } from '@b-design/ui';
import type { import type {
ApplicationDetail, ApplicationDetail,
ApplicationPolicyBase, ApplicationPolicyBase,
@ -12,6 +12,7 @@ import Empty from '../../../../components/Empty';
import Translation from '../../../../components/Translation'; import Translation from '../../../../components/Translation';
import locale from '../../../../utils/locale'; import locale from '../../../../utils/locale';
import Item from '../../../../components/Item'; import Item from '../../../../components/Item';
import Permission from '../../../../components/Permission';
// import Permission from '../../../../components/Permission'; // import Permission from '../../../../components/Permission';
type Props = { type Props = {
@ -43,13 +44,13 @@ class PolicyList extends Component<Props, State> {
render() { render() {
const { Row, Col } = Grid; const { Row, Col } = Grid;
const { policies, envbinding } = this.props; const { policies, envbinding, applicationDetail } = this.props;
const envNameAlias: any = {}; const envNameAlias: any = {};
envNameAlias[''] = '-'; envNameAlias[''] = '-';
envbinding?.map((item) => { envbinding?.map((item) => {
envNameAlias[item.name] = item.alias; envNameAlias[item.name] = item.alias;
}); });
//const projectName = applicationDetail && applicationDetail.project?.name; const projectName = applicationDetail && applicationDetail.project?.name;
return ( return (
<div className="list-warper"> <div className="list-warper">
<div className="box"> <div className="box">
@ -63,7 +64,7 @@ class PolicyList extends Component<Props, State> {
{item.alias ? `${item.alias}(${item.name})` : item.name} {item.alias ? `${item.alias}(${item.name})` : item.name}
{/* </a> */} {/* </a> */}
</div> </div>
{/* <div className="trigger-list-operation"> <div className="trigger-list-operation">
<Permission <Permission
request={{ request={{
resource: `project:${projectName}/application/policy:${item.name}`, resource: `project:${projectName}/application/policy:${item.name}`,
@ -80,7 +81,7 @@ class PolicyList extends Component<Props, State> {
}} }}
/> />
</Permission> </Permission>
</div> */} </div>
</div> </div>
<div className="policy-list-content"> <div className="policy-list-content">
<Row wrap={true}> <Row wrap={true}>

View File

@ -47,6 +47,7 @@ type State = {
traitDefinitions?: DefinitionBase[]; traitDefinitions?: DefinitionBase[];
podDisruptive?: any; podDisruptive?: any;
component?: ApplicationComponent; component?: ApplicationComponent;
propertiesMode: 'native' | 'code';
}; };
@connect() @connect()
class TraitDialog extends React.Component<Props, State> { class TraitDialog extends React.Component<Props, State> {
@ -58,6 +59,7 @@ class TraitDialog extends React.Component<Props, State> {
definitionLoading: false, definitionLoading: false,
isLoading: false, isLoading: false,
traitDefinitions: [], traitDefinitions: [],
propertiesMode: 'native',
}; };
this.field = new Field(this); this.field = new Field(this);
this.uiSchemaRef = React.createRef(); this.uiSchemaRef = React.createRef();
@ -286,7 +288,7 @@ class TraitDialog extends React.Component<Props, State> {
const FormItem = Form.Item; const FormItem = Form.Item;
const { Row, Col } = Grid; const { Row, Col } = Grid;
const { onClose, isEditTrait } = this.props; const { onClose, isEditTrait } = this.props;
const { definitionDetail, definitionLoading, podDisruptive } = this.state; const { definitionDetail, definitionLoading, podDisruptive, propertiesMode } = this.state;
const validator = (rule: Rule, value: any, callback: (error?: string) => void) => { const validator = (rule: Rule, value: any, callback: (error?: string) => void) => {
this.uiSchemaRef.current?.validate(callback); this.uiSchemaRef.current?.validate(callback);
}; };
@ -393,8 +395,37 @@ class TraitDialog extends React.Component<Props, State> {
required={true} required={true}
hasToggleIcon={true} hasToggleIcon={true}
> >
<If condition={definitionDetail && definitionDetail.uiSchema}> <If condition={definitionDetail}>
<FormItem required={true}> <FormItem required={true}>
<If condition={definitionDetail && definitionDetail.uiSchema}>
<div className="flexright">
<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 code mode'}
/>
</If>
<If condition={propertiesMode === 'code'}>
<Icon
style={{ color: '#1b58f4' }}
type={'laptop'}
title={'Switch to native mode'}
/>
</If>
</Button>
</div>
</If>
<UISchema <UISchema
key={traitType} key={traitType}
{...init(`properties`, { {...init(`properties`, {
@ -405,7 +436,13 @@ class TraitDialog extends React.Component<Props, State> {
}, },
], ],
})} })}
enableCodeEdit={propertiesMode === 'code'}
uiSchema={definitionDetail && definitionDetail.uiSchema} uiSchema={definitionDetail && definitionDetail.uiSchema}
definition={{
type: 'trait',
name: definitionDetail?.name || '',
description: definitionDetail?.description || '',
}}
ref={this.uiSchemaRef} ref={this.uiSchemaRef}
mode={this.props.isEditTrait ? 'edit' : 'new'} mode={this.props.isEditTrait ? 'edit' : 'new'}
/> />

View File

@ -15,8 +15,8 @@
.list-warper { .list-warper {
padding: 0 8px; padding: 0 8px;
.box { .box {
background: #fff;
min-height: 400px; min-height: 400px;
background: #fff;
.box-item { .box-item {
margin-bottom: 4px; margin-bottom: 4px;
.next-card { .next-card {

View File

@ -10,6 +10,7 @@ import {
deleteComponent, deleteComponent,
getComponentDefinitions, getComponentDefinitions,
deleteApplicationPlan, deleteApplicationPlan,
deletePolicy,
} from '../../api/application'; } from '../../api/application';
import Translation from '../../components/Translation'; import Translation from '../../components/Translation';
import Title from '../../components/Title'; import Title from '../../components/Title';
@ -370,6 +371,26 @@ class ApplicationConfig extends Component<Props, State> {
}); });
}; };
onDeletePolicy = (policyName: string) => {
const { appName } = this.state;
deletePolicy({ appName: appName, policyName: policyName }).then((re) => {
if (re) {
Message.success('Application policy deleted successfully');
this.loadApplicationPolicies();
}
});
};
loadApplicationPolicies = async () => {
const {
params: { appName },
} = this.props.match;
this.props.dispatch({
type: 'application/getApplicationPolicies',
payload: { appName: appName },
});
};
render() { render() {
const { applicationDetail, workflows, components, policies, envbinding } = this.props; const { applicationDetail, workflows, components, policies, envbinding } = this.props;
const { const {
@ -555,7 +576,7 @@ class ApplicationConfig extends Component<Props, State> {
policies={policies} policies={policies}
envbinding={envbinding} envbinding={envbinding}
onDeletePolicy={(name: string) => { onDeletePolicy={(name: string) => {
console.log(name); this.onDeletePolicy(name);
}} }}
onShowPolicy={(name: string) => { onShowPolicy={(name: string) => {
console.log(name); console.log(name);

View File

@ -92,6 +92,9 @@ class ContainerLog extends Component<Props, State> {
if (res && res.logs) { if (res && res.logs) {
this.setState({ logs: this.toLogLines(res.logs), info: res.info }); this.setState({ logs: this.toLogLines(res.logs), info: res.info });
} }
if (res && res.err) {
this.setState({ logs: this.toLogLines(res.err) });
}
const { autoRefresh, refreshInterval } = this.state; const { autoRefresh, refreshInterval } = this.state;
if (autoRefresh) { if (autoRefresh) {
this.timeoutID = setTimeout(() => this.loadContainerLog(), refreshInterval); this.timeoutID = setTimeout(() => this.loadContainerLog(), refreshInterval);

View File

@ -15,7 +15,7 @@ import type {
} from '../../../../interface/application'; } from '../../../../interface/application';
import { If } from 'tsx-control-statements/components'; import { If } from 'tsx-control-statements/components';
import locale from '../../../../utils/locale'; import locale from '../../../../utils/locale';
import { Link } from 'dva/router'; import { Link, routerRedux } from 'dva/router';
import i18n from 'i18next'; import i18n from 'i18next';
import Permission from '../../../../components/Permission'; import Permission from '../../../../components/Permission';
@ -113,13 +113,14 @@ class Header extends Component<Props, State> {
Dialog.confirm({ Dialog.confirm({
content: i18n.t('Are you sure you want to delete the current environment binding?'), content: i18n.t('Are you sure you want to delete the current environment binding?'),
onOk: () => { onOk: () => {
const { applicationDetail, envName, updateEnvs } = this.props; const { applicationDetail, envName, updateEnvs, dispatch } = this.props;
if (applicationDetail) { if (applicationDetail) {
deleteApplicationEnvbinding({ appName: applicationDetail.name, envName: envName }).then( deleteApplicationEnvbinding({ appName: applicationDetail.name, envName: envName }).then(
(re) => { (re) => {
if (re) { if (re) {
Message.success(i18n.t('Environment binding deleted successfully')); Message.success(i18n.t('Environment binding deleted successfully'));
updateEnvs(); updateEnvs();
dispatch(routerRedux.push(`/applications/${applicationDetail.name}/config`));
} }
}, },
); );

View File

@ -99,7 +99,7 @@ class PodDetail extends React.Component<Props, State> {
this.setState({ showContainerLog: true, containerName: containerName }); this.setState({ showContainerLog: true, containerName: containerName });
}; };
getContainerCloumns = () => { getContainerColumns = () => {
const { observability } = this.state; const { observability } = this.state;
const { clusterName, env, pod, application } = this.props; const { clusterName, env, pod, application } = this.props;
let domain = ''; let domain = '';
@ -244,7 +244,7 @@ class PodDetail extends React.Component<Props, State> {
]; ];
}; };
getEventCloumns = () => { getEventColumns = () => {
return [ return [
{ {
key: 'type', key: 'type',
@ -282,15 +282,15 @@ class PodDetail extends React.Component<Props, State> {
render() { render() {
const { Column } = Table; const { Column } = Table;
const containerColumns = this.getContainerCloumns(); const containerColumns = this.getContainerColumns();
const eventCloumns = this.getEventCloumns(); const eventColumns = this.getEventColumns();
const { events, containers, loading, showContainerLog, containerName } = this.state; const { events, containers, loading, showContainerLog, containerName } = this.state;
const { pod, clusterName } = this.props; const { pod, clusterName } = this.props;
return ( return (
<div className="table-podDetails-list margin-top-20"> <div className="table-podDetails-list margin-top-20">
<Table <Table
className="container-table-wraper margin-top-20" className="container-table-wrapper margin-top-20"
dataSource={containers} dataSource={containers || []}
hasBorder={false} hasBorder={false}
primaryKey="name" primaryKey="name"
loading={loading} loading={loading}
@ -301,15 +301,15 @@ class PodDetail extends React.Component<Props, State> {
</Table> </Table>
<Table <Table
className="event-table-wraper margin-top-20" className="event-table-wrapper margin-top-20"
dataSource={events} dataSource={events || []}
hasBorder={false} hasBorder={false}
loading={loading} loading={loading}
primaryKey="time" primaryKey="time"
locale={locale().Table} locale={locale().Table}
> >
{eventCloumns && {eventColumns &&
eventCloumns.map((col, key) => <Column {...col} key={key} align={'left'} />)} eventColumns.map((col, key) => <Column {...col} key={key} align={'left'} />)}
</Table> </Table>
<If condition={showContainerLog}> <If condition={showContainerLog}>

View File

@ -1,16 +1,16 @@
.podlist-table-wraper { .podlist-table-wrapper {
.next-table-cell.last .next-table-cell-wrapper { .next-table-cell.last .next-table-cell-wrapper {
text-align: left; text-align: left;
} }
} }
.container-table-wraper { .container-table-wrapper {
.next-table-cell.last .next-table-cell-wrapper { .next-table-cell.last .next-table-cell-wrapper {
text-align: left; text-align: left;
} }
} }
.event-table-wraper { .event-table-wrapper {
.next-table-cell.last .next-table-cell-wrapper { .next-table-cell.last .next-table-cell-wrapper {
text-align: left; text-align: left;
} }

View File

@ -184,10 +184,15 @@ class ApplicationInstanceList extends React.Component<Props, State> {
const instances: CloudInstance[] = []; const instances: CloudInstance[] = [];
if (Array.isArray(configurations) && configurations.length > 0) { if (Array.isArray(configurations) && configurations.length > 0) {
configurations.map((configuration) => { configurations.map((configuration) => {
let url = configuration.metadata.annotations['cloud-resource/console-url']; let url = '';
const identifierKey = configuration.metadata.annotations['cloud-resource/identifier']; let identifierKey = '';
if (configuration.metadata.annotations) {
url = configuration.metadata.annotations['cloud-resource/console-url'];
identifierKey = configuration.metadata.annotations['cloud-resource/identifier'];
}
const outputs = configuration.status?.apply?.outputs; const outputs = configuration.status?.apply?.outputs;
let instanceName = ''; let instanceName = '';
const region = configuration.status?.apply?.region || configuration.spec.region || '';
if (outputs) { if (outputs) {
if (outputs[identifierKey]) { if (outputs[identifierKey]) {
instanceName = outputs[identifierKey].value; instanceName = outputs[identifierKey].value;
@ -197,8 +202,8 @@ class ApplicationInstanceList extends React.Component<Props, State> {
if (Array.isArray(params) && params.length > 0) { if (Array.isArray(params) && params.length > 0) {
params.map((param) => { params.map((param) => {
const paramKey = param.replace('{', '').replace('}', ''); const paramKey = param.replace('{', '').replace('}', '');
if (paramKey.toLowerCase() == 'region' && configuration.spec.region) { if (paramKey.toLowerCase() == 'region' && region) {
url = url.replace(param, configuration.spec.region); url = url.replace(param, region);
} }
if (outputs[paramKey]) { if (outputs[paramKey]) {
url = url.replace(param, outputs[paramKey].value); url = url.replace(param, outputs[paramKey].value);
@ -209,12 +214,13 @@ class ApplicationInstanceList extends React.Component<Props, State> {
} }
instances.push({ instances.push({
instanceName: instanceName, instanceName: instanceName,
status: configuration.status?.apply?.state || 'Initing', status: configuration.status?.apply?.state || '-',
url: url, url: url,
createTime: configuration.metadata.creationTimestamp, createTime: configuration.metadata.creationTimestamp || '',
region: configuration.spec.region, region: region,
message: configuration.status?.apply?.message, message: configuration.status?.apply?.message,
type: configuration.metadata.labels['workload.oam.dev/type'], type:
configuration.metadata.labels && configuration.metadata.labels['workload.oam.dev/type'],
}); });
}); });
} }
@ -549,11 +555,11 @@ class ApplicationInstanceList extends React.Component<Props, State> {
<Table <Table
style={{ marginBottom: '32px' }} style={{ marginBottom: '32px' }}
locale={locale().Table} locale={locale().Table}
className="podlist-table-wraper" className="podlist-table-wrapper"
size="medium" size="medium"
primaryKey={'primaryKey'} primaryKey={'primaryKey'}
loading={loading} loading={loading}
dataSource={podList} dataSource={podList || []}
expandedIndexSimulate expandedIndexSimulate
expandedRowRender={expandedRowRender} expandedRowRender={expandedRowRender}
openRowKeys={this.state.openRowKeys} openRowKeys={this.state.openRowKeys}

View File

@ -110,6 +110,9 @@ class ContainerLog extends Component<Props, State> {
if (res && res.logs) { if (res && res.logs) {
this.setState({ logs: this.toLogLines(res.logs), info: res.info }); this.setState({ logs: this.toLogLines(res.logs), info: res.info });
} }
if (res && res.err) {
this.setState({ logs: this.toLogLines(res.err) });
}
const { autoRefresh, refreshInterval } = this.state; const { autoRefresh, refreshInterval } = this.state;
if (autoRefresh) { if (autoRefresh) {
this.timeoutID = setTimeout(() => this.loadContainerLog(), refreshInterval); this.timeoutID = setTimeout(() => this.loadContainerLog(), refreshInterval);

View File

@ -1,15 +1,15 @@
@import '../../../../lib.less'; @import '../../../../lib.less';
.graph-container { .graph-container {
padding: 32px; display: flex;
overflow: auto; align-items: center;
background: rgba(171, 205, 239, 0.2); justify-content: start;
min-width: 100%; min-width: 100%;
max-width: 100%; max-width: 100%;
min-height: calc(100vh - 443px); min-height: calc(100vh - 443px);
display: flex; padding: 32px;
justify-content: start; overflow: auto;
align-items: center; background: rgba(171, 205, 239, 0.2);
//position: relative; //position: relative;
.operation { .operation {
position: absolute; position: absolute;

View File

@ -3,12 +3,17 @@ import React from 'react';
import { If } from 'tsx-control-statements/components'; import { If } from 'tsx-control-statements/components';
import type { TreeNode } from '../../../../components/TreeGraph'; import type { TreeNode } from '../../../../components/TreeGraph';
import { TreeGraph } from '../../../../components/TreeGraph'; import { TreeGraph } from '../../../../components/TreeGraph';
import type { ApplicationDetail, EnvBinding } from '../../../../interface/application'; import type {
ApplicationDetail,
ApplicationStatus,
EnvBinding,
} from '../../../../interface/application';
import type { AppliedResource, ResourceTreeNode } from '../../../../interface/observation'; import type { AppliedResource, ResourceTreeNode } from '../../../../interface/observation';
import { ShowResource } from './resource-show'; import { ShowResource } from './resource-show';
import './index.less'; import './index.less';
type Props = { type Props = {
applicationStatus?: ApplicationStatus;
application?: ApplicationDetail; application?: ApplicationDetail;
env?: EnvBinding; env?: EnvBinding;
resources: AppliedResource[]; resources: AppliedResource[];

View File

@ -1,4 +1,4 @@
@import "../../lib.less"; @import '../../lib.less';
.application-status-wrapper { .application-status-wrapper {
.tags-content { .tags-content {
@ -16,12 +16,13 @@
.next-switch { .next-switch {
width: 80px; width: 80px;
} }
.next-switch-on,.next-switch-off { .next-switch-on,
.next-switch-off {
background-color: (@primarycolor) !important; background-color: (@primarycolor) !important;
} }
.next-switch-children { .next-switch-children {
display: block !important; display: block !important;
text-align: center;
color: #fff !important; color: #fff !important;
text-align: center;
} }
} }

View File

@ -11,7 +11,7 @@ import {
Message, Message,
Dialog, Dialog,
Tag, Tag,
Switch, Tab,
} from '@b-design/ui'; } from '@b-design/ui';
import type { import type {
ApplicationComponent, ApplicationComponent,
@ -42,6 +42,7 @@ import { checkPermission } from '../../utils/permission';
import type { LoginUserInfo } from '../../interface/user'; import type { LoginUserInfo } from '../../interface/user';
import './index.less'; import './index.less';
import ApplicationGraph from './components/ApplicationGraph'; import ApplicationGraph from './components/ApplicationGraph';
import i18n from '../../i18n';
type Props = { type Props = {
dispatch: ({}) => {}; dispatch: ({}) => {};
@ -69,7 +70,7 @@ type State = {
resourceLoading: boolean; resourceLoading: boolean;
endpointLoading: boolean; endpointLoading: boolean;
envName: string; envName: string;
mode: 'table' | 'graph'; mode: 'overview' | 'resource-graph' | 'application-graph';
}; };
@connect((store: any) => { @connect((store: any) => {
@ -84,7 +85,7 @@ class ApplicationStatusPage extends React.Component<Props, State> {
resourceLoading: false, resourceLoading: false,
endpointLoading: false, endpointLoading: false,
envName: '', envName: '',
mode: 'graph', mode: 'overview',
resources: [], resources: [],
}; };
} }
@ -114,9 +115,11 @@ class ApplicationStatusPage extends React.Component<Props, State> {
this.props.dispatch({ this.props.dispatch({
type: 'application/getApplicationStatus', type: 'application/getApplicationStatus',
payload: { appName: appName, envName: envName }, payload: { appName: appName, envName: envName },
callback: () => { callback: (res: any) => {
if (res.status) {
this.loadApplicationEndpoints(); this.loadApplicationEndpoints();
this.loadApplicationAppliedResources(); this.loadApplicationAppliedResources();
}
}, },
}); });
} }
@ -175,7 +178,7 @@ class ApplicationStatusPage extends React.Component<Props, State> {
loadApplicationAppliedResources = async () => { loadApplicationAppliedResources = async () => {
const { mode } = this.state; const { mode } = this.state;
if (mode == 'graph') { if (mode === 'resource-graph') {
await this.loadResourceTree(); await this.loadResourceTree();
return; return;
} }
@ -256,6 +259,16 @@ class ApplicationStatusPage extends React.Component<Props, State> {
}); });
}; };
loadApplicationPolicies = async () => {
const {
params: { appName },
} = this.props.match;
this.props.dispatch({
type: 'application/getApplicationPolicies',
payload: { appName: appName },
});
};
loadApplicationEnvbinding = async () => { loadApplicationEnvbinding = async () => {
const { const {
params: { appName }, params: { appName },
@ -325,14 +338,13 @@ class ApplicationStatusPage extends React.Component<Props, State> {
} }
}; };
onChangeMode = () => { onChangeMode = (mode: string | number) => {
const { mode } = this.state; if (mode == 'overview') {
if (mode == 'graph') { this.setState({ mode: mode }, () => {
this.setState({ mode: 'table' }, () => {
this.loadApplicationAppliedResources(); this.loadApplicationAppliedResources();
}); });
} else { } else if (mode == 'resource-graph') {
this.setState({ mode: 'graph' }, () => { this.setState({ mode: mode }, () => {
this.loadApplicationAppliedResources(); this.loadApplicationAppliedResources();
}); });
} }
@ -351,7 +363,6 @@ class ApplicationStatusPage extends React.Component<Props, State> {
resources, resources,
componentName, componentName,
deployLoading, deployLoading,
mode,
} = this.state; } = this.state;
const gatewayIPs: any = []; const gatewayIPs: any = [];
endpoints?.map((endpointObj) => { endpoints?.map((endpointObj) => {
@ -381,6 +392,7 @@ class ApplicationStatusPage extends React.Component<Props, State> {
updateEnvs={() => { updateEnvs={() => {
this.loadApplicationEnvbinding(); this.loadApplicationEnvbinding();
this.loadApplicationWorkflows(); this.loadApplicationWorkflows();
this.loadApplicationPolicies();
}} }}
updateQuery={(params: { target?: string; component?: string }) => { updateQuery={(params: { target?: string; component?: string }) => {
this.updateQuery(params); this.updateQuery(params);
@ -391,25 +403,28 @@ class ApplicationStatusPage extends React.Component<Props, State> {
dispatch={this.props.dispatch} dispatch={this.props.dispatch}
/> />
</Loading> </Loading>
<If condition={applicationStatus}> <Tab onChange={this.onChangeMode} shape="capsule">
<Switch <Tab.Item title={i18n.t('Overview')} key="overview">
unCheckedChildren="Table"
checkedChildren="Graph"
defaultChecked={true}
onChange={this.onChangeMode}
/>
</If>
<Loading visible={loading && resourceLoading} style={{ width: '100%' }}> <Loading visible={loading && resourceLoading} style={{ width: '100%' }}>
<If condition={applicationStatus && mode === 'graph'}> <If condition={applicationStatus}>
<ApplicationGraph application={applicationDetail} env={env} resources={resources} /> <If condition={componentStatus}>
</If>
<If condition={applicationStatus && mode === 'table'}>
<Card <Card
locale={locale().Card} locale={locale().Card}
contentHeight="200px" style={{ marginTop: '8px', marginBottom: '16px' }}
title={<Translation>Applied Resources</Translation>} contentHeight="auto"
title={<Translation>Component Status</Translation>}
> >
<Table locale={locale().Table} dataSource={resources}> <Table
locale={locale().Table}
className="customTable"
dataSource={componentStatus}
>
<Table.Column
align="left"
dataIndex="name"
style={{ width: '17%' }}
title={<Translation>Name</Translation>}
/>
<Table.Column <Table.Column
dataIndex="cluster" dataIndex="cluster"
title={<Translation>Cluster</Translation>} title={<Translation>Cluster</Translation>}
@ -431,61 +446,6 @@ class ApplicationStatusPage extends React.Component<Props, State> {
return <span>{clusterName}</span>; return <span>{clusterName}</span>;
}} }}
/> />
<Table.Column
dataIndex="name"
width="240px"
title={<Translation>Namespace/Name</Translation>}
cell={(v: string, i: number, row: AppliedResource) => {
return `${row.namespace}/${row.name}`;
}}
/>
<Table.Column
width="200px"
dataIndex="kind"
title={<Translation>Kind</Translation>}
/>
<Table.Column
dataIndex="apiVersion"
title={<Translation>APIVersion</Translation>}
/>
<Table.Column dataIndex="component" title={<Translation>Component</Translation>} />
<Table.Column
dataIndex="deployVersion"
title={<Translation>Revision</Translation>}
cell={(v: string, i: number, row: AppliedResource) => {
if (row.latest) {
return (
<span>
<Icon
style={{ color: 'green', marginRight: '8px' }}
type="NEW"
title="latest version resource"
/>
<Link to={`/applications/${applicationDetail?.name}/revisions`}>{v}</Link>
</span>
);
}
return (
<Link to={`/applications/${applicationDetail?.name}/revisions`}>{v}</Link>
);
}}
/>
</Table>
</Card>
<If condition={componentStatus}>
<Card
locale={locale().Card}
style={{ marginTop: '8px', marginBottom: '16px' }}
contentHeight="auto"
title={<Translation>Component Status</Translation>}
>
<Table locale={locale().Table} className="customTable" dataSource={componentStatus}>
<Table.Column
align="left"
dataIndex="name"
style={{ width: '17%' }}
title={<Translation>Name</Translation>}
/>
<Table.Column <Table.Column
align="left" align="left"
dataIndex="healthy" dataIndex="healthy"
@ -541,7 +501,6 @@ class ApplicationStatusPage extends React.Component<Props, State> {
<Table.Column <Table.Column
align="center" align="center"
dataIndex="message" dataIndex="message"
style={{ width: '50%' }}
title={<Translation>Message</Translation>} title={<Translation>Message</Translation>}
cell={(v: string, i: number, record: ComponentStatus) => { cell={(v: string, i: number, record: ComponentStatus) => {
const { message = '', traits } = record; const { message = '', traits } = record;
@ -566,6 +525,80 @@ class ApplicationStatusPage extends React.Component<Props, State> {
</Table> </Table>
</Card> </Card>
</If> </If>
<Card
locale={locale().Card}
contentHeight="200px"
title={<Translation>Applied Resources</Translation>}
>
<Table locale={locale().Table} dataSource={resources}>
<Table.Column
dataIndex="name"
width="240px"
title={<Translation>Namespace/Name</Translation>}
cell={(v: string, i: number, row: AppliedResource) => {
return `${row.namespace || '-'}/${row.name}`;
}}
/>
<Table.Column
dataIndex="cluster"
title={<Translation>Cluster</Translation>}
width="200px"
cell={(v: string) => {
let clusterName = v;
if (!clusterName) {
clusterName = 'Local';
}
if (
checkPermission(
{ resource: 'cluster:*', action: 'list' },
applicationDetail?.project?.name,
userInfo,
)
) {
return <Link to="/clusters">{clusterName}</Link>;
}
return <span>{clusterName}</span>;
}}
/>
<Table.Column
width="200px"
dataIndex="kind"
title={<Translation>Kind</Translation>}
/>
<Table.Column
dataIndex="apiVersion"
title={<Translation>APIVersion</Translation>}
/>
<Table.Column
dataIndex="component"
title={<Translation>Component</Translation>}
/>
<Table.Column
dataIndex="deployVersion"
title={<Translation>Revision</Translation>}
cell={(v: string, i: number, row: AppliedResource) => {
if (row.latest) {
return (
<span>
<Icon
style={{ color: 'green', marginRight: '8px' }}
type="NEW"
title="latest version resource"
/>
<Link to={`/applications/${applicationDetail?.name}/revisions`}>
{v}
</Link>
</span>
);
}
return (
<Link to={`/applications/${applicationDetail?.name}/revisions`}>{v}</Link>
);
}}
/>
</Table>
</Card>
<If condition={applicationStatus?.conditions}> <If condition={applicationStatus?.conditions}>
<Card <Card
locale={locale().Card} locale={locale().Card}
@ -632,6 +665,20 @@ class ApplicationStatusPage extends React.Component<Props, State> {
</div> </div>
</If> </If>
</Loading> </Loading>
</Tab.Item>
<Tab.Item title={i18n.t('Resource Graph')} key="resource-graph">
<Loading visible={loading && resourceLoading} style={{ width: '100%' }}>
<If condition={applicationStatus}>
<ApplicationGraph
applicationStatus={applicationStatus}
application={applicationDetail}
env={env}
resources={resources}
/>
</If>
</Loading>
</Tab.Item>
</Tab>
</div> </div>
); );
} }

View File

@ -5,7 +5,7 @@ import _ from 'lodash';
import type { DiagramMakerNode } from 'diagram-maker'; import type { DiagramMakerNode } from 'diagram-maker';
import type { WorkFlowNodeType } from '../entity'; import type { WorkFlowNodeType } from '../entity';
import { Grid, Field, Form, Select, Input, Message, Button } from '@b-design/ui'; import { Grid, Field, Form, Select, Input, Message, Button, Icon } from '@b-design/ui';
import type { Rule } from '@alifd/field'; import type { Rule } from '@alifd/field';
import { withTranslation } from 'react-i18next'; import { withTranslation } from 'react-i18next';
import Group from '../../../extends/Group'; import Group from '../../../extends/Group';
@ -35,6 +35,7 @@ type Props = {
type State = { type State = {
definitionDetail?: DefinitionDetail; definitionDetail?: DefinitionDetail;
definitionLoading: boolean; definitionLoading: boolean;
propertiesMode: 'native' | 'code';
}; };
@connect() @connect()
@ -45,6 +46,7 @@ class WorkflowForm extends Component<Props, State> {
super(props); super(props);
this.state = { this.state = {
definitionLoading: false, definitionLoading: false,
propertiesMode: 'native',
}; };
this.field = new Field(this, { this.field = new Field(this, {
onChange: (name: string, value: string) => { onChange: (name: string, value: string) => {
@ -129,7 +131,7 @@ class WorkflowForm extends Component<Props, State> {
const { Row, Col } = Grid; const { Row, Col } = Grid;
const FormItem = Form.Item; const FormItem = Form.Item;
const { t, closeDrawer, data, checkStepName } = this.props; const { t, closeDrawer, data, checkStepName } = this.props;
const { definitionDetail } = this.state; const { definitionDetail, propertiesMode } = this.state;
const validator = (rule: Rule, value: any, callback: (error?: string) => void) => { const validator = (rule: Rule, value: any, callback: (error?: string) => void) => {
this.uiSchemaRef.current?.validate(callback); this.uiSchemaRef.current?.validate(callback);
}; };
@ -260,8 +262,37 @@ class WorkflowForm extends Component<Props, State> {
required={true} required={true}
hasToggleIcon={true} hasToggleIcon={true}
> >
<If condition={definitionDetail && definitionDetail.uiSchema}> <If condition={definitionDetail}>
<FormItem required={true}> <FormItem required={true}>
<If condition={definitionDetail && definitionDetail.uiSchema}>
<div className="flexright">
<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 code mode'}
/>
</If>
<If condition={propertiesMode === 'code'}>
<Icon
style={{ color: '#1b58f4' }}
type={'laptop'}
title={'Switch to native mode'}
/>
</If>
</Button>
</div>
</If>
<UISchema <UISchema
{...init(`properties`, { {...init(`properties`, {
rules: [ rules: [
@ -271,7 +302,13 @@ class WorkflowForm extends Component<Props, State> {
}, },
], ],
})} })}
enableCodeEdit={propertiesMode === 'code'}
uiSchema={definitionDetail && definitionDetail.uiSchema} uiSchema={definitionDetail && definitionDetail.uiSchema}
definition={{
type: 'workflowstep',
name: definitionDetail?.name || '',
description: definitionDetail?.description || '',
}}
ref={this.uiSchemaRef} ref={this.uiSchemaRef}
mode={this.props.data ? 'edit' : 'new'} mode={this.props.data ? 'edit' : 'new'}
/> />

View File

@ -12,6 +12,8 @@ import SelectSearch from './components/SelectSearch';
import locale from '../../utils/locale'; import locale from '../../utils/locale';
import { getMatchParamObj } from '../../utils/utils'; import { getMatchParamObj } from '../../utils/utils';
import './index.less'; import './index.less';
import { checkPermission } from '../../utils/permission';
import type { LoginUserInfo } from '../../interface/user';
type Props = { type Props = {
match: { match: {
@ -19,6 +21,7 @@ type Props = {
definitionType: string; definitionType: string;
}; };
}; };
userInfo?: LoginUserInfo;
}; };
type State = { type State = {
@ -30,7 +33,7 @@ type State = {
}; };
@connect((store: any) => { @connect((store: any) => {
return { ...store.definitions }; return { ...store.definitions, ...store.user };
}) })
class Definitions extends Component<Props, State> { class Definitions extends Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
@ -62,10 +65,14 @@ class Definitions extends Component<Props, State> {
} }
lisDefinitions() { lisDefinitions() {
const { userInfo } = this.props;
const { definitionType } = this.state; const { definitionType } = this.state;
if (!definitionType) { if (!definitionType) {
return; return;
} }
if (!checkPermission({ resource: 'definition:*', action: 'list' }, '', userInfo)) {
return;
}
const params = { const params = {
definitionType, definitionType,
queryAll: true, queryAll: true,

View File

@ -1,4 +1,5 @@
.table-delivery-list { .table-env-list {
overflow: auto;
.next-table-cell.last .next-table-cell-wrapper { .next-table-cell.last .next-table-cell-wrapper {
text-align: left; text-align: left;
} }

View File

@ -80,14 +80,14 @@ class TableList extends Component<Props> {
return <span>{v}</span>; return <span>{v}</span>;
}, },
}, },
// { {
// key: 'project', key: 'project',
// title: <Translation>Project</Translation>, title: <Translation>Project</Translation>,
// dataIndex: 'project', dataIndex: 'project',
// cell: (v: NameAlias) => { cell: (v: NameAlias) => {
// return <span>{v.alias || v.name}</span>; return <span>{v.alias || v.name}</span>;
// }, },
// }, },
{ {
key: 'description', key: 'description',
title: <Translation>Description</Translation>, title: <Translation>Description</Translation>,
@ -118,6 +118,7 @@ class TableList extends Component<Props> {
key: 'operation', key: 'operation',
title: <Translation>Actions</Translation>, title: <Translation>Actions</Translation>,
dataIndex: 'operation', dataIndex: 'operation',
width: '120px',
cell: (v: string, i: number, record: Env) => { cell: (v: string, i: number, record: Env) => {
return ( return (
<div> <div>
@ -171,11 +172,12 @@ class TableList extends Component<Props> {
const columns = this.getColumns(); const columns = this.getColumns();
const { list } = this.props; const { list } = this.props;
return ( return (
<div className="table-delivery-list margin-top-20"> <div className="table-env-list margin-top-20">
<Table <Table
locale={locale().Table} locale={locale().Table}
className="customTable" className="customTable"
size="medium" size="medium"
style={{ minWidth: '1200px' }}
dataSource={list} dataSource={list}
hasBorder={false} hasBorder={false}
loading={false} loading={false}

View File

@ -103,7 +103,7 @@ class CreateIntegration extends React.Component<Props, State> {
createConfigType(queryData, params) createConfigType(queryData, params)
.then((res) => { .then((res) => {
if (res) { if (res) {
Message.success(<Translation>Create config success</Translation>); Message.success(<Translation>Integration config created successfully</Translation>);
this.props.onCreate(); this.props.onCreate();
} }
}) })
@ -172,7 +172,7 @@ class CreateIntegration extends React.Component<Props, State> {
{...init('name', { {...init('name', {
rules: [ rules: [
{ {
required: false, required: true,
pattern: checkName, pattern: checkName,
message: <Translation>Please enter a valid name</Translation>, message: <Translation>Please enter a valid name</Translation>,
}, },

View File

@ -102,8 +102,8 @@ class Integrations extends Component<Props, State> {
if (params) { if (params) {
deleteConfig(params).then((res) => { deleteConfig(params).then((res) => {
if (res) { if (res) {
Message.success(<Translation>Delete config success</Translation>); Message.success(<Translation>Integration config deleted successfully</Translation>);
this.listIntegrations(); setTimeout(() => this.listIntegrations(), 2000);
} }
}); });
} }
@ -114,7 +114,7 @@ class Integrations extends Component<Props, State> {
onCreate = () => { onCreate = () => {
this.setState({ visible: false }); this.setState({ visible: false });
this.listIntegrations(); setTimeout(() => this.listIntegrations(), 2000);
}; };
onCloseCreate = () => { onCloseCreate = () => {

View File

@ -1,4 +1,5 @@
.table-delivery-list { .table-delivery-list {
overflow: auto;
.next-table-cell.last .next-table-cell-wrapper { .next-table-cell.last .next-table-cell-wrapper {
text-align: left; text-align: left;
} }

View File

@ -140,6 +140,7 @@ class TableList extends Component<Props> {
locale={locale().Table} locale={locale().Table}
className="customTable" className="customTable"
size="medium" size="medium"
style={{ minWidth: '1200px' }}
dataSource={list} dataSource={list}
hasBorder={false} hasBorder={false}
loading={false} loading={false}

View File

@ -93,6 +93,9 @@ class DeliveryDialog extends React.Component<Props, State> {
if (cluster) { if (cluster) {
this.loadNamespaces(cluster.clusterName); this.loadNamespaces(cluster.clusterName);
} }
if (project.name) {
this.getProviderList(project.name);
}
} }
} }

View File

@ -1,6 +1,7 @@
import 'mocha'; import 'mocha';
import { assert } from 'chai'; import { assert } from 'chai';
import { resourceMatch, ResourceName } from '../permission'; import { resourceMatch, ResourceName } from '../permission';
import { equalArray } from '../common';
describe('test permission', () => { describe('test permission', () => {
it('test resourceMatch', () => { it('test resourceMatch', () => {
@ -14,3 +15,11 @@ describe('test permission', () => {
); );
}); });
}); });
describe('test util', () => {
it('test equal the array', () => {
assert.equal(equalArray(['a', 'b'], ['b', 'a']), true);
assert.equal(equalArray(['b'], ['b', 'a']), false);
assert.equal(equalArray(undefined, ['b', 'a']), false);
});
});

View File

@ -89,6 +89,43 @@ export function momentShortDate(time: undefined | string): string {
return moment(time).format('YYYY/MM/DD'); return moment(time).format('YYYY/MM/DD');
} }
export function beautifyTime(time?: string) {
if (!time) {
return '';
}
const timestamp = moment(time).unix();
const now = new Date();
let mistiming = Math.round(now.getTime() / 1000) - timestamp;
const postfix = mistiming > 0 ? 'ago' : 'later';
mistiming = Math.abs(mistiming);
const arrr = ['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds'];
const arrn = [31536000, 2592000, 604800, 86400, 3600, 60, 1];
for (let i = 0; i < 7; i++) {
const inm = Math.floor(mistiming / arrn[i]);
if (inm != 0) {
return inm + ' ' + arrr[i] + ' ' + postfix;
}
}
}
export function beautifyBinarySize(value?: number) {
if (null == value || value == 0) {
return '0 Bytes';
}
const unitArr = new Array('Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
let index = 0;
index = Math.floor(Math.log(value) / Math.log(1024));
const size = value / Math.pow(1024, index);
let sizeStr = '';
if (size % 1 === 0) {
sizeStr = size.toFixed(0);
} else {
sizeStr = size.toFixed(2);
}
return sizeStr + unitArr[index];
}
export const checkName = /^[a-z]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/; export const checkName = /^[a-z]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/;
export const urlRegular = export const urlRegular =
/(https|http|ftp|file):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/g; /(https|http|ftp|file):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/g;
@ -171,3 +208,24 @@ export function isMatchBusinessCode(businessCode: number) {
const tokenExpiredList = [12002, 12010]; const tokenExpiredList = [12002, 12010];
return tokenExpiredList.includes(businessCode); return tokenExpiredList.includes(businessCode);
} }
export function equalArray(a?: string[], b?: string[]) {
if (a === undefined && b === undefined) {
return true;
}
if (b === undefined || a === undefined) {
return false;
}
if (a.length !== b.length) {
return false;
} else {
const sa = a.sort();
const sb = b.sort();
for (let i = 0; i < sa.length; i++) {
if (sa[i] !== sb[i]) {
return false;
}
}
return true;
}
}

View File

@ -88,6 +88,29 @@
resolved "https://registry.nlark.com/@alifd/validate/download/@alifd/validate-1.2.3.tgz?cache=0&sync_timestamp=1626662152988&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40alifd%2Fvalidate%2Fdownload%2F%40alifd%2Fvalidate-1.2.3.tgz" resolved "https://registry.nlark.com/@alifd/validate/download/@alifd/validate-1.2.3.tgz?cache=0&sync_timestamp=1626662152988&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40alifd%2Fvalidate%2Fdownload%2F%40alifd%2Fvalidate-1.2.3.tgz"
integrity sha1-O3+IChqVVHCzA/3c5a+kKp8tn/I= integrity sha1-O3+IChqVVHCzA/3c5a+kKp8tn/I=
"@ant-design/colors@^3.1.0":
version "3.2.2"
resolved "https://registry.yarnpkg.com/@ant-design/colors/-/colors-3.2.2.tgz#5ad43d619e911f3488ebac303d606e66a8423903"
integrity sha512-YKgNbG2dlzqMhA9NtI3/pbY16m3Yl/EeWBRa+lB1X1YaYxHrxNexiQYCLTWO/uDvAjLFMEDU+zR901waBtMtjQ==
dependencies:
tinycolor2 "^1.4.1"
"@ant-design/icons-svg@^4.0.0":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz#8630da8eb4471a4aabdaed7d1ff6a97dcb2cf05a"
integrity sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw==
"@ant-design/icons@4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-4.0.0.tgz#cdb870777f185422cfc0e235392592d3e4795ef6"
integrity sha512-oJTNTyo/6DjVmK/DSa58A+7gZRGgzBCAEJ9Pfa6U+BAZO28tvOE3YzlQd9gq9Qu6d47JL1ixyID3qsmRFqitlQ==
dependencies:
"@ant-design/colors" "^3.1.0"
"@ant-design/icons-svg" "^4.0.0"
classnames "^2.2.6"
insert-css "^2.0.0"
rc-util "^4.9.0"
"@b-design/ui@^1.0.63": "@b-design/ui@^1.0.63":
version "1.0.63" version "1.0.63"
resolved "https://registry.npmmirror.com/@b-design/ui/download/@b-design/ui-1.0.63.tgz" resolved "https://registry.npmmirror.com/@b-design/ui/download/@b-design/ui-1.0.63.tgz"
@ -2139,6 +2162,13 @@ acorn@^7.1.0, acorn@^7.4.0:
resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz" resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
add-dom-event-listener@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz#6a92db3a0dd0abc254e095c0f1dc14acbbaae310"
integrity sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==
dependencies:
object-assign "4.x"
address@1.1.2, address@^1.0.1: address@1.1.2, address@^1.0.1:
version "1.1.2" version "1.1.2"
resolved "https://registry.npmjs.org/address/-/address-1.1.2.tgz" resolved "https://registry.npmjs.org/address/-/address-1.1.2.tgz"
@ -5289,6 +5319,11 @@ getpass@^0.1.1:
dependencies: dependencies:
assert-plus "^1.0.0" assert-plus "^1.0.0"
github-markdown-css@^5.1.0:
version "5.1.0"
resolved "https://registry.npmmirror.com/github-markdown-css/-/github-markdown-css-5.1.0.tgz#a96281cd90a8969e3c13b9d3ca6a733a523a00a6"
integrity sha512-QLtORwHHtUHhPMHu7i4GKfP6Vx5CWZn+NKQXe+cBhslY1HEt0CTEkP4d/vSROKV0iIJSpl4UtlQ16AD8C6lMug==
glob-parent@^3.1.0: glob-parent@^3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.npmmirror.com/glob-parent/download/glob-parent-3.1.0.tgz?cache=0&sync_timestamp=1632953810778&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fglob-parent%2Fdownload%2Fglob-parent-3.1.0.tgz" resolved "https://registry.npmmirror.com/glob-parent/download/glob-parent-3.1.0.tgz?cache=0&sync_timestamp=1632953810778&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fglob-parent%2Fdownload%2Fglob-parent-3.1.0.tgz"
@ -5920,6 +5955,11 @@ inline-style-parser@0.1.1:
resolved "https://registry.npm.taobao.org/inline-style-parser/download/inline-style-parser-0.1.1.tgz" resolved "https://registry.npm.taobao.org/inline-style-parser/download/inline-style-parser-0.1.1.tgz"
integrity sha1-7Io7QpJ06cCh8cT/qUU6f+9yzqE= integrity sha1-7Io7QpJ06cCh8cT/qUU6f+9yzqE=
insert-css@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/insert-css/-/insert-css-2.0.0.tgz#eb5d1097b7542f4c79ea3060d3aee07d053880f4"
integrity sha512-xGq5ISgcUP5cvGkS2MMFLtPDBtrtQPSFfC6gA6U8wHKqfjTIMZLZNxOItQnoSjdOzlXOLU/yD32RKC4SvjNbtA==
internal-ip@^4.3.0: internal-ip@^4.3.0:
version "4.3.0" version "4.3.0"
resolved "https://registry.npmmirror.com/internal-ip/download/internal-ip-4.3.0.tgz" resolved "https://registry.npmmirror.com/internal-ip/download/internal-ip-4.3.0.tgz"
@ -7904,7 +7944,7 @@ oauth-sign@~0.9.0:
resolved "https://registry.nlark.com/oauth-sign/download/oauth-sign-0.9.0.tgz" resolved "https://registry.nlark.com/oauth-sign/download/oauth-sign-0.9.0.tgz"
integrity sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU= integrity sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU=
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: object-assign@4.x, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@ -9229,6 +9269,15 @@ prop-types@^15.0.0, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1,
object-assign "^4.1.1" object-assign "^4.1.1"
react-is "^16.8.1" react-is "^16.8.1"
prop-types@^15.5.10:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.13.1"
property-information@^6.0.0: property-information@^6.0.0:
version "6.0.1" version "6.0.1"
resolved "https://registry.nlark.com/property-information/download/property-information-6.0.1.tgz" resolved "https://registry.nlark.com/property-information/download/property-information-6.0.1.tgz"
@ -9421,6 +9470,17 @@ rax@^1.1.0:
rax-create-factory "^1.0.0" rax-create-factory "^1.0.0"
rax-is-valid-element "^1.0.0" rax-is-valid-element "^1.0.0"
rc-util@^4.9.0:
version "4.21.1"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.21.1.tgz#88602d0c3185020aa1053d9a1e70eac161becb05"
integrity sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==
dependencies:
add-dom-event-listener "^1.1.0"
prop-types "^15.5.10"
react-is "^16.12.0"
react-lifecycles-compat "^3.0.4"
shallowequal "^1.1.0"
react-cookies@^0.1.1: react-cookies@^0.1.1:
version "0.1.1" version "0.1.1"
resolved "https://registry.npm.taobao.org/react-cookies/download/react-cookies-0.1.1.tgz" resolved "https://registry.npm.taobao.org/react-cookies/download/react-cookies-0.1.1.tgz"
@ -9508,7 +9568,12 @@ react-i18next@11.13.0:
"@babel/runtime" "^7.14.5" "@babel/runtime" "^7.14.5"
html-parse-stringify "^3.0.1" html-parse-stringify "^3.0.1"
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: react-icons@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.4.0.tgz#a13a8a20c254854e1ec9aecef28a95cdf24ef703"
integrity sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg==
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1" version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@ -11135,6 +11200,11 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.3:
resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz" resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
tinycolor2@^1.4.1:
version "1.4.2"
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803"
integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==
to-arraybuffer@^1.0.0: to-arraybuffer@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.npm.taobao.org/to-arraybuffer/download/to-arraybuffer-1.0.1.tgz" resolved "https://registry.npm.taobao.org/to-arraybuffer/download/to-arraybuffer-1.0.1.tgz"