Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
D
dlink
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
zhaowei
dlink
Commits
61576e02
Commit
61576e02
authored
Jun 10, 2021
by
godkaikai
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
0.2.1
parent
58683396
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
164 additions
and
73 deletions
+164
-73
dependency-reduced-pom.xml
dlink-client/dlink-client-1.12/dependency-reduced-pom.xml
+1
-1
pom.xml
dlink-core/pom.xml
+2
-2
package.json
dlink-web/package.json
+1
-0
index.tsx
dlink-web/src/components/Studio/StudioConsole/index.tsx
+1
-1
index.less
dlink-web/src/components/Studio/StudioTabs/index.less
+8
-0
index.tsx
dlink-web/src/components/Studio/StudioTabs/index.tsx
+7
-11
index.tsx
dlink-web/src/components/Studio/StudioTree/index.tsx
+17
-7
index.less
dlink-web/src/components/Studio/index.less
+38
-0
index.tsx
dlink-web/src/components/Studio/index.tsx
+51
-51
model.ts
dlink-web/src/pages/FlinkSqlStudio/model.ts
+19
-0
Welcome.tsx
dlink-web/src/pages/Welcome.tsx
+19
-0
No files found.
dlink-client/dlink-client-1.12/dependency-reduced-pom.xml
View file @
61576e02
...
...
@@ -3,7 +3,7 @@
<parent>
<artifactId>
dlink-client
</artifactId>
<groupId>
com.dlink
</groupId>
<version>
0.
2.0
</version>
<version>
0.
3.0-SNAPSHOT
</version>
</parent>
<modelVersion>
4.0.0
</modelVersion>
<artifactId>
dlink-client-1.12
</artifactId>
...
...
dlink-core/pom.xml
View file @
61576e02
...
...
@@ -59,12 +59,12 @@
<dependency>
<groupId>
com.dlink
</groupId>
<artifactId>
dlink-client-1.12
</artifactId>
<
scope>
provided
</scope
>
<
!--<scope>provided</scope>--
>
</dependency>
<dependency>
<groupId>
com.dlink
</groupId>
<artifactId>
dlink-connector-jdbc
</artifactId>
<
scope>
provided
</scope
>
<
!--<scope>provided</scope>--
>
</dependency>
</dependencies>
</project>
\ No newline at end of file
dlink-web/package.json
View file @
61576e02
...
...
@@ -66,6 +66,7 @@
"nzh"
:
"^1.0.3"
,
"omit.js"
:
"^2.0.2"
,
"react"
:
"^17.0.0"
,
"react-custom-scrollbars"
:
"^4.2.1"
,
"react-dev-inspector"
:
"^1.1.1"
,
"react-dom"
:
"^17.0.0"
,
"react-helmet-async"
:
"^1.0.4"
,
...
...
dlink-web/src/components/Studio/StudioConsole/index.tsx
View file @
61576e02
...
...
@@ -16,7 +16,7 @@ const { TabPane } = Tabs;
const
StudioConsole
=
(
props
:
any
)
=>
{
return
(
<
Tabs
defaultActiveKey=
"StudioMsg"
size=
"small"
>
<
Tabs
defaultActiveKey=
"StudioMsg"
size=
"small"
tabPosition=
"bottom"
style=
{
{
border
:
"1px solid #f0f0f0"
}
}
>
<
TabPane
tab=
{
<
span
>
...
...
dlink-web/src/components/Studio/StudioTabs/index.less
0 → 100644
View file @
61576e02
.edit-tabs{
:global {
.ant-tabs-nav, .ant-tabs-bottom > .ant-tabs-nav, .ant-tabs-top > div > .ant-tabs-nav, .ant-tabs-bottom > div > .ant-tabs-nav {
margin: 0;
}
}
}
dlink-web/src/components/Studio/StudioTabs/index.tsx
View file @
61576e02
import
{
message
,
Tabs
}
from
'antd'
;
import
React
,
{
useState
}
from
'react'
;
import
StudioEdit
from
"../StudioEdit"
;
import
{
connect
}
from
"umi"
;
import
{
StateType
}
from
"@/pages/FlinkSqlStudio/model"
;
import
styles
from
'./index.less'
;
const
{
TabPane
}
=
Tabs
;
const
EditorTabs
=
(
props
:
any
)
=>
{
const
{
tabs
,
dispatch
}
=
props
;
const
{
tabs
,
dispatch
,
current
}
=
props
;
const
[
newTabIndex
,
setNewTabIndex
]
=
useState
<
number
>
(
0
);
const
[
activeKey
,
setActiveKey
]
=
useState
<
number
>
(
tabs
.
activeKey
);
const
[
panes
,
setPanes
]
=
useState
<
any
>
(
tabs
.
panes
);
...
...
@@ -29,25 +29,19 @@ const EditorTabs = (props: any) => {
const
add
=
()
=>
{
message
.
warn
(
'敬请期待'
);
/*let index = newTabIndex + 1;
const newPanes = [...panes];
newPanes.push({ title: `未命名${index}`,value:'', key: -index });
setPanes(newPanes);
setActiveKey(-index);
setNewTabIndex(index);*/
};
const
remove
=
(
targetKey
:
any
)
=>
{
let
newActiveKey
=
tabs
.
activeKey
;
let
lastIndex
=
0
;
tabs
.
panes
.
forEach
((
pane
,
i
)
=>
{
if
(
pane
.
key
===
targetKey
)
{
if
(
pane
.
key
.
toString
()
===
targetKey
)
{
lastIndex
=
i
-
1
;
}
});
let
panes
=
tabs
.
panes
;
const
newPanes
=
panes
.
filter
(
pane
=>
pane
.
key
!=
targetKey
);
if
(
newPanes
.
length
&&
newActiveKey
===
targetKey
)
{
const
newPanes
=
panes
.
filter
(
pane
=>
pane
.
key
.
toString
()
!=
targetKey
);
if
(
newPanes
.
length
&&
newActiveKey
.
toString
()
===
targetKey
)
{
if
(
lastIndex
>
0
)
{
newActiveKey
=
newPanes
[
lastIndex
].
key
;
}
else
{
...
...
@@ -66,11 +60,13 @@ const EditorTabs = (props: any) => {
return
(
<>
<
Tabs
hideAdd
type=
"editable-card"
size=
"small"
onChange=
{
onChange
}
activeKey=
{
tabs
.
activeKey
+
''
}
onEdit=
{
onEdit
}
className=
{
styles
[
"edit-tabs"
]
}
>
{
tabs
.
panes
.
map
(
pane
=>
(
<
TabPane
tab=
{
pane
.
title
}
key=
{
pane
.
key
}
closable=
{
pane
.
closable
}
>
...
...
dlink-web/src/components/Studio/StudioTree/index.tsx
View file @
61576e02
...
...
@@ -15,7 +15,12 @@ const { DirectoryTree } = Tree;
const
{
Search
}
=
Input
;
type
StudioTreeProps
=
{};
type
StudioTreeProps
=
{
rightClickMenu
:
StateType
[
'rightClickMenu'
];
dispatch
:
any
;
tabs
:
StateType
[
'tabs'
];
current
:
StateType
[
'current'
];
};
type
RightClickMenu
=
{
pageX
:
number
,
...
...
@@ -44,7 +49,7 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
const
[
treeData
,
setTreeData
]
=
useState
<
TreeDataNode
[]
>
();
const
[
dataList
,
setDataList
]
=
useState
<
[]
>
();
const
[
rightClickNodeTreeItem
,
setRightClickNodeTreeItem
]
=
useState
<
RightClickMenu
>
();
const
{
currentPath
,
dispatch
,
tabs
}
=
props
;
const
{
rightClickMenu
,
dispatch
,
tabs
}
=
props
;
const
[
updateCatalogueModalVisible
,
handleUpdateCatalogueModalVisible
]
=
useState
<
boolean
>
(
false
);
const
[
updateTaskModalVisible
,
handleUpdateTaskModalVisible
]
=
useState
<
boolean
>
(
false
);
const
[
isCreate
,
setIsCreate
]
=
useState
<
boolean
>
(
true
);
...
...
@@ -74,7 +79,6 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
};
const
handleMenuClick
=
(
key
:
string
)
=>
{
setRightClickNodeTreeItem
(
null
);
if
(
key
==
'Open'
){
toOpen
(
rightClickNode
);
}
else
if
(
key
==
'Submit'
){
...
...
@@ -109,6 +113,7 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
key
:
node
.
taskId
,
value
:(
result
.
datas
.
statement
?
result
.
datas
.
statement
:
''
),
closable
:
true
,
path
:
node
.
path
,
task
:{
session
:
'admin'
,
maxRowNum
:
100
,
...
...
@@ -200,8 +205,8 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
position
:
'absolute'
,
// left: `${pageX - 50}px`,
// top: `${pageY - 202}px`,
left
:
`
${
pageX
-
30
}
px`
,
top
:
`
${
pageY
-
1
52
}
px`
,
left
:
`
${
pageX
-
25
}
px`
,
top
:
`
${
pageY
-
1
40
}
px`
,
};
let
menuItems
;
if
(
rightClickNode
&&
rightClickNode
.
isLeaf
){
...
...
@@ -216,6 +221,7 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
<
Menu
.
Item
key=
'CreateCatalogue'
>
{
'创建目录'
}
</
Menu
.
Item
>
<
Menu
.
Item
key=
'CreateTask'
>
{
'创建作业'
}
</
Menu
.
Item
>
<
Menu
.
Item
key=
'Rename'
>
{
'重命名'
}
</
Menu
.
Item
>
<
Menu
.
Item
disabled
>
{
'删除'
}
</
Menu
.
Item
>
</>)
}
else
{
menuItems
=
(<>
...
...
@@ -234,7 +240,7 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
{
menuItems
}
</
Menu
>
);
return
(
rightClickNodeTreeItem
==
null
)
?
''
:
menu
;
return
rightClickMenu
?
menu
:
''
;
};
const
getEmpty
=
()
=>
{
...
...
@@ -257,6 +263,10 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
id
:
e
.
node
.
id
,
categoryName
:
e
.
node
.
name
});
dispatch
&&
dispatch
({
type
:
"Studio/showRightClickMenu"
,
payload
:
true
,
});
};
const
onSelect
=
(
selectedKeys
:[],
e
:
any
)
=>
{
...
...
@@ -267,7 +277,6 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
});
toOpen
(
e
.
node
);
}
setRightClickNodeTreeItem
(
null
);
};
return
(
...
...
@@ -329,4 +338,5 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
export
default
connect
(({
Studio
}:
{
Studio
:
StateType
})
=>
({
currentPath
:
Studio
.
currentPath
,
tabs
:
Studio
.
tabs
,
rightClickMenu
:
Studio
.
rightClickMenu
,
}))(
StudioTree
);
dlink-web/src/components/Studio/index.less
View file @
61576e02
...
...
@@ -16,3 +16,41 @@
.card{
background-color: #ffffff;
}
:global {
/* --- card 内偏移样式 --- start */
#studio_card > .ant-card-body {
padding: 0px;
}
/* --- card 内偏移样式 --- start */
}
/* --- tabs 垂直样式 --- start */
.vertical-tabs{
:global {
.ant-tabs-left>.ant-tabs-nav, .ant-tabs-right>.ant-tabs-nav, .ant-tabs-left>div>.ant-tabs-nav, .ant-tabs-right>div>.ant-tabs-nav {
min-width: 20px;
}
.ant-tabs-left>.ant-tabs-nav .ant-tabs-tab, .ant-tabs-right>.ant-tabs-nav .ant-tabs-tab, .ant-tabs-left>div>.ant-tabs-nav .ant-tabs-tab, .ant-tabs-right>div>.ant-tabs-nav .ant-tabs-tab {
padding: 5px 5px;
}
.ant-tabs-tab-btn {
writing-mode: vertical-lr;
}
.ant-tabs-tab .anticon {
margin-right: 0;
}
.anticon {
vertical-align: 0;
}
}
}
/* --- tabs 垂直样式 --- end */
dlink-web/src/components/Studio/index.tsx
View file @
61576e02
import
React
,
{
useEffect
,
useState
}
from
"react"
;
import
{
connect
}
from
"umi"
;
import
styles
from
'./index.less'
;
import
{
BarsOutlined
,
SettingOutlined
,
AuditOutlined
,
ScheduleOutlined
,
AppstoreOutlined
,
ApiOutlined
,
DashboardOutlined
,
FireOutlined
}
from
"@ant-design/icons"
;
FireOutlined
,
ClusterOutlined
,
DatabaseOutlined
,
FunctionOutlined
}
from
"@ant-design/icons"
;
import
StudioMenu
from
"./StudioMenu"
;
import
{
Row
,
Col
,
Card
,
Empty
,
Tabs
,
Form
,
BackTop
}
from
"antd"
;
...
...
@@ -17,29 +18,55 @@ import StudioConnector from "./StudioConnector";
const
{
TabPane
}
=
Tabs
;
type
StudioProps
=
{
sql
:
StateType
[
'sql'
];
// sql: StateType['sql'];
rightClickMenu
:
StateType
[
'rightClickMenu'
];
dispatch
:
any
;
};
const
Studio
:
React
.
FC
<
StudioProps
>
=
({
sql
})
=>
{
const
[
console
,
setConsole
]
=
useState
<
boolean
>
(
false
);
const
[
sqls
,
setSqls
]
=
useState
<
String
>
();
const
Studio
:
React
.
FC
<
StudioProps
>
=
(
props
)
=>
{
const
{
rightClickMenu
,
dispatch
}
=
props
;
const
[
form
]
=
Form
.
useForm
();
useEffect
(()
=>
{
/*
useEffect(() => {
setSqls(sql);
},
[
sql
]);
}, [sql]);*/
const
onClick
=
()
=>
{
if
(
rightClickMenu
){
dispatch
&&
dispatch
({
type
:
"Studio/showRightClickMenu"
,
payload
:
false
,
});
}
};
return
(
<
div
>
<
div
onClick=
{
onClick
}
>
<
StudioMenu
form=
{
form
}
/>
<
Card
bordered=
{
false
}
className=
{
styles
.
card
}
size=
"small"
>
<
Card
bordered=
{
false
}
className=
{
styles
.
card
}
size=
"small"
id=
"studio_card"
>
<
Row
>
<
Col
span=
{
4
}
>
<
Tabs
defaultActiveKey=
"1"
size=
"small"
>
<
TabPane
tab=
{
<
span
><
BarsOutlined
/>
目录
</
span
>
}
key=
"1
"
>
<
Col
span=
{
4
}
className=
{
styles
[
"vertical-tabs"
]
}
>
<
Tabs
defaultActiveKey=
"1"
size=
"small"
tabPosition=
"left"
style=
{
{
height
:
"100%"
,
border
:
"1px solid #f0f0f0"
}
}
>
<
TabPane
tab=
{
<
span
><
BarsOutlined
/>
目录
</
span
>
}
key=
"StudioTree
"
>
<
StudioTree
/>
</
TabPane
>
<
TabPane
tab=
{
<
span
><
AppstoreOutlined
/>
元数据
</
span
>
}
key=
"2"
>
<
TabPane
tab=
{
<
span
><
DatabaseOutlined
/>
数据源
</
span
>
}
key=
"DataSource"
>
<
Empty
image=
{
Empty
.
PRESENTED_IMAGE_SIMPLE
}
/>
</
TabPane
>
<
TabPane
tab=
{
<
span
><
AppstoreOutlined
/>
元数据
</
span
>
}
key=
"MetaData"
>
<
Empty
image=
{
Empty
.
PRESENTED_IMAGE_SIMPLE
}
/>
</
TabPane
>
<
TabPane
tab=
{
<
span
><
ClusterOutlined
/>
集群
</
span
>
}
key=
"Cluster"
>
<
Empty
image=
{
Empty
.
PRESENTED_IMAGE_SIMPLE
}
/>
</
TabPane
>
<
TabPane
tab=
{
<
span
><
ApiOutlined
/>
连接器
</
span
>
}
key=
"Connectors"
>
<
StudioConnector
/>
</
TabPane
>
<
TabPane
tab=
{
<
span
><
FireOutlined
/>
任务
</
span
>
}
key=
"FlinkTask"
>
<
Empty
image=
{
Empty
.
PRESENTED_IMAGE_SIMPLE
}
/>
</
TabPane
>
<
TabPane
tab=
{
<
span
><
FunctionOutlined
/>
函数
</
span
>
}
key=
"FlinkTask"
>
<
Empty
image=
{
Empty
.
PRESENTED_IMAGE_SIMPLE
}
/>
</
TabPane
>
</
Tabs
>
...
...
@@ -47,59 +74,32 @@ const Studio: React.FC<StudioProps> = ({sql}) => {
<
Col
span=
{
16
}
>
<
StudioTabs
/>
<
StudioEdit
/>
<
StudioConsole
/>
</
Col
>
<
Col
span=
{
4
}
>
<
Tabs
defaultActiveKey=
"1"
size=
"small"
>
<
TabPane
tab=
{
<
span
><
SettingOutlined
/>
配置
</
span
>
}
key=
"1"
>
<
Col
span=
{
4
}
className=
{
styles
[
"vertical-tabs"
]
}
>
<
Tabs
defaultActiveKey=
"1"
size=
"small"
tabPosition=
"right"
style=
{
{
height
:
"100%"
,
border
:
"1px solid #f0f0f0"
}
}
>
<
TabPane
tab=
{
<
span
><
SettingOutlined
/>
配置
</
span
>
}
key=
"1"
>
<
StudioSetting
form=
{
form
}
/>
</
TabPane
>
<
TabPane
tab=
{
<
span
><
ScheduleOutlined
/>
详情
</
span
>
}
key=
"2"
>
<
TabPane
tab=
{
<
span
><
ScheduleOutlined
/>
详情
</
span
>
}
key=
"2"
>
<
Empty
image=
{
Empty
.
PRESENTED_IMAGE_SIMPLE
}
/>
</
TabPane
>
<
TabPane
tab=
{
<
span
><
AuditOutlined
/>
审计
</
span
>
}
key=
"3"
>
<
Empty
image=
{
Empty
.
PRESENTED_IMAGE_SIMPLE
}
/>
</
TabPane
>
</
Tabs
>
<
Tabs
defaultActiveKey=
"1"
size=
"small"
>
<
TabPane
tab=
{
<
span
>
<
ApiOutlined
/>
连接器
</
span
>
}
key=
"1"
>
<
StudioConnector
/>
</
TabPane
>
<
TabPane
tab=
{
<
span
>
<
DashboardOutlined
/>
总览
</
span
>
}
key=
"2"
>
<
Empty
image=
{
Empty
.
PRESENTED_IMAGE_SIMPLE
}
/>
</
TabPane
>
<
TabPane
tab=
{
<
span
>
<
FireOutlined
/>
任务
</
span
>
}
key=
"3"
>
<
TabPane
tab=
{
<
span
><
AuditOutlined
/>
审计
</
span
>
}
key=
"3"
>
<
Empty
image=
{
Empty
.
PRESENTED_IMAGE_SIMPLE
}
/>
</
TabPane
>
</
Tabs
>
</
Col
>
</
Row
>
<
Row
>
<
Col
span=
{
24
}
>
<
StudioConsole
/>
</
Col
>
</
Row
>
</
Card
>
<
BackTop
/>
</
div
>
)
};
/*function mapStateToProps(state) {
// 这个state是所有model层的state,这里只用到其中一个,所以state.testPage把命名空间为testPage这个model层的state数据取出来
// es6语法解构赋值
debugger;
const { data } = state.Studio;
// 这里return出去的数据,会变成此组件的props,在组件可以通过props.num取到。props变化了,会重新触发render方法,界面也就更新了。
return {
data,
};
}*/
// export default connect(mapStateToProps)(Studio);
export
default
connect
(({
Studio
}:
{
Studio
:
StateType
})
=>
({
current
:
Studio
.
current
,
catalogue
:
Studio
.
catalogue
,
sql
:
Studio
.
sql
,
cluster
:
Studio
.
cluster
,
tabs
:
Studio
.
tabs
,
rightClickMenu
:
Studio
.
rightClickMenu
,
}))(
Studio
);
// export default Studio;
dlink-web/src/pages/FlinkSqlStudio/model.ts
View file @
61576e02
...
...
@@ -47,6 +47,7 @@ export type TabsItemType = {
key
:
number
,
value
:
string
;
closable
:
boolean
;
path
:
string
[];
task
?:
TaskType
;
console
:
ConsoleType
;
}
...
...
@@ -56,6 +57,12 @@ export type TabsType = {
panes
?:
TabsItemType
[];
}
export
type
RightClickMenu
=
{
pageX
:
number
,
pageY
:
number
,
id
:
number
,
name
:
string
};
export
type
StateType
=
{
cluster
?:
ClusterType
[];
current
:
TabsItemType
;
...
...
@@ -64,6 +71,7 @@ export type StateType = {
currentPath
?:
string
[];
tabs
:
TabsType
;
session
:
string
[];
rightClickMenu
?:
boolean
;
};
export
type
ModelType
=
{
...
...
@@ -80,6 +88,7 @@ export type ModelType = {
changeActiveKey
:
Reducer
<
StateType
>
;
saveTaskData
:
Reducer
<
StateType
>
;
saveSession
:
Reducer
<
StateType
>
;
showRightClickMenu
:
Reducer
<
StateType
>
;
};
};
...
...
@@ -103,6 +112,7 @@ const Model: ModelType = {
key
:
0
,
value
:
''
,
closable
:
false
,
path
:
[
'草稿'
],
task
:{
checkPoint
:
0
,
savePointPath
:
''
,
...
...
@@ -127,6 +137,7 @@ const Model: ModelType = {
key
:
0
,
value
:
''
,
closable
:
false
,
path
:
[
'草稿'
],
task
:{
checkPoint
:
0
,
savePointPath
:
''
,
...
...
@@ -143,6 +154,7 @@ const Model: ModelType = {
}],
},
session
:[
'admin'
],
rightClickMenu
:
false
},
effects
:
{
...
...
@@ -224,6 +236,7 @@ const Model: ModelType = {
tabs
:{
...
tabs
,
},
currentPath
:
newCurrent
.
path
,
};
},
saveTaskData
(
state
,
{
payload
})
{
...
...
@@ -253,6 +266,12 @@ const Model: ModelType = {
session
:
newSession
,
};
},
showRightClickMenu
(
state
,
{
payload
})
{
return
{
...
state
,
rightClickMenu
:
payload
,
};
},
},
};
...
...
dlink-web/src/pages/Welcome.tsx
View file @
61576e02
...
...
@@ -143,6 +143,25 @@ export default (): React.ReactNode => {
</
ul
>
</
Paragraph
>
</
Timeline
.
Item
>
<
Timeline
.
Item
><
Text
code
>
0.2.1
</
Text
>
<
Text
type=
"secondary"
>
2021-06-11
</
Text
>
<
p
>
</
p
>
<
Paragraph
>
<
ul
>
<
li
>
<
Link
href=
""
>
FlinkSql Studio 页面仿IDE设计改进
</
Link
>
</
li
>
<
li
>
<
Link
href=
""
>
解决了目录树右键菜单的不能任意点关闭问题
</
Link
>
</
li
>
<
li
>
<
Link
href=
""
>
解决了选项卡关闭不能正确刷新编辑器的问题
</
Link
>
</
li
>
<
li
>
<
Link
href=
""
>
解决了当前位置不根据选项卡刷新的问题
</
Link
>
</
li
>
</
ul
>
</
Paragraph
>
</
Timeline
.
Item
>
</
Timeline
>
</
Card
>
</
PageContainer
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment