@ant-design/icons#ClockCircleOutlined TypeScript Examples
The following examples show how to use
@ant-design/icons#ClockCircleOutlined.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: general-time-picker.editor.spec.tsx From next-basics with GNU General Public License v3.0 | 6 votes |
describe("GeneralTimePickerEditor", () => {
it("should work", () => {
mockUseBuilderNode.mockReturnValueOnce({
type: "brick",
id: "B-001",
brick: "general-time-picker",
alias: "my-brick",
$$parsedProperties: {
placeholder: "请选择时间",
},
});
const wrapper = shallow(<GeneralTimePickerEditor nodeUid={1} />);
expect(wrapper.find(".placeholder").text()).toBe("请选择时间");
expect(wrapper.find(ClockCircleOutlined).length).toEqual(1);
});
});
Example #2
Source File: time-range-picker.editor.tsx From next-basics with GNU General Public License v3.0 | 6 votes |
export function TimeRangePickerEditor({
nodeUid,
}: EditorComponentProps): React.ReactElement {
const node = useBuilderNode<TimeRangePickerProperties>({ nodeUid });
const { required, label } = node.$$parsedProperties;
return (
<EditorContainer nodeUid={nodeUid}>
<div className={formSharedStyle.formItemWrapper}>
<div
className={classNames(formSharedStyle.labelContainer, {
[formSharedStyle.requireMark]: required,
})}
>
<span
className={classNames({ [formSharedStyle.formLabel]: !!label })}
>
{formCommonFieldDisplay(label)}
</span>
</div>
<div className={styles.rangePickerItem}>
<div className={styles.leftPicker}>
<ClockCircleOutlined />
</div>
<span className={styles.union}></span>
<div className={styles.rightPicker}>
<ClockCircleOutlined />
</div>
</div>
</div>
</EditorContainer>
);
}
Example #3
Source File: StatusIcon.tsx From datart with Apache License 2.0 | 6 votes |
WaitingIcon: React.FC<{
title: React.ReactNode;
onClick?: React.MouseEventHandler<HTMLSpanElement> | undefined;
onMouseEnter?: React.MouseEventHandler<HTMLSpanElement> | undefined;
}> = ({ title, onClick, onMouseEnter }) => {
return (
<Tooltip title={title ?? 'waitingLoad'}>
<Button
onClick={onClick}
onMouseEnter={onMouseEnter}
icon={<ClockCircleOutlined style={{ color: PRIMARY }} />}
type="link"
/>
</Tooltip>
);
}
Example #4
Source File: ToggleButtonChartFilter.tsx From posthog-foss with MIT License | 5 votes |
export function ToggleButtonChartFilter({
onChange = noop,
disabled = false,
simpleMode,
}: ToggleButtonChartFilterProps): JSX.Element | null {
const { insightProps } = useValues(insightLogic)
const { clickhouseFeaturesEnabled, aggregationTargetLabel } = useValues(funnelLogic(insightProps))
const { chartFilter } = useValues(chartFilterLogic(insightProps))
const { setChartFilter } = useActions(chartFilterLogic(insightProps))
const defaultDisplay = FunnelVizType.Steps
const options = [
{
key: FunnelVizType.Steps,
label: 'Conversion steps',
description: `Track ${aggregationTargetLabel.plural} progress between steps of the funnel`,
icon: <FunnelPlotOutlined />,
},
{
key: FunnelVizType.TimeToConvert,
label: 'Time to convert',
description: `Track how long it takes for ${aggregationTargetLabel.plural} to convert`,
icon: <ClockCircleOutlined />,
hidden: !clickhouseFeaturesEnabled,
},
{
key: FunnelVizType.Trends,
label: 'Historical trends',
description: "Track how this funnel's conversion rate is trending over time",
icon: <LineChartOutlined />,
hidden: !clickhouseFeaturesEnabled,
},
]
if (options.filter((option) => !option.hidden).length <= 1) {
return null
}
const innerContent = (
<div className="funnel-chart-filter">
<DropdownSelector
options={options}
value={chartFilter || defaultDisplay}
onValueChange={(val) => {
const valueTyped = val as FunnelVizType
setChartFilter(valueTyped)
onChange(valueTyped)
}}
disabled={disabled}
hideDescriptionOnDisplay
compact={simpleMode}
/>
</div>
)
return simpleMode ? (
innerContent
) : (
<div>
<h4 className="secondary">Graph Type</h4>
{innerContent}
</div>
)
}
Example #5
Source File: general-time-picker.editor.tsx From next-basics with GNU General Public License v3.0 | 5 votes |
export function GeneralTimePickerEditor({
nodeUid,
}: EditorComponentProps): React.ReactElement {
const node = useBuilderNode<GeneralTimePickerProperties>({ nodeUid });
const { label, required, placeholder } = node.$$parsedProperties;
return (
<EditorContainer nodeUid={nodeUid}>
<div className={formSharedStyle.formItemWrapper}>
<div
className={classNames(formSharedStyle.labelContainer, {
[formSharedStyle.requireMark]: required,
})}
>
<span
className={classNames({ [formSharedStyle.formLabel]: !!label })}
>
{formCommonFieldDisplay(label)}
</span>
</div>
<div
className={formSharedStyle.formInputItem}
style={{ flex: "0 0 200px" }}
>
<div
className={classNames(
formSharedStyle.placeholder,
formSharedStyle.placeholderOffset
)}
>
<span>{formCommonFieldDisplay(placeholder)}</span>
</div>
<span className={styles.suffixIcon}>
<ClockCircleOutlined />
</span>
</div>
</div>
</EditorContainer>
);
}
Example #6
Source File: Paths.tsx From posthog-foss with MIT License | 4 votes |
export function Paths({ dashboardItemId = null, color = 'white' }: PathsProps): JSX.Element {
const canvas = useRef<HTMLDivElement>(null)
const { width: canvasWidth = FALLBACK_CANVAS_WIDTH, height: canvasHeight = FALLBACK_CANVAS_HEIGHT } =
useResizeObserver({ ref: canvas })
const { insightProps } = useValues(insightLogic)
const { paths, resultsLoading: pathsLoading, filter, pathsError } = useValues(pathsLogic(insightProps))
const { openPersonsModal, setFilter, updateExclusions, viewPathToFunnel } = useActions(pathsLogic(insightProps))
const [pathItemCards, setPathItemCards] = useState<PathNodeData[]>([])
const { user } = useValues(userLogic)
const hasAdvancedPaths = user?.organization?.available_features?.includes(AvailableFeature.PATHS_ADVANCED)
useEffect(() => {
setPathItemCards([])
renderPaths()
}, [paths, !pathsLoading, canvasWidth, canvasHeight, color])
const createCanvas = (width: number, height: number): D3Selector => {
return d3
.select(canvas.current)
.append('svg')
.style('background', 'var(--item-background)')
.style('width', width)
.style('height', height)
}
const createSankey = (width: number, height: number): any => {
// @ts-expect-error - d3 sankey typing things
return new Sankey.sankey()
.nodeId((d: PathNodeData) => d.name)
.nodeAlign(Sankey.sankeyJustify)
.nodeSort(null)
.nodeWidth(15)
.size([width, height])
}
const appendPathNodes = (svg: any, nodes: PathNodeData[]): void => {
svg.append('g')
.selectAll('rect')
.data(nodes)
.join('rect')
.attr('x', (d: PathNodeData) => d.x0 + 1)
.attr('y', (d: PathNodeData) => d.y0)
.attr('height', (d: PathNodeData) => d.y1 - d.y0)
.attr('width', (d: PathNodeData) => d.x1 - d.x0 - 2)
.attr('fill', (d: PathNodeData) => {
let c
for (const link of d.sourceLinks) {
if (c === undefined) {
c = link.color
} else if (c !== link.color) {
c = null
}
}
if (c === undefined) {
for (const link of d.targetLinks) {
if (c === undefined) {
c = link.color
} else if (c !== link.color) {
c = null
}
}
}
if (isSelectedPathStartOrEnd(filter, d)) {
return d3.color('purple')
}
const startNodeColor =
c && d3.color(c) ? d3.color(c) : isMonochrome(color) ? d3.color('#5375ff') : d3.color('white')
return startNodeColor
})
.on('mouseover', (data: PathNodeData) => {
if (data.y1 - data.y0 > HIDE_PATH_CARD_HEIGHT) {
return
}
setPathItemCards(
nodes.map((node: PathNodeData) =>
node.index === data.index
? { ...node, visible: true }
: { ...node, visible: node.y1 - node.y0 > HIDE_PATH_CARD_HEIGHT }
)
)
})
.append('title')
.text((d: PathNodeData) => `${stripHTTP(d.name)}\n${d.value.toLocaleString()}`)
}
const appendDropoffs = (svg: any): void => {
const dropOffGradient = svg
.append('defs')
.append('linearGradient')
.attr('id', 'dropoff-gradient')
.attr('gradientTransform', 'rotate(90)')
dropOffGradient
.append('stop')
.attr('offset', '0%')
.attr('stop-color', color === 'white' ? 'rgba(220,53,69,0.7)' : 'rgb(220,53,69)')
dropOffGradient
.append('stop')
.attr('offset', '100%')
.attr('stop-color', color === 'white' ? '#fff' : 'var(--item-background)')
}
const appendPathLinks = (svg: any, links: PathNodeData[], nodes: PathNodeData[]): void => {
const link = svg
.append('g')
.attr('fill', 'none')
.selectAll('g')
.data(links)
.join('g')
.attr('stroke', () => (isMonochrome(color) ? 'var(--primary)' : 'white'))
.attr('opacity', 0.35)
link.append('path')
.attr('d', Sankey.sankeyLinkHorizontal())
.attr('id', (d: PathNodeData) => `path-${d.index}`)
.attr('stroke-width', (d: PathNodeData) => {
return Math.max(1, d.width)
})
.on('mouseover', (data: PathNodeData) => {
svg.select(`#path-${data.index}`).attr('stroke', 'blue')
if (data?.source?.targetLinks.length === 0) {
return
}
const nodesToColor = [data.source]
const pathCardsToShow: number[] = []
while (nodesToColor.length > 0) {
const _node = nodesToColor.pop()
_node?.targetLinks.forEach((_link: PathTargetLink) => {
svg.select(`#path-${_link.index}`).attr('stroke', 'blue')
nodesToColor.push(_link.source)
pathCardsToShow.push(_link.source.index)
})
}
const pathCards = [data.target]
pathCardsToShow.push(data.target.index, data.source.index)
while (pathCards.length > 0) {
const node = pathCards.pop()
node?.sourceLinks.forEach((l: PathTargetLink) => {
pathCards.push(l.target)
pathCardsToShow.push(l.target.index)
})
}
setPathItemCards(
nodes.map((node: PathNodeData) => ({
...node,
...{
visible: pathCardsToShow.includes(node.index)
? true
: node.y1 - node.y0 > HIDE_PATH_CARD_HEIGHT,
},
}))
)
})
.on('mouseleave', () => {
svg.selectAll('path').attr('stroke', () => (isMonochrome(color) ? 'var(--primary)' : 'white'))
})
link.append('g')
.append('path')
.attr('d', (data: PathNodeData) => {
if (data.source.layer === 0) {
return
}
const _height =
data.source.y1 -
data.source.y0 -
data.source.sourceLinks.reduce((prev, curr) => prev + curr.width, 0)
return roundedRect(0, 0, 30, _height, Math.min(25, _height), false, true, false, false)
})
.attr('fill', 'url(#dropoff-gradient)')
.attr('stroke-width', 0)
.attr('transform', (data: PathNodeData) => {
return (
'translate(' +
Math.round(data.source.x1) +
',' +
Math.round(data.source.y0 + data.source.sourceLinks.reduce((prev, curr) => prev + curr.width, 0)) +
')'
)
})
}
const addChartAxisLines = (svg: any, height: number, nodes: PathNodeData[], maxLayer: number): void => {
if (maxLayer > 5) {
const arr = [...Array(maxLayer)]
const minWidthApart = nodes[1].x0 - nodes[0].x0
arr.forEach((_, i) => {
svg.append('line')
.style('stroke', 'var(--border)')
.attr('stroke-width', 2)
.attr('x1', minWidthApart * (i + 1) - 20)
.attr('y1', 0)
.attr('x2', minWidthApart * (i + 1) - 20)
.attr('y2', height)
})
}
}
function renderPaths(): void {
const elements = document
?.getElementById(`'${dashboardItemId || DEFAULT_PATHS_ID}'`)
?.querySelectorAll(`.paths svg`)
elements?.forEach((node) => node?.parentNode?.removeChild(node))
if (!paths || paths.nodes.length === 0) {
setPathItemCards([])
return
}
const maxLayer = paths.links.reduce((prev, curr) => {
// @ts-expect-error - sometimes target is an object instead of string
const currNum = curr.target.name || curr.target
return Math.max(prev, Number(currNum.match(/[^_]*/)))
}, 0)
const minWidth = canvasWidth > FALLBACK_CANVAS_WIDTH || maxLayer < 3 ? canvasWidth : FALLBACK_CANVAS_WIDTH
const width = maxLayer > 5 && canvasWidth ? (minWidth / 5) * maxLayer : minWidth
const height = canvasHeight
const svg = createCanvas(width, height)
const sankey = createSankey(width, height)
const { nodes, links } = sankey({
nodes: paths.nodes.map((d) => ({ ...d })),
links: paths.links.map((d) => ({ ...d })),
})
setPathItemCards(
nodes.map((node: PathNodeData) => ({ ...node, visible: node.y1 - node.y0 > HIDE_PATH_CARD_HEIGHT }))
)
appendPathNodes(svg, nodes)
appendDropoffs(svg)
appendPathLinks(svg, links, nodes)
addChartAxisLines(svg, height, nodes, maxLayer)
}
return (
<div className="paths-container" id={`'${dashboardItemId || DEFAULT_PATHS_ID}'`}>
<div ref={canvas} className="paths" data-attr="paths-viz">
{!pathsLoading && paths && paths.nodes.length === 0 && !pathsError && <InsightEmptyState />}
{!pathsError &&
pathItemCards &&
pathItemCards.map((pathItemCard: PathNodeData, idx) => {
const continuingValue = getContinuingValue(pathItemCard.sourceLinks)
const dropOffValue = getDropOffValue(pathItemCard)
return (
<Tooltip key={idx} title={pageUrl(pathItemCard)} placement="right">
<Dropdown
key={idx}
overlay={
<Menu
style={{
marginTop: -5,
border: '1px solid var(--border)',
borderRadius: '0px 0px 4px 4px',
width: 200,
}}
>
{pathItemCard.sourceLinks.length > 0 && (
<Menu.Item
disabled
className="pathcard-dropdown-info-option text-small"
style={{
borderBottom: `${
dropOffValue > 0 || pathItemCard.targetLinks.length > 0
? '1px solid var(--border)'
: ''
}`,
}}
>
<span className="text-small">
<span style={{ paddingRight: 8 }}>
<IconPathsCompletedArrow />
</span>{' '}
Continuing
</span>{' '}
<span className="primary text-small">
<ValueInspectorButton
style={{ paddingRight: 0, fontSize: 12 }}
onClick={() => openPersonsModal(pathItemCard.name)}
>
{continuingValue}
<span className="text-muted-alt" style={{ paddingLeft: 4 }}>
(
{((continuingValue / pathItemCard.value) * 100).toFixed(
1
)}
%)
</span>
</ValueInspectorButton>
</span>
</Menu.Item>
)}
{dropOffValue > 0 && (
<Menu.Item
disabled
className="pathcard-dropdown-info-option text-small"
style={{
borderBottom: '1px solid var(--border)',
}}
>
<span className="text-small" style={{ display: 'flex' }}>
<span
style={{
paddingRight: 8,
display: 'flex',
alignItems: 'center',
}}
>
<IconPathsDropoffArrow />
</span>{' '}
Dropping off
</span>{' '}
<span className="primary">
<ValueInspectorButton
style={{ paddingRight: 0, fontSize: 12 }}
onClick={() =>
openPersonsModal(
undefined,
undefined,
pathItemCard.name
)
}
>
{dropOffValue}{' '}
<span
className="text-muted-alt text-small"
style={{ paddingLeft: 4 }}
>
(
{((dropOffValue / pathItemCard.value) * 100).toFixed(1)}
%)
</span>
</ValueInspectorButton>
</span>
</Menu.Item>
)}
{pathItemCard.targetLinks.length > 0 && (
<Menu.Item
disabled
className="pathcard-dropdown-info-option"
style={{
padding: '5px 8px',
fontWeight: 500,
fontSize: 12,
}}
>
<ClockCircleOutlined
style={{ color: 'var(--muted)', fontSize: 16 }}
/>
<span
className="text-small"
style={{
wordWrap: 'break-word',
whiteSpace: 'normal',
paddingLeft: 8,
}}
>
Average time from previous step{' '}
</span>
{humanFriendlyDuration(
pathItemCard.targetLinks[0].average_conversion_time / 1000
)}
</Menu.Item>
)}
</Menu>
}
placement="bottomCenter"
>
<Button
key={idx}
style={{
position: 'absolute',
left:
pathItemCard.sourceLinks.length === 0
? pathItemCard.x0 - (200 - 7)
: pathItemCard.x0 + 7,
top:
pathItemCard.sourceLinks.length > 0
? pathItemCard.y0 + 5
: pathItemCard.y0 + (pathItemCard.y1 - pathItemCard.y0) / 2,
background: 'white',
width: 200,
border: `1px solid ${
isSelectedPathStartOrEnd(filter, pathItemCard)
? 'purple'
: 'var(--border)'
}`,
padding: 4,
justifyContent: 'space-between',
alignItems: 'center',
display: `${pathItemCard.visible ? 'flex' : 'none'}`,
}}
>
<div style={{ display: 'flex', alignItems: 'center' }}>
<span
className="text-muted"
style={{
fontSize: 10,
fontWeight: 600,
marginRight: 4,
lineHeight: '10px',
}}
>{`0${pathItemCard.name[0]}`}</span>{' '}
<span style={{ fontSize: 12, fontWeight: 600 }}>
{pageUrl(pathItemCard, true)}
</span>
</div>
<Row style={{ alignSelf: 'center' }}>
<span
onClick={() => openPersonsModal(undefined, pathItemCard.name)}
className="primary text-small"
style={{ alignSelf: 'center', paddingRight: 4, fontWeight: 500 }}
>
{continuingValue + dropOffValue}
</span>
<Dropdown
trigger={['click']}
overlay={
<Menu className="paths-options-dropdown">
<Menu.Item
onClick={() =>
setFilter({ start_point: pageUrl(pathItemCard) })
}
>
Set as path start
</Menu.Item>
{hasAdvancedPaths && (
<>
<Menu.Item
onClick={() =>
setFilter({ end_point: pageUrl(pathItemCard) })
}
>
Set as path end
</Menu.Item>
<Menu.Item
onClick={() => {
updateExclusions([
...(filter.exclude_events || []),
pageUrl(pathItemCard, false),
])
}}
>
Exclude path item
</Menu.Item>
<Menu.Item
onClick={() => viewPathToFunnel(pathItemCard)}
>
View funnel
</Menu.Item>
</>
)}
<Menu.Item
onClick={() => copyToClipboard(pageUrl(pathItemCard))}
>
Copy path item name
</Menu.Item>
</Menu>
}
>
<div className="paths-dropdown-ellipsis">...</div>
</Dropdown>
</Row>
</Button>
</Dropdown>
</Tooltip>
)
})}
</div>
</div>
)
}
Example #7
Source File: Icon.tsx From html2sketch with MIT License | 4 votes |
IconSymbol: FC = () => {
return (
<Row>
{/*<CaretUpOutlined*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/1.CaretUpOutlined'}*/}
{/*/>*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/2.MailOutlined'}*/}
{/*/>*/}
{/*<StepBackwardOutlined*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
{/*/>*/}
{/*<StepForwardOutlined*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
{/*/>*/}
<StepForwardOutlined />
<ShrinkOutlined />
<ArrowsAltOutlined />
<DownOutlined />
<UpOutlined />
<LeftOutlined />
<RightOutlined />
<CaretUpOutlined />
<CaretDownOutlined />
<CaretLeftOutlined />
<CaretRightOutlined />
<VerticalAlignTopOutlined />
<RollbackOutlined />
<FastBackwardOutlined />
<FastForwardOutlined />
<DoubleRightOutlined />
<DoubleLeftOutlined />
<VerticalLeftOutlined />
<VerticalRightOutlined />
<VerticalAlignMiddleOutlined />
<VerticalAlignBottomOutlined />
<ForwardOutlined />
<BackwardOutlined />
<EnterOutlined />
<RetweetOutlined />
<SwapOutlined />
<SwapLeftOutlined />
<SwapRightOutlined />
<ArrowUpOutlined />
<ArrowDownOutlined />
<ArrowLeftOutlined />
<ArrowRightOutlined />
<LoginOutlined />
<LogoutOutlined />
<MenuFoldOutlined />
<MenuUnfoldOutlined />
<BorderBottomOutlined />
<BorderHorizontalOutlined />
<BorderInnerOutlined />
<BorderOuterOutlined />
<BorderLeftOutlined />
<BorderRightOutlined />
<BorderTopOutlined />
<BorderVerticleOutlined />
<PicCenterOutlined />
<PicLeftOutlined />
<PicRightOutlined />
<RadiusBottomleftOutlined />
<RadiusBottomrightOutlined />
<RadiusUpleftOutlined />
<RadiusUprightOutlined />
<FullscreenOutlined />
<FullscreenExitOutlined />
<QuestionOutlined />
<PauseOutlined />
<MinusOutlined />
<PauseCircleOutlined />
<InfoOutlined />
<CloseOutlined />
<ExclamationOutlined />
<CheckOutlined />
<WarningOutlined />
<IssuesCloseOutlined />
<StopOutlined />
<EditOutlined />
<CopyOutlined />
<ScissorOutlined />
<DeleteOutlined />
<SnippetsOutlined />
<DiffOutlined />
<HighlightOutlined />
<AlignCenterOutlined />
<AlignLeftOutlined />
<AlignRightOutlined />
<BgColorsOutlined />
<BoldOutlined />
<ItalicOutlined />
<UnderlineOutlined />
<StrikethroughOutlined />
<RedoOutlined />
<UndoOutlined />
<ZoomInOutlined />
<ZoomOutOutlined />
<FontColorsOutlined />
<FontSizeOutlined />
<LineHeightOutlined />
<SortAscendingOutlined />
<SortDescendingOutlined />
<DragOutlined />
<OrderedListOutlined />
<UnorderedListOutlined />
<RadiusSettingOutlined />
<ColumnWidthOutlined />
<ColumnHeightOutlined />
<AreaChartOutlined />
<PieChartOutlined />
<BarChartOutlined />
<DotChartOutlined />
<LineChartOutlined />
<RadarChartOutlined />
<HeatMapOutlined />
<FallOutlined />
<RiseOutlined />
<StockOutlined />
<BoxPlotOutlined />
<FundOutlined />
<SlidersOutlined />
<AndroidOutlined />
<AppleOutlined />
<WindowsOutlined />
<IeOutlined />
<ChromeOutlined />
<GithubOutlined />
<AliwangwangOutlined />
<DingdingOutlined />
<WeiboSquareOutlined />
<WeiboCircleOutlined />
<TaobaoCircleOutlined />
<Html5Outlined />
<WeiboOutlined />
<TwitterOutlined />
<WechatOutlined />
<AlipayCircleOutlined />
<TaobaoOutlined />
<SkypeOutlined />
<FacebookOutlined />
<CodepenOutlined />
<CodeSandboxOutlined />
<AmazonOutlined />
<GoogleOutlined />
<AlipayOutlined />
<AntDesignOutlined />
<AntCloudOutlined />
<ZhihuOutlined />
<SlackOutlined />
<SlackSquareOutlined />
<BehanceSquareOutlined />
<DribbbleOutlined />
<DribbbleSquareOutlined />
<InstagramOutlined />
<YuqueOutlined />
<AlibabaOutlined />
<YahooOutlined />
<RedditOutlined />
<SketchOutlined />
<AccountBookOutlined />
<AlertOutlined />
<ApartmentOutlined />
<ApiOutlined />
<QqOutlined />
<MediumWorkmarkOutlined />
<GitlabOutlined />
<MediumOutlined />
<GooglePlusOutlined />
<AppstoreAddOutlined />
<AppstoreOutlined />
<AudioOutlined />
<AudioMutedOutlined />
<AuditOutlined />
<BankOutlined />
<BarcodeOutlined />
<BarsOutlined />
<BellOutlined />
<BlockOutlined />
<BookOutlined />
<BorderOutlined />
<BranchesOutlined />
<BuildOutlined />
<BulbOutlined />
<CalculatorOutlined />
<CalendarOutlined />
<CameraOutlined />
<CarOutlined />
<CarryOutOutlined />
<CiCircleOutlined />
<CiOutlined />
<CloudOutlined />
<ClearOutlined />
<ClusterOutlined />
<CodeOutlined />
<CoffeeOutlined />
<CompassOutlined />
<CompressOutlined />
<ContactsOutlined />
<ContainerOutlined />
<ControlOutlined />
<CopyrightCircleOutlined />
<CopyrightOutlined />
<CreditCardOutlined />
<CrownOutlined />
<CustomerServiceOutlined />
<DashboardOutlined />
<DatabaseOutlined />
<DeleteColumnOutlined />
<DeleteRowOutlined />
<DisconnectOutlined />
<DislikeOutlined />
<DollarCircleOutlined />
<DollarOutlined />
<DownloadOutlined />
<EllipsisOutlined />
<EnvironmentOutlined />
<EuroCircleOutlined />
<EuroOutlined />
<ExceptionOutlined />
<ExpandAltOutlined />
<ExpandOutlined />
<ExperimentOutlined />
<ExportOutlined />
<EyeOutlined />
<FieldBinaryOutlined />
<FieldNumberOutlined />
<FieldStringOutlined />
<DesktopOutlined />
<DingtalkOutlined />
<FileAddOutlined />
<FileDoneOutlined />
<FileExcelOutlined />
<FileExclamationOutlined />
<FileOutlined />
<FileImageOutlined />
<FileJpgOutlined />
<FileMarkdownOutlined />
<FilePdfOutlined />
<FilePptOutlined />
<FileProtectOutlined />
<FileSearchOutlined />
<FileSyncOutlined />
<FileTextOutlined />
<FileUnknownOutlined />
<FileWordOutlined />
<FilterOutlined />
<FireOutlined />
<FlagOutlined />
<FolderAddOutlined />
<FolderOutlined />
<FolderOpenOutlined />
<ForkOutlined />
<FormatPainterOutlined />
<FrownOutlined />
<FunctionOutlined />
<FunnelPlotOutlined />
<GatewayOutlined />
<GifOutlined />
<GiftOutlined />
<GlobalOutlined />
<GoldOutlined />
<GroupOutlined />
<HddOutlined />
<HeartOutlined />
<HistoryOutlined />
<HomeOutlined />
<HourglassOutlined />
<IdcardOutlined />
<ImportOutlined />
<InboxOutlined />
<InsertRowAboveOutlined />
<InsertRowBelowOutlined />
<InsertRowLeftOutlined />
<InsertRowRightOutlined />
<InsuranceOutlined />
<InteractionOutlined />
<KeyOutlined />
<LaptopOutlined />
<LayoutOutlined />
<LikeOutlined />
<LineOutlined />
<LinkOutlined />
<Loading3QuartersOutlined />
<LoadingOutlined />
<LockOutlined />
<MailOutlined />
<ManOutlined />
<MedicineBoxOutlined />
<MehOutlined />
<MenuOutlined />
<MergeCellsOutlined />
<MessageOutlined />
<MobileOutlined />
<MoneyCollectOutlined />
<MonitorOutlined />
<MoreOutlined />
<NodeCollapseOutlined />
<NodeExpandOutlined />
<NodeIndexOutlined />
<NotificationOutlined />
<NumberOutlined />
<PaperClipOutlined />
<PartitionOutlined />
<PayCircleOutlined />
<PercentageOutlined />
<PhoneOutlined />
<PictureOutlined />
<PoundCircleOutlined />
<PoundOutlined />
<PoweroffOutlined />
<PrinterOutlined />
<ProfileOutlined />
<ProjectOutlined />
<PropertySafetyOutlined />
<PullRequestOutlined />
<PushpinOutlined />
<QrcodeOutlined />
<ReadOutlined />
<ReconciliationOutlined />
<RedEnvelopeOutlined />
<ReloadOutlined />
<RestOutlined />
<RobotOutlined />
<RocketOutlined />
<SafetyCertificateOutlined />
<SafetyOutlined />
<ScanOutlined />
<ScheduleOutlined />
<SearchOutlined />
<SecurityScanOutlined />
<SelectOutlined />
<SendOutlined />
<SettingOutlined />
<ShakeOutlined />
<ShareAltOutlined />
<ShopOutlined />
<ShoppingCartOutlined />
<ShoppingOutlined />
<SisternodeOutlined />
<SkinOutlined />
<SmileOutlined />
<SolutionOutlined />
<SoundOutlined />
<SplitCellsOutlined />
<StarOutlined />
<SubnodeOutlined />
<SyncOutlined />
<TableOutlined />
<TabletOutlined />
<TagOutlined />
<TagsOutlined />
<TeamOutlined />
<ThunderboltOutlined />
<ToTopOutlined />
<ToolOutlined />
<TrademarkCircleOutlined />
<TrademarkOutlined />
<TransactionOutlined />
<TrophyOutlined />
<UngroupOutlined />
<UnlockOutlined />
<UploadOutlined />
<UsbOutlined />
<UserAddOutlined />
<UserDeleteOutlined />
<UserOutlined />
<UserSwitchOutlined />
<UsergroupAddOutlined />
<UsergroupDeleteOutlined />
<VideoCameraOutlined />
<WalletOutlined />
<WifiOutlined />
<BorderlessTableOutlined />
<WomanOutlined />
<BehanceOutlined />
<DropboxOutlined />
<DeploymentUnitOutlined />
<UpCircleOutlined />
<DownCircleOutlined />
<LeftCircleOutlined />
<RightCircleOutlined />
<UpSquareOutlined />
<DownSquareOutlined />
<LeftSquareOutlined />
<RightSquareOutlined />
<PlayCircleOutlined />
<QuestionCircleOutlined />
<PlusCircleOutlined />
<PlusSquareOutlined />
<MinusSquareOutlined />
<MinusCircleOutlined />
<InfoCircleOutlined />
<ExclamationCircleOutlined />
<CloseCircleOutlined />
<CloseSquareOutlined />
<CheckCircleOutlined />
<CheckSquareOutlined />
<ClockCircleOutlined />
<FormOutlined />
<DashOutlined />
<SmallDashOutlined />
<YoutubeOutlined />
<CodepenCircleOutlined />
<AliyunOutlined />
<PlusOutlined />
<LinkedinOutlined />
<AimOutlined />
<BugOutlined />
<CloudDownloadOutlined />
<CloudServerOutlined />
<CloudSyncOutlined />
<CloudUploadOutlined />
<CommentOutlined />
<ConsoleSqlOutlined />
<EyeInvisibleOutlined />
<FileGifOutlined />
<DeliveredProcedureOutlined />
<FieldTimeOutlined />
<FileZipOutlined />
<FolderViewOutlined />
<FundProjectionScreenOutlined />
<FundViewOutlined />
<MacCommandOutlined />
<PlaySquareOutlined />
<OneToOneOutlined />
<RotateLeftOutlined />
<RotateRightOutlined />
<SaveOutlined />
<SwitcherOutlined />
<TranslationOutlined />
<VerifiedOutlined />
<VideoCameraAddOutlined />
<WhatsAppOutlined />
{/*</Col>*/}
</Row>
);
}
Example #8
Source File: index.tsx From ql with MIT License | 4 votes |
Crontab = () => {
const columns = [
{
title: '任务名',
dataIndex: 'name',
key: 'name',
align: 'center' as const,
render: (text: string, record: any) => (
<span>{record.name || record._id}</span>
),
},
{
title: '任务',
dataIndex: 'command',
key: 'command',
width: '40%',
align: 'center' as const,
render: (text: string, record: any) => {
return (
<span
style={{
textAlign: 'left',
width: '100%',
display: 'inline-block',
wordBreak: 'break-all',
}}
>
{text}
</span>
);
},
},
{
title: '任务定时',
dataIndex: 'schedule',
key: 'schedule',
align: 'center' as const,
},
{
title: '状态',
key: 'status',
dataIndex: 'status',
align: 'center' as const,
render: (text: string, record: any) => (
<>
{(!record.isDisabled || record.status !== CrontabStatus.idle) && (
<>
{record.status === CrontabStatus.idle && (
<Tag icon={<ClockCircleOutlined />} color="default">
空闲中
</Tag>
)}
{record.status === CrontabStatus.running && (
<Tag
icon={<Loading3QuartersOutlined spin />}
color="processing"
>
运行中
</Tag>
)}
{record.status === CrontabStatus.queued && (
<Tag icon={<FieldTimeOutlined />} color="default">
队列中
</Tag>
)}
</>
)}
{record.isDisabled === 1 && record.status === CrontabStatus.idle && (
<Tag icon={<CloseCircleOutlined />} color="error">
已禁用
</Tag>
)}
</>
),
},
{
title: '操作',
key: 'action',
align: 'center' as const,
render: (text: string, record: any, index: number) => (
<Space size="middle">
{record.status === CrontabStatus.idle && (
<Tooltip title="运行">
<a
onClick={() => {
runCron(record, index);
}}
>
<PlayCircleOutlined />
</a>
</Tooltip>
)}
{record.status !== CrontabStatus.idle && (
<Tooltip title="停止">
<a
onClick={() => {
stopCron(record, index);
}}
>
<PauseCircleOutlined />
</a>
</Tooltip>
)}
<Tooltip title="日志">
<a
onClick={() => {
setLogCron({ ...record, timestamp: Date.now() });
}}
>
<FileTextOutlined />
</a>
</Tooltip>
<MoreBtn key="more" record={record} index={index} />
</Space>
),
},
];
const [width, setWidth] = useState('100%');
const [marginLeft, setMarginLeft] = useState(0);
const [marginTop, setMarginTop] = useState(-72);
const [value, setValue] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [isModalVisible, setIsModalVisible] = useState(false);
const [editedCron, setEditedCron] = useState();
const [searchText, setSearchText] = useState('');
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
const [logCron, setLogCron] = useState<any>();
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(20);
const getCrons = () => {
setLoading(true);
request
.get(`${config.apiPrefix}crons?searchValue=${searchText}`)
.then((data: any) => {
setValue(
data.data.sort((a: any, b: any) => {
const sortA = a.isDisabled ? 4 : a.status;
const sortB = b.isDisabled ? 4 : b.status;
return CrontabSort[sortA] - CrontabSort[sortB];
}),
);
})
.finally(() => setLoading(false));
};
const addCron = () => {
setEditedCron(null as any);
setIsModalVisible(true);
};
const editCron = (record: any, index: number) => {
setEditedCron(record);
setIsModalVisible(true);
};
const delCron = (record: any, index: number) => {
Modal.confirm({
title: '确认删除',
content: (
<>
确认删除定时任务{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{record.name}
</Text>{' '}
吗
</>
),
onOk() {
request
.delete(`${config.apiPrefix}crons`, { data: [record._id] })
.then((data: any) => {
if (data.code === 200) {
message.success('删除成功');
const result = [...value];
result.splice(index + pageSize * (currentPage - 1), 1);
setValue(result);
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const runCron = (record: any, index: number) => {
Modal.confirm({
title: '确认运行',
content: (
<>
确认运行定时任务{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{record.name}
</Text>{' '}
吗
</>
),
onOk() {
request
.put(`${config.apiPrefix}crons/run`, { data: [record._id] })
.then((data: any) => {
if (data.code === 200) {
const result = [...value];
result.splice(index + pageSize * (currentPage - 1), 1, {
...record,
status: CrontabStatus.running,
});
setValue(result);
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const stopCron = (record: any, index: number) => {
Modal.confirm({
title: '确认停止',
content: (
<>
确认停止定时任务{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{record.name}
</Text>{' '}
吗
</>
),
onOk() {
request
.put(`${config.apiPrefix}crons/stop`, { data: [record._id] })
.then((data: any) => {
if (data.code === 200) {
const result = [...value];
result.splice(index + pageSize * (currentPage - 1), 1, {
...record,
pid: null,
status: CrontabStatus.idle,
});
setValue(result);
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const enabledOrDisabledCron = (record: any, index: number) => {
Modal.confirm({
title: `确认${record.isDisabled === 1 ? '启用' : '禁用'}`,
content: (
<>
确认{record.isDisabled === 1 ? '启用' : '禁用'}
定时任务{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{record.name}
</Text>{' '}
吗
</>
),
onOk() {
request
.put(
`${config.apiPrefix}crons/${
record.isDisabled === 1 ? 'enable' : 'disable'
}`,
{
data: [record._id],
},
)
.then((data: any) => {
if (data.code === 200) {
const newStatus = record.isDisabled === 1 ? 0 : 1;
const result = [...value];
result.splice(index + pageSize * (currentPage - 1), 1, {
...record,
isDisabled: newStatus,
});
setValue(result);
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const MoreBtn: React.FC<{
record: any;
index: number;
}> = ({ record, index }) => (
<Dropdown
arrow
trigger={['click']}
overlay={
<Menu onClick={({ key }) => action(key, record, index)}>
<Menu.Item key="edit" icon={<EditOutlined />}>
编辑
</Menu.Item>
<Menu.Item
key="enableordisable"
icon={
record.isDisabled === 1 ? (
<CheckCircleOutlined />
) : (
<StopOutlined />
)
}
>
{record.isDisabled === 1 ? '启用' : '禁用'}
</Menu.Item>
{record.isSystem !== 1 && (
<Menu.Item key="delete" icon={<DeleteOutlined />}>
删除
</Menu.Item>
)}
</Menu>
}
>
<a>
<EllipsisOutlined />
</a>
</Dropdown>
);
const action = (key: string | number, record: any, index: number) => {
switch (key) {
case 'edit':
editCron(record, index);
break;
case 'enableordisable':
enabledOrDisabledCron(record, index);
break;
case 'delete':
delCron(record, index);
break;
default:
break;
}
};
const handleCancel = (cron?: any) => {
setIsModalVisible(false);
if (cron) {
handleCrons(cron);
}
};
const onSearch = (value: string) => {
setSearchText(value);
};
const handleCrons = (cron: any) => {
const index = value.findIndex((x) => x._id === cron._id);
const result = [...value];
if (index === -1) {
result.push(cron);
} else {
result.splice(index, 1, {
...cron,
});
}
setValue(result);
};
const getCronDetail = (cron: any) => {
request
.get(`${config.apiPrefix}crons/${cron._id}`)
.then((data: any) => {
console.log(value);
const index = value.findIndex((x) => x._id === cron._id);
console.log(index);
const result = [...value];
result.splice(index, 1, {
...cron,
...data.data,
});
setValue(result);
})
.finally(() => setLoading(false));
};
const onSelectChange = (selectedIds: any[]) => {
setSelectedRowIds(selectedIds);
};
const rowSelection = {
selectedRowIds,
onChange: onSelectChange,
selections: [
Table.SELECTION_ALL,
Table.SELECTION_INVERT,
Table.SELECTION_NONE,
],
};
const delCrons = () => {
Modal.confirm({
title: '确认删除',
content: <>确认删除选中的定时任务吗</>,
onOk() {
request
.delete(`${config.apiPrefix}crons`, { data: selectedRowIds })
.then((data: any) => {
if (data.code === 200) {
message.success('批量删除成功');
setSelectedRowIds([]);
getCrons();
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const operateCrons = (operationStatus: number) => {
Modal.confirm({
title: `确认${OperationName[operationStatus]}`,
content: <>确认{OperationName[operationStatus]}选中的定时任务吗</>,
onOk() {
request
.put(`${config.apiPrefix}crons/${OperationPath[operationStatus]}`, {
data: selectedRowIds,
})
.then((data: any) => {
if (data.code === 200) {
getCrons();
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const onPageChange = (page: number, pageSize: number | undefined) => {
setCurrentPage(page);
setPageSize(pageSize as number);
localStorage.setItem('pageSize', pageSize + '');
};
useEffect(() => {
if (logCron) {
localStorage.setItem('logCron', logCron._id);
setIsLogModalVisible(true);
}
}, [logCron]);
useEffect(() => {
getCrons();
}, [searchText]);
useEffect(() => {
if (document.body.clientWidth < 768) {
setWidth('auto');
setMarginLeft(0);
setMarginTop(0);
} else {
setWidth('100%');
setMarginLeft(0);
setMarginTop(-72);
}
setPageSize(parseInt(localStorage.getItem('pageSize') || '20'));
}, []);
return (
<PageContainer
className="ql-container-wrapper crontab-wrapper"
title="定时任务"
extra={[
<Search
placeholder="请输入名称或者关键词"
style={{ width: 'auto' }}
enterButton
loading={loading}
onSearch={onSearch}
/>,
<Button key="2" type="primary" onClick={() => addCron()}>
添加定时
</Button>,
]}
header={{
style: {
padding: '4px 16px 4px 15px',
position: 'sticky',
top: 0,
left: 0,
zIndex: 20,
marginTop,
width,
marginLeft,
},
}}
>
{selectedRowIds.length > 0 && (
<div style={{ marginBottom: 16 }}>
<Button type="primary" style={{ marginBottom: 5 }} onClick={delCrons}>
批量删除
</Button>
<Button
type="primary"
onClick={() => operateCrons(0)}
style={{ marginLeft: 8, marginBottom: 5 }}
>
批量启用
</Button>
<Button
type="primary"
onClick={() => operateCrons(1)}
style={{ marginLeft: 8, marginRight: 8 }}
>
批量禁用
</Button>
<Button
type="primary"
style={{ marginRight: 8 }}
onClick={() => operateCrons(2)}
>
批量运行
</Button>
<Button type="primary" onClick={() => operateCrons(3)}>
批量停止
</Button>
<span style={{ marginLeft: 8 }}>
已选择
<a>{selectedRowIds?.length}</a>项
</span>
</div>
)}
<Table
columns={columns}
pagination={{
hideOnSinglePage: true,
current: currentPage,
onChange: onPageChange,
pageSize: pageSize,
showSizeChanger: true,
defaultPageSize: 20,
showTotal: (total: number, range: number[]) =>
`第 ${range[0]}-${range[1]} 条/总共 ${total} 条`,
}}
dataSource={value}
rowKey="_id"
size="middle"
scroll={{ x: 768 }}
loading={loading}
rowSelection={rowSelection}
/>
<CronLogModal
visible={isLogModalVisible}
handleCancel={() => {
getCronDetail(logCron);
setIsLogModalVisible(false);
}}
cron={logCron}
/>
<CronModal
visible={isModalVisible}
handleCancel={handleCancel}
cron={editedCron}
/>
</PageContainer>
);
}
Example #9
Source File: GeoDataResolver.tsx From jitsu with MIT License | 4 votes |
function GeoDataResolver() {
const services = useServices()
const [saving, setSaving] = useState(false)
const [testingConnection, setTestingConnection] = useState(false)
const [formDisabled, setFormDisabled] = useState(false)
const [form] = useForm<GeoDataResolverFormValues>()
const {
error: loadingError,
data: formConfig,
setData: setFormConfig,
} = useLoaderAsObject<MaxMindConfig>(async () => {
const response = await services.backendApiClient.get(
`/configurations/${geoDataResolversCollection}?id=${services.activeProject.id}`
)
let config = {
license_key: response.maxmind?.license_key,
enabled: response.maxmind?.enabled,
editions: [],
}
//set statuses or load
if (response.maxmind?.enabled && response.maxmind?._statuses) {
config.editions = response.maxmind._statuses
} else {
const response = await ApplicationServices.get().backendApiClient.get(
withQueryParams("/geo_data_resolvers/editions", { project_id: services.activeProject.id }),
{ proxy: true }
)
config.editions = response.editions
}
form.setFieldsValue({
license_key: config.license_key,
enabled: config.enabled,
})
setFormDisabled(!config.enabled)
return config
}, [])
const submit = async () => {
setSaving(true)
let formValues = form.getFieldsValue()
try {
if (formValues.enabled) {
await testConnection(true)
}
await save()
} catch (error) {
actionNotification.error(error.message || error)
} finally {
setSaving(false)
}
}
const save = async () => {
let formValues = form.getFieldsValue()
let config = {
maxmind: {
enabled: formValues.enabled,
license_key: formValues.license_key,
_statuses: formValues.enabled ? formConfig.editions : null,
},
}
await services.backendApiClient.post(
`/configurations/${geoDataResolversCollection}?id=${services.activeProject.id}`,
Marshal.toPureJson(config)
)
let anyConnected =
formConfig.editions.filter(editionStatus => {
return editionStatus.main.status === "ok" || editionStatus.analog?.status === "ok"
}).length > 0
if (!formValues.enabled || anyConnected) {
actionNotification.success("Settings saved!")
}
if (formValues.enabled && !anyConnected) {
actionNotification.warn(
`Settings have been saved, but there is no available MaxMind database for this license key. Geo Resolution won't be applied to your JSON events`
)
}
}
const testConnection = async (hideMessage?: boolean) => {
setTestingConnection(true)
let formValues = form.getFieldsValue()
try {
const response = await ApplicationServices.get().backendApiClient.post(
withQueryParams("/geo_data_resolvers/test", { project_id: services.activeProject.id }),
{ maxmind_url: formValues.license_key },
{
proxy: true,
}
)
if (response.message) throw new Error(response.message)
//enrich state
let currentFormConfig = formConfig
currentFormConfig.editions = response.editions
setFormConfig(currentFormConfig)
//show notification
if (!hideMessage) {
let anyConnected =
formConfig.editions.filter(editionStatus => {
return editionStatus.main.status === "ok" || editionStatus.analog?.status === "ok"
}).length > 0
if (anyConnected) {
actionNotification.success("Successfully connected!")
} else {
actionNotification.error("Connection failed: there is no available MaxMind database for this license key")
}
}
} catch (error) {
if (!hideMessage) {
handleError(error, "Connection failed")
}
} finally {
setTestingConnection(false)
}
}
const databaseStatusesRepresentation = (dbStatus: any) => {
let body = <>-</>
if (dbStatus) {
let icon = (
<Tooltip title="Not connected yet">
<ClockCircleOutlined className="text-secondaryText" />
</Tooltip>
)
if (dbStatus.status === "ok") {
icon = (
<Tooltip title="Successfully connected">
<CheckCircleOutlined className="text-success" />
</Tooltip>
)
} else if (dbStatus.status === "error") {
icon = (
<Tooltip title={dbStatus.message}>
<CloseCircleOutlined className="text-error" />
</Tooltip>
)
}
body = (
<>
{dbStatus.name}: {icon}
</>
)
}
return body
}
if (loadingError) {
return <CenteredError error={loadingError} />
} else if (!formConfig) {
return <CenteredSpin />
}
return (
<div className="flex justify-center w-full">
<div className="w-full pt-8 px-4" style={{ maxWidth: "1000px" }}>
<p>
Jitsu uses <a href="https://www.maxmind.com/">MaxMind</a> databases for geo resolution. There are two families
of MaxMind databases: <b>GeoIP2</b> and <b>GeoLite2</b>. After setting a license key{" "}
<b>all available MaxMind databases, which the license key has access</b>, will be downloaded and used for
enriching incoming events. For using a certain database add{" "}
<CodeInline>{"?edition_id=<database type>"}</CodeInline> to MaxMind License Key value. For example:{" "}
<CodeInline>{"M10sDzWKmnDYUBM0?edition_id=GeoIP2-City,GeoIP2-ISP"}</CodeInline>.
</p>
<div className="w-96 flex-wrap flex justify-content-center">
<Table
pagination={false}
columns={[
{
title: (
<>
Database{" "}
<Tooltip title="Paid MaxMind Database">
<QuestionCircleOutlined className="label-with-tooltip_question-mark" />
</Tooltip>
</>
),
dataIndex: "main",
key: "name",
render: databaseStatusesRepresentation,
},
{
title: (
<>
Analog{" "}
<Tooltip title="Free MaxMind Database analog. Usually it is less accurate than paid version. It is downloaded only if paid one is unavailable.">
<QuestionCircleOutlined className="label-with-tooltip_question-mark" />
</Tooltip>
</>
),
dataIndex: "analog",
key: "name",
render: databaseStatusesRepresentation,
},
]}
dataSource={formConfig.editions}
/>
</div>
<br />
<Form form={form} onFinish={submit}>
<FormLayout>
<FormField
label="Enabled"
tooltip={
<>
If enabled - Jitsu downloads <a href="https://www.maxmind.com/en/geoip2-databases">GeoIP Databases</a>{" "}
with your license key and enriches incoming JSON events with location based data. Read more
information about{" "}
<a href="https://jitsu.com/docs/other-features/geo-data-resolution">Geo data resolution</a>.
</>
}
key="enabled"
>
<Form.Item name="enabled" valuePropName="checked">
<Switch
onChange={value => {
setFormDisabled(!value)
}}
size="default"
/>
</Form.Item>
</FormField>
<FormField
label="MaxMind License Key"
tooltip={
<>
Your MaxMind licence key. Obtain a new one in your <a href="https://www.maxmind.com/">Account</a>{" "}
{"->"} Manage License Keys. Jitsu downloads all available MaxMind databases with your license key. If
you would like to enrich events JSON with the only certain MaxMind DB data{": "}
specify license key with the format:{" "}
{"<license_key>?edition_id=<comma separated editions like: GeoIP2-City,GeoIP2-ISP>"}. If you use{" "}
<a href="https://cloud.jitsu.com/">Jitsu.Cloud</a> and MaxMind isn't set - free GeoLite2-City and
GeoLite2-ASN MaxMind databases are applied. Read more about{" "}
<a href="https://dev.maxmind.com/geoip/geolite2-free-geolocation-data?lang=en">
free MaxMind databases
</a>
.{" "}
</>
}
key="license_key"
>
<Form.Item name="license_key">
<Input
disabled={formDisabled}
size="large"
name="license_key"
placeholder="for example: M10sDzWKmnDYUBM0"
required={true}
/>
</Form.Item>
</FormField>
<FormActions>
<Button
size="large"
className="mr-3"
type="dashed"
loading={testingConnection}
onClick={() => testConnection()}
icon={<ApiOutlined />}
disabled={formDisabled}
>
Test connection
</Button>
<Button loading={saving} htmlType="submit" size="large" type="primary">
Save
</Button>
</FormActions>
</FormLayout>
</Form>
</div>
</div>
)
}
Example #10
Source File: detail.tsx From dashboard with Apache License 2.0 | 4 votes |
CustomerDetail: React.FC = () => {
const queryFormRef = useRef<FormInstance>();
const actionRef = useRef<ActionType>();
const [customerDetail, setCustomerDetail] = useState<CustomerItem>()
const [staffMap, setStaffMap] = useState<Dictionary<StaffOption>>({});
const [allCustomerTagGroups, setAllCustomerTagGroups] = useState<CustomerTagGroupItem[]>([]);
const [defaultCustomerTags, setDefaultCustomerTags] = useState<CustomerTag[]>([]);
const [defaultInternalTagsIds, setDefaultInternalTagsIds] = useState<string []>([])
const [personalTagModalVisible, setPersonalTagModalVisible] = useState(false)
const [customerTagModalVisible, setCustomerTagModalVisible] = useState(false)
const [internalTagList, setInternalTagList] = useState<InternalTags.Item[]>([])
const [internalTagListMap, setInternalTagListMap] = useState<Dictionary<InternalTags.Item>>({});
const [initialEvents, setInitialEvents] = useState<CustomerEvents.Item[]>([])
const [currentTab, setCurrentTab] = useState('survey')
const [basicInfoDisplay, setBasicInfoDisplay] = useState({} as any)// 展示哪些基本信息
const [basicInfoValues, setBasicInfoValues] = useState({} as any) // 基本信息取值
const [remarkValues, setRemarkValues] = useState<Remark[]>([])
const [reloadCusDataTimesTamp, setReloadCusDataTimesTamp] = useState(Date.now)
const [formRef] = Form.useForm()
const params = new URLSearchParams(window.location.search);
const currentStaff = localStorage.getItem('extStaffAdminID') as string
const extCustomerID = params.get('ext_customer_id') || "";
if (!extCustomerID) {
message.error('传入参数请带上ID');
}
const extStaff = () => {
const staffs: StaffItem[] = [];
customerDetail?.staff_relations?.forEach((staff_relation) => {
// @ts-ignore
const staff = staffMap[staff_relation.ext_staff_id];
if (staff) {
staffs.push(staff);
}
});
return staffs;
}
const getCustomerDetail = () => {
const hide = message.loading("加载数据中");
GetCustomerDetail(extCustomerID).then(res => {
hide();
if (res?.code !== 0) {
message.error("获取客户详情失败");
return;
}
setCustomerDetail(res?.data);
const cusTags: any[] = [];
const interTagsIds: any[] = []
res?.data?.staff_relations?.forEach((relation: any) => {
if (relation.ext_staff_id === currentStaff) {
relation.customer_staff_tags?.forEach((tag: any) => {
cusTags.push({...tag, name: tag.tag_name, ext_id: tag.ext_tag_id});
});
relation.internal_tags?.forEach((tagId: string) => {
interTagsIds.push(tagId);
})
}
});
setDefaultCustomerTags(cusTags)
setDefaultInternalTagsIds(interTagsIds)
}).catch(() => {
hide();
})
}
const getInternalTags = () => {
QueryInternalTags({page_size: 5000, ext_staff_id: currentStaff}).then(res => {
if (res?.code === 0) {
setInternalTagList(res?.data?.items)
setInternalTagListMap(_.keyBy(res?.data?.items, 'id'))
} else {
message.error(res?.message)
}
})
}
const getCustomerRemark = () => { // 自定义信息id-key
QueryCustomerRemark().then(res => {
if (res?.code === 0) {
console.log('QueryCustomerRemark', res.data)
} else {
message.error(res?.message)
}
})
}
const getBasicInfoDisplay = () => {
GetCustomerBasicInfoDisplay().then(res => {
if (res?.code === 0) {
const displayData = res?.data
delete displayData.id
delete displayData.ext_corp_id
delete displayData.created_at
delete displayData.updated_at
delete displayData.deleted_at
setBasicInfoDisplay(displayData || {})
} else {
message.error(res?.message)
}
})
}
const getBasicInfoAndRemarkValues = () => {
GetBasicInfoAndRemarkValues({
ext_customer_id: extCustomerID,
ext_staff_id: currentStaff,
}).then(res => {
if (res?.code === 0) {
const resData = res?.data
delete resData.id
delete resData.ext_corp_id
delete resData.ext_creator_id
delete resData.ext_customer_id
delete resData.ext_staff_id
delete resData.created_at
delete resData.updated_at
delete resData.deleted_at
delete resData.remark_values
setBasicInfoValues(resData)
setRemarkValues(res?.data?.remark_values || [])
}
})
}
const updateBasicInfoAndRemark = (basicInfoParams: any) => {
UpdateBasicInfoAndRemark({
ext_staff_id: currentStaff,
ext_customer_id: extCustomerID,
...basicInfoParams
}).then(res => {
if (res?.code === 0) {
message.success('客户信息更新成功')
setReloadCusDataTimesTamp(Date.now)
} else {
message.error('客户信息更新失败')
}
})
}
useEffect(() => {
getInternalTags()
getCustomerDetail()
getCustomerRemark()
getBasicInfoDisplay()
getBasicInfoAndRemarkValues()
}, [reloadCusDataTimesTamp])
useEffect(() => {
QueryCustomerTagGroups({page_size: 5000}).then((res) => {
if (res.code === 0) {
setAllCustomerTagGroups(res?.data?.items);
} else {
message.error(res.message);
}
});
}, []);
useEffect(() => {
QuerySimpleStaffs({page_size: 5000}).then((res) => {
if (res.code === 0) {
const staffs = res?.data?.items?.map((item: SimpleStaffInterface) => {
return {
label: item.name,
value: item.ext_id,
...item,
};
}) || [];
setStaffMap(_.keyBy<StaffOption>(staffs, 'ext_id'));
} else {
message.error(res.message);
}
});
}, []);
useEffect(() => {
QueryCustomerEvents({
ext_customer_id: extCustomerID,
ext_staff_id: currentStaff,
page_size: 5
}).then(res => {
console.log('QueryCustomerEventsQueryCustomerEvents', res)
setInitialEvents(res?.data?.items || [])
})
}, [])
useEffect(() => {
formRef.setFieldsValue(basicInfoValues)
}, [basicInfoValues])
return (
<PageContainer
fixedHeader
onBack={() => history.go(-1)}
backIcon={<LeftOutlined/>}
header={{
title: '客户详情',
}}
>
<ProCard>
<Descriptions title="客户信息" column={1}>
<Descriptions.Item>
<div className={'customer-info-field'}>
<div><img src={customerDetail?.avatar} alt={customerDetail?.name} style={{borderRadius: 5}}/></div>
<div style={{fontSize: 16, marginLeft: 10}}>
<p>{customerDetail?.name}</p>
{customerDetail?.corp_name && (
<p style={{color: '#eda150', marginTop: 10}}>@{customerDetail?.corp_name}</p>
)}
{customerDetail?.type === 1 && (
<p style={{
color: '#5ec75d',
fontSize: '13px'
}}>@微信</p>
)}
</div>
</div>
</Descriptions.Item>
<div>
<div style={{width: 70, display: 'inline-block'}}>企业标签:</div>
<div className={styles.tagContainer}>
<Space direction={'horizontal'} wrap={true}>
{
defaultCustomerTags?.length > 0 && defaultCustomerTags?.map((tag) =>
<Tag
key={tag?.id}
className={'tag-item selected-tag-item'}
>
{tag?.name}
</Tag>
)}
</Space>
</div>
<Button
key='addCusTags'
icon={<EditOutlined/>}
type={'link'}
onClick={() => {
setCustomerTagModalVisible(true);
}}
>
编辑
</Button>
</div>
<div>
<div style={{width: 70, display: 'inline-block'}}>个人标签:</div>
<div className={styles.tagContainer}>
<Space direction={'horizontal'} wrap={true}>
{
defaultInternalTagsIds?.length > 0 && defaultInternalTagsIds.map(id => internalTagListMap[id])?.map((tag) =>
<Tag
key={tag?.id}
className={'tag-item selected-tag-item'}
>
{tag?.name}
<span>
</span>
</Tag>
)}
</Space>
</div>
<Button
key='addInternalTags'
icon={<EditOutlined/>}
type={'link'}
onClick={() => {
setPersonalTagModalVisible(true);
}}
>
编辑
</Button>
</div>
</Descriptions>
</ProCard>
<ProCard
tabs={{
onChange: (activeKey: string) => setCurrentTab(activeKey),
activeKey: currentTab
}}
style={{marginTop: 25}}
>
<ProCard.TabPane key="survey" tab="客户概况">
<div className={styles.survey}>
<div className={styles.cusSurveyLeft}>
<div>
<Descriptions title={<div><ContactsFilled/> 添加客服信息</div>} layout="vertical" bordered
column={4}>
<Descriptions.Item label="所属员工">
<CollapsedStaffs limit={2} staffs={extStaff()}/>
</Descriptions.Item>
<Descriptions.Item label="客户来源">
<span>
{customerDetail?.staff_relations?.map((para) => {
return (`${addWayEnums[para.add_way || 0]}\n`);
})}
</span>
</Descriptions.Item>
<Descriptions.Item label="添加时间">
<Space>
{customerDetail?.staff_relations?.map((para) => {
return (
<div className={styles.staffTag}
dangerouslySetInnerHTML={{
__html: moment(para.createtime)
.format('YYYY-MM-DD HH:mm')
.split(' ')
.join('<br />'),
}}
/>
);
})}
</Space>
</Descriptions.Item>
<Descriptions.Item label="更新时间">
<div
dangerouslySetInnerHTML={{
__html: moment(customerDetail?.updated_at)
.format('YYYY-MM-DD HH:mm')
.split(' ')
.join('<br />'),
}}
/>
</Descriptions.Item>
</Descriptions>
</div>
<Form form={formRef} onFinish={(values) => {
console.log('ooooooooooooooovaluesvalues', values)
const basicInfoParams = {...values}
updateBasicInfoAndRemark(basicInfoParams)
}}>
<div style={{paddingTop: 20}} className={styles.baseInfoContainer}>
<Descriptions
title={<div><BookFilled/> 基本信息</div>}
bordered
column={2}
size={'small'}
>
{
Object.keys(basicInfoDisplay).map(key => {
return <Descriptions.Item label={basicInfo[key]}>
<TableInput name={key} />
</Descriptions.Item>
})
}
</Descriptions>
</div>
{
remarkValues.length > 0 && <div style={{paddingTop: 20}} className={styles.customInfoContainer}>
<Descriptions
title={<div><EditFilled/> 自定义信息</div>}
bordered
column={2}
size={'small'}
>
<Descriptions.Item label="sfdsf">
<TableInput name={'aa'}/>
</Descriptions.Item>
<Descriptions.Item label="违法的">
<TableInput name={'bb'}/>
</Descriptions.Item>
<Descriptions.Item label="sdf434">
<TableInput name={'cc'}/>
</Descriptions.Item>
<Descriptions.Item label="yjkyujy">
<TableInput name={'dd'}/>
</Descriptions.Item>
</Descriptions>
</div>
}
<div style={{display: 'flex', justifyContent: 'center', marginTop: 40}}>
<Space>
<Button onClick={() => formRef.setFieldsValue(basicInfoValues)}>重置</Button>
<Button type={"primary"} onClick={() => {
formRef.submit()
}}>提交</Button>
</Space>
</div>
</Form>
</div>
<div className={styles.cusSurveyRight}>
<div className={styles.eventsTitle}>
<span className={styles.titleText}><SoundFilled/> 客户动态</span>
<a onClick={() => setCurrentTab('events')} style={{fontSize: 12}}>查看更多<RightOutlined/></a>
</div>
<Events data={initialEvents.filter(elem => elem !== null)} simpleRender={true} staffMap={staffMap}
extCustomerID={extCustomerID}/>
</div>
</div>
</ProCard.TabPane>
<ProCard.TabPane key="events" tab="客户动态">
<Events staffMap={staffMap} extCustomerID={extCustomerID}/>
</ProCard.TabPane>
<ProCard.TabPane key="room" tab="所在群聊">
<ProTable<GroupChatItem>
search={false}
formRef={queryFormRef}
actionRef={actionRef}
className={'table'}
scroll={{x: 'max-content'}}
columns={columns}
rowKey="id"
toolBarRender={false}
bordered={false}
tableAlertRender={false}
dateFormatter="string"
request={async (originParams: any, sort, filter) => {
return ProTableRequestAdapter(
originParams,
sort,
filter,
QueryCustomerGroupsList,
);
}}
/>
</ProCard.TabPane>
<ProCard.TabPane key="chat" tab="聊天记录">
{
setStaffMap[currentStaff]?.enable_msg_arch === 1 ? <Button
key={'chatSession'}
type={"link"}
icon={<ClockCircleOutlined style={{fontSize: 16, verticalAlign: '-3px'}}/>}
onClick={() => {
window.open(`/staff-admin/corp-risk-control/chat-session?staff=${currentStaff}`)
}}
>
聊天记录查询
</Button>
:
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={<span>员工暂未开启消息存档</span>}/>
}
</ProCard.TabPane>
</ProCard>
<CustomerTagSelectionModal
type={'customerDetailEnterpriseTag'}
isEditable={true}
withLogicalCondition={false}
width={'630px'}
visible={customerTagModalVisible}
setVisible={setCustomerTagModalVisible}
defaultCheckedTags={defaultCustomerTags}
onFinish={(selectedTags) => {
const removeAry = _.difference(defaultCustomerTags.map(dt => dt.ext_id), selectedTags.map(st => st.ext_id))
UpdateCustomerTags({
// @ts-ignore
add_ext_tag_ids: selectedTags.map((tag) => tag.ext_id),
ext_customer_ids: [extCustomerID],
ext_staff_id: currentStaff,
// @ts-ignore
remove_ext_tag_ids: removeAry
}).then(() => {
getCustomerDetail()
})
}}
allTagGroups={allCustomerTagGroups}
/>
<InternalTagModal
width={560}
allTags={internalTagList}
allTagsMap={internalTagListMap}
setAllTags={setInternalTagList}
visible={personalTagModalVisible}
setVisible={setPersonalTagModalVisible}
defaultCheckedTagsIds={defaultInternalTagsIds}
reloadTags={getInternalTags}
onFinish={(selectedTags) => {
console.log('selectedTags', selectedTags)
const removeAry = _.difference(defaultInternalTagsIds, selectedTags.map(st => st.id))
CustomerInternalTags({
// @ts-ignore
add_ext_tag_ids: selectedTags.map((tag) => tag.id),
ext_customer_id: extCustomerID,
ext_staff_id: currentStaff,
// @ts-ignore
remove_ext_tag_ids: removeAry
}).then(() => {
getCustomerDetail()
})
}
}
/>
</PageContainer>
);
}
Example #11
Source File: PopularTimes.tsx From office-hours with GNU General Public License v3.0 | 4 votes |
export default function PopularTimes({ heatmap }: HeatmapProps): ReactElement {
const [currentDayOfWeek, setCurrentDayOfWeek] = useState(new Date().getDay());
const [firstHour, lastHour] = findWeekMinAndMax(heatmap);
const dailyAvgWaitTimes: number[] = chunk(heatmap, 24).map((hours) => {
const filteredOfficeHours = hours.filter((v) => v !== -1);
return filteredOfficeHours.length > 0 ? mean(filteredOfficeHours) : -1;
});
return (
<div className="hide-in-percy">
<TitleRow>
<h2>Wait Times on</h2>
<Dropdown
trigger={["click"]}
overlay={
<Menu>
{DAYS_OF_WEEK.map((dayName, i) => (
<Menu.Item key={dayName}>
<a onClick={() => setCurrentDayOfWeek(i)}>{dayName}</a>
</Menu.Item>
))}
</Menu>
}
>
<WeekdayDropdown>
{DAYS_OF_WEEK[currentDayOfWeek]}
<DownOutlined />
</WeekdayDropdown>
</Dropdown>
</TitleRow>
<GraphWithArrow>
<GraphArrowButtons
onClick={() => setCurrentDayOfWeek((7 + currentDayOfWeek - 1) % 7)}
>
<LeftOutlined />
</GraphArrowButtons>
<GraphContainer>
<ParentSize>
{({ width }) => (
<TimeGraph
values={heatmap
.slice(currentDayOfWeek * 24, (currentDayOfWeek + 1) * 24 - 1)
.map((i) => (i < 0 ? 0 : Math.floor(i)))}
maxTime={Math.max(...heatmap)}
firstHour={firstHour}
lastHour={lastHour}
width={width}
height={220}
/>
)}
</ParentSize>
</GraphContainer>
<GraphArrowButtons
onClick={() => setCurrentDayOfWeek((currentDayOfWeek + 1) % 7)}
>
<RightOutlined />
</GraphArrowButtons>
</GraphWithArrow>
{dailyAvgWaitTimes[currentDayOfWeek] >= 0 && (
<GraphNotes>
<ClockCircleOutlined /> {DAYS_OF_WEEK[currentDayOfWeek]}s have{" "}
<strong>
{generateBusyText(currentDayOfWeek, dailyAvgWaitTimes)}
</strong>{" "}
wait times.
</GraphNotes>
)}
{new Date().getDay() === currentDayOfWeek &&
heatmap[currentDayOfWeek * 24 + new Date().getHours()] >= 0 && (
<GraphNotes>
<HourglassOutlined /> At {formatDateHour(new Date().getHours())},
people generally wait{" "}
<strong>
{formatWaitTime(
heatmap[currentDayOfWeek * 24 + new Date().getHours()]
)}
</strong>
.
</GraphNotes>
)}
</div>
);
}
Example #12
Source File: App.tsx From pcap2socks-gui with MIT License | 4 votes |
renderRunning = () => {
return (
<div className="content-content">
<Row className="content-content-row" gutter={[16, 16]} justify="center">
<Col className="content-content-col" span={24}>
{(() => {
if (Number.isNaN(this.state.time)) {
return <QuestionCircleTwoTone className="content-content-icon" />;
} else {
return <CheckCircleTwoTone className="content-content-icon" twoToneColor="#52c41a" />;
}
})()}
</Col>
</Row>
<Row className="content-content-row" gutter={[16, 32]} justify="center">
<Col className="content-content-col" span={24}>
<Paragraph>
<Title level={3}>
{(() => {
if (Number.isNaN(this.state.time)) {
return "未运行";
} else {
return "运行中";
}
})()}
</Title>
</Paragraph>
</Col>
</Row>
<Row gutter={[16, 0]} justify="center">
<Col xs={24} sm={12} md={6} style={{ marginBottom: "16px" }}>
<Card className="card" hoverable>
<Statistic
precision={2}
prefix={<ClockCircleOutlined />}
title="运行时间"
value={Convert.convertTime(this.state.time)}
/>
</Card>
</Col>
<Col xs={24} sm={12} md={6} style={{ marginBottom: "16px" }}>
<Card className="card" hoverable>
<Statistic
prefix={<HourglassOutlined />}
title="延迟"
value={Convert.convertDuration(this.state.latency)}
valueStyle={(() => {
if (this.state.latency === Infinity) {
return { color: "#cf1322" };
} else if (this.state.latency >= 100) {
return { color: "#faad14" };
}
})()}
suffix={Convert.convertDurationUnit(this.state.latency)}
/>
</Card>
</Col>
<Col xs={24} sm={12} md={6} style={{ marginBottom: "16px" }}>
<Card hoverable onClick={this.switchTraffic}>
<Statistic
precision={2}
prefix={<ArrowUpOutlined />}
title="上传"
value={this.showUploadValue()}
suffix={this.showUploadUnit()}
/>
</Card>
</Col>
<Col xs={24} sm={12} md={6} style={{ marginBottom: "16px" }}>
<Card hoverable onClick={this.switchTraffic}>
<Statistic
precision={2}
prefix={<ArrowDownOutlined />}
title="下载"
value={this.showDownloadValue()}
suffix={this.showDownloadUnit()}
/>
</Card>
</Col>
</Row>
</div>
);
};