fix(plugin-workflow): fix status button styles (#2516)
This commit is contained in:
parent
96caf9befa
commit
17f438ac39
@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
ActionContextProvider,
|
ActionContextProvider,
|
||||||
css,
|
cx,
|
||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
useAPIClient,
|
useAPIClient,
|
||||||
useCompile,
|
useCompile,
|
||||||
@ -12,15 +12,14 @@ import { Breadcrumb, Dropdown, Space, Tag } from 'antd';
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { CanvasContent } from './CanvasContent';
|
import { CanvasContent } from './CanvasContent';
|
||||||
import { ExecutionStatusOptionsMap, JobStatusOptions, JobStatusOptionsMap } from './constants';
|
import { ExecutionStatusOptionsMap, JobStatusOptions } from './constants';
|
||||||
import { FlowContext, useFlowContext } from './FlowContext';
|
import { FlowContext, useFlowContext } from './FlowContext';
|
||||||
import { lang, NAMESPACE } from './locale';
|
import { lang, NAMESPACE } from './locale';
|
||||||
import { instructions } from './nodes';
|
import { instructions } from './nodes';
|
||||||
import useStyles from './style';
|
import useStyles from './style';
|
||||||
import { linkNodes } from './utils';
|
import { linkNodes } from './utils';
|
||||||
import { DownOutlined } from '@ant-design/icons';
|
import { DownOutlined } from '@ant-design/icons';
|
||||||
import { StatusIcon } from './components/StatusIcon';
|
import { StatusButton } from './components/StatusButton';
|
||||||
import classnames from 'classnames';
|
|
||||||
|
|
||||||
function attachJobs(nodes, jobs: any[] = []): void {
|
function attachJobs(nodes, jobs: any[] = []): void {
|
||||||
const nodesMap = new Map();
|
const nodesMap = new Map();
|
||||||
@ -174,18 +173,23 @@ function ExecutionsDropdown(props) {
|
|||||||
return execution ? (
|
return execution ? (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
menu={{
|
menu={{
|
||||||
className: styles.executionsDropdownRowClass,
|
|
||||||
onClick,
|
onClick,
|
||||||
|
defaultSelectedKeys: [`${execution.id}`],
|
||||||
|
className: cx(styles.dropdownClass, styles.executionsDropdownRowClass),
|
||||||
items: [...executionsAfter, execution, ...executionsBefore].map((item) => {
|
items: [...executionsAfter, execution, ...executionsBefore].map((item) => {
|
||||||
return {
|
return {
|
||||||
key: item.id,
|
key: item.id,
|
||||||
label: (
|
label: (
|
||||||
<span className={classnames('row', { current: item.id === execution.id })}>
|
<>
|
||||||
<span className="id">{`#${item.id}`}</span>
|
<span className="id">{`#${item.id}`}</span>
|
||||||
<time>{str2moment(item.createdAt).format('YYYY-MM-DD HH:mm:ss')}</time>
|
<time>{str2moment(item.createdAt).format('YYYY-MM-DD HH:mm:ss')}</time>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
icon: (
|
||||||
|
<span>
|
||||||
|
<StatusButton statusMap={ExecutionStatusOptionsMap} status={item.status} />
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
icon: <StatusIcon status={item.status} />,
|
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
|
@ -159,7 +159,7 @@ export function WorkflowCanvas() {
|
|||||||
menu={{
|
menu={{
|
||||||
onClick: onSwitchVersion,
|
onClick: onSwitchVersion,
|
||||||
defaultSelectedKeys: [`${workflow.id}`],
|
defaultSelectedKeys: [`${workflow.id}`],
|
||||||
className: cx(styles.workflowVersionDropdownClass),
|
className: cx(styles.dropdownClass, styles.workflowVersionDropdownClass),
|
||||||
items: revisions
|
items: revisions
|
||||||
.sort((a, b) => b.id - a.id)
|
.sort((a, b) => b.id - a.id)
|
||||||
.map((item, index) => ({
|
.map((item, index) => ({
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button, Tag } from 'antd';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
import { createStyles } from '@nocobase/client';
|
||||||
|
|
||||||
|
const useStyles = createStyles(({ css, token }) => ({
|
||||||
|
statusButtonClass: css`
|
||||||
|
border: none;
|
||||||
|
.ant-tag {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
noStatusButtonClass: css`
|
||||||
|
border-width: 2px;
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export function StatusButton(props) {
|
||||||
|
const { styles } = useStyles();
|
||||||
|
let tag = null;
|
||||||
|
if (typeof props.status !== 'undefined' && props.statusMap?.[props.status]) {
|
||||||
|
const { icon, color } = props.statusMap[props.status];
|
||||||
|
tag = <Tag color={color}>{icon}</Tag>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
{...props}
|
||||||
|
shape="circle"
|
||||||
|
size="small"
|
||||||
|
className={classnames(tag ? styles.statusButtonClass : styles.noStatusButtonClass, props.className)}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
@ -1,30 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Tag } from 'antd';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
|
|
||||||
import { createStyles } from '@nocobase/client';
|
|
||||||
|
|
||||||
import { JobStatusOptionsMap } from '../constants';
|
|
||||||
|
|
||||||
const useStyles = createStyles(({ css, token }) => ({
|
|
||||||
statusIconClass: css`
|
|
||||||
padding: 0;
|
|
||||||
width: ${token.sizeLG}px;
|
|
||||||
height: ${token.sizeLG}px;
|
|
||||||
line-height: ${token.sizeLG}px;
|
|
||||||
margin-right: 0;
|
|
||||||
border-radius: 50%;
|
|
||||||
text-align: center;
|
|
||||||
`,
|
|
||||||
}));
|
|
||||||
|
|
||||||
export function StatusIcon(props) {
|
|
||||||
const { icon, color } = JobStatusOptionsMap[props.status];
|
|
||||||
const { styles } = useStyles();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tag color={color} className={classnames(styles.statusIconClass, props.className)}>
|
|
||||||
{icon}
|
|
||||||
</Tag>
|
|
||||||
);
|
|
||||||
}
|
|
@ -5,6 +5,8 @@ import {
|
|||||||
CheckOutlined,
|
CheckOutlined,
|
||||||
MinusOutlined,
|
MinusOutlined,
|
||||||
ExclamationOutlined,
|
ExclamationOutlined,
|
||||||
|
HourglassOutlined,
|
||||||
|
LoadingOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { NAMESPACE } from './locale';
|
import { NAMESPACE } from './locale';
|
||||||
|
|
||||||
@ -24,48 +26,56 @@ export const ExecutionStatusOptions = [
|
|||||||
value: EXECUTION_STATUS.QUEUEING,
|
value: EXECUTION_STATUS.QUEUEING,
|
||||||
label: `{{t("Queueing", { ns: "${NAMESPACE}" })}}`,
|
label: `{{t("Queueing", { ns: "${NAMESPACE}" })}}`,
|
||||||
color: 'blue',
|
color: 'blue',
|
||||||
|
icon: <HourglassOutlined />,
|
||||||
description: `{{t("Triggered but still waiting in queue to execute.", { ns: "${NAMESPACE}" })}}`,
|
description: `{{t("Triggered but still waiting in queue to execute.", { ns: "${NAMESPACE}" })}}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: EXECUTION_STATUS.STARTED,
|
value: EXECUTION_STATUS.STARTED,
|
||||||
label: `{{t("On going", { ns: "${NAMESPACE}" })}}`,
|
label: `{{t("On going", { ns: "${NAMESPACE}" })}}`,
|
||||||
color: 'gold',
|
color: 'gold',
|
||||||
|
icon: <LoadingOutlined />,
|
||||||
description: `{{t("Started and executing, maybe waiting for an async callback (manual, delay etc.).", { ns: "${NAMESPACE}" })}}`,
|
description: `{{t("Started and executing, maybe waiting for an async callback (manual, delay etc.).", { ns: "${NAMESPACE}" })}}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: EXECUTION_STATUS.RESOLVED,
|
value: EXECUTION_STATUS.RESOLVED,
|
||||||
label: `{{t("Resolved", { ns: "${NAMESPACE}" })}}`,
|
label: `{{t("Resolved", { ns: "${NAMESPACE}" })}}`,
|
||||||
color: 'green',
|
color: 'green',
|
||||||
|
icon: <CheckOutlined />,
|
||||||
description: `{{t("Successfully finished.", { ns: "${NAMESPACE}" })}}`,
|
description: `{{t("Successfully finished.", { ns: "${NAMESPACE}" })}}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: EXECUTION_STATUS.FAILED,
|
value: EXECUTION_STATUS.FAILED,
|
||||||
label: `{{t("Failed", { ns: "${NAMESPACE}" })}}`,
|
label: `{{t("Failed", { ns: "${NAMESPACE}" })}}`,
|
||||||
color: 'red',
|
color: 'red',
|
||||||
|
icon: <ExclamationOutlined />,
|
||||||
description: `{{t("Failed to satisfy node configurations.", { ns: "${NAMESPACE}" })}}`,
|
description: `{{t("Failed to satisfy node configurations.", { ns: "${NAMESPACE}" })}}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: EXECUTION_STATUS.ERROR,
|
value: EXECUTION_STATUS.ERROR,
|
||||||
label: `{{t("Error", { ns: "${NAMESPACE}" })}}`,
|
label: `{{t("Error", { ns: "${NAMESPACE}" })}}`,
|
||||||
color: 'red',
|
color: 'red',
|
||||||
|
icon: <CloseOutlined />,
|
||||||
description: `{{t("Some node meets error.", { ns: "${NAMESPACE}" })}}`,
|
description: `{{t("Some node meets error.", { ns: "${NAMESPACE}" })}}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: EXECUTION_STATUS.ABORTED,
|
value: EXECUTION_STATUS.ABORTED,
|
||||||
label: `{{t("Aborted", { ns: "${NAMESPACE}" })}}`,
|
label: `{{t("Aborted", { ns: "${NAMESPACE}" })}}`,
|
||||||
color: 'red',
|
color: 'red',
|
||||||
|
icon: <MinusOutlined rotate={90} />,
|
||||||
description: `{{t("Running of some node was aborted by program flow.", { ns: "${NAMESPACE}" })}}`,
|
description: `{{t("Running of some node was aborted by program flow.", { ns: "${NAMESPACE}" })}}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: EXECUTION_STATUS.CANCELED,
|
value: EXECUTION_STATUS.CANCELED,
|
||||||
label: `{{t("Canceled", { ns: "${NAMESPACE}" })}}`,
|
label: `{{t("Canceled", { ns: "${NAMESPACE}" })}}`,
|
||||||
color: 'volcano',
|
color: 'volcano',
|
||||||
|
icon: <MinusOutlined rotate={45} />,
|
||||||
description: `{{t("Manually canceled whole execution when waiting.", { ns: "${NAMESPACE}" })}}`,
|
description: `{{t("Manually canceled whole execution when waiting.", { ns: "${NAMESPACE}" })}}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: EXECUTION_STATUS.REJECTED,
|
value: EXECUTION_STATUS.REJECTED,
|
||||||
label: `{{t("Rejected", { ns: "${NAMESPACE}" })}}`,
|
label: `{{t("Rejected", { ns: "${NAMESPACE}" })}}`,
|
||||||
color: 'volcano',
|
color: 'volcano',
|
||||||
|
icon: <MinusOutlined />,
|
||||||
description: `{{t("Rejected from a manual node.", { ns: "${NAMESPACE}" })}}`,
|
description: `{{t("Rejected from a manual node.", { ns: "${NAMESPACE}" })}}`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -36,7 +36,7 @@ import query from './query';
|
|||||||
import request from './request';
|
import request from './request';
|
||||||
import sql from './sql';
|
import sql from './sql';
|
||||||
import update from './update';
|
import update from './update';
|
||||||
import { StatusIcon } from '../components/StatusIcon';
|
import { StatusButton } from '../components/StatusButton';
|
||||||
|
|
||||||
export interface Instruction {
|
export interface Instruction {
|
||||||
title: string;
|
title: string;
|
||||||
@ -237,16 +237,6 @@ export function RemoveButton() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function InnerJobButton({ job, ...props }) {
|
|
||||||
const { styles } = useStyles();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button {...props} shape="circle" size="small" className={cx(styles.nodeJobButtonClass, props.className)}>
|
|
||||||
<StatusIcon status={job.status} />
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function JobButton() {
|
export function JobButton() {
|
||||||
const { execution, setViewJob } = useFlowContext();
|
const { execution, setViewJob } = useFlowContext();
|
||||||
const { jobs } = useNodeContext() ?? {};
|
const { jobs } = useNodeContext() ?? {};
|
||||||
@ -257,20 +247,7 @@ export function JobButton() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!jobs.length) {
|
if (!jobs.length) {
|
||||||
return (
|
return <StatusButton className={styles.nodeJobButtonClass} disabled />;
|
||||||
<span
|
|
||||||
className={cx(
|
|
||||||
styles.nodeJobButtonClass,
|
|
||||||
css`
|
|
||||||
border: 2px solid #d9d9d9;
|
|
||||||
border-radius: 50%;
|
|
||||||
cursor: not-allowed;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
`,
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onOpenJob({ key }) {
|
function onOpenJob({ key }) {
|
||||||
@ -285,31 +262,30 @@ export function JobButton() {
|
|||||||
return {
|
return {
|
||||||
key: job.id,
|
key: job.id,
|
||||||
label: (
|
label: (
|
||||||
<div
|
<>
|
||||||
className={css`
|
<StatusButton statusMap={JobStatusOptionsMap} status={job.status} />
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5em;
|
|
||||||
|
|
||||||
time {
|
|
||||||
color: #999;
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<StatusIcon status={job.status} />
|
|
||||||
<time>{str2moment(job.updatedAt).format('YYYY-MM-DD HH:mm:ss')}</time>
|
<time>{str2moment(job.updatedAt).format('YYYY-MM-DD HH:mm:ss')}</time>
|
||||||
</div>
|
</>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
onClick: onOpenJob,
|
onClick: onOpenJob,
|
||||||
|
className: styles.dropdownClass,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<InnerJobButton job={jobs[jobs.length - 1]} />
|
<StatusButton
|
||||||
|
statusMap={JobStatusOptionsMap}
|
||||||
|
status={jobs[jobs.length - 1].status}
|
||||||
|
className={styles.nodeJobButtonClass}
|
||||||
|
/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
) : (
|
) : (
|
||||||
<InnerJobButton job={jobs[0]} onClick={() => setViewJob(jobs[0])} />
|
<StatusButton
|
||||||
|
statusMap={JobStatusOptionsMap}
|
||||||
|
status={jobs[0].status}
|
||||||
|
onClick={() => setViewJob(jobs[0])}
|
||||||
|
className={styles.nodeJobButtonClass}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,12 +15,11 @@ const useStyles = createStyles(({ css, token }) => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
background: ${token.colorBgContainer};
|
background: ${token.colorBgContainer};
|
||||||
border-bottom: 1px solid ${token.colorBorder};
|
border-bottom: 1px solid ${token.colorBorderSecondary};
|
||||||
|
|
||||||
header {
|
header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 2rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
aside {
|
aside {
|
||||||
@ -55,7 +54,7 @@ const useStyles = createStyles(({ css, token }) => {
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|
||||||
workflowVersionDropdownClass: css`
|
dropdownClass: css`
|
||||||
.ant-dropdown-menu-item {
|
.ant-dropdown-menu-item {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
.ant-dropdown-menu-title-content {
|
.ant-dropdown-menu-title-content {
|
||||||
@ -65,11 +64,16 @@ const useStyles = createStyles(({ css, token }) => {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
||||||
time {
|
time {
|
||||||
width: 12em;
|
width: 14em;
|
||||||
color: ${token.colorText};
|
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
|
||||||
|
workflowVersionDropdownClass: css`
|
||||||
|
.ant-dropdown-menu-item {
|
||||||
|
.ant-dropdown-menu-title-content {
|
||||||
strong {
|
strong {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
@ -90,24 +94,11 @@ const useStyles = createStyles(({ css, token }) => {
|
|||||||
`,
|
`,
|
||||||
|
|
||||||
executionsDropdownRowClass: css`
|
executionsDropdownRowClass: css`
|
||||||
.row {
|
.ant-dropdown-menu-item {
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
|
|
||||||
&.current {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.id {
|
.id {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
time {
|
|
||||||
width: 12em;
|
|
||||||
color: ${token.colorText};
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|
||||||
@ -283,10 +274,6 @@ const useStyles = createStyles(({ css, token }) => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: ${token.colorTextLightSolid};
|
color: ${token.colorTextLightSolid};
|
||||||
|
|
||||||
&[type='button'] {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
|
|
||||||
nodeHeaderClass: css`
|
nodeHeaderClass: css`
|
||||||
|
Loading…
Reference in New Issue
Block a user