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
a82923c7
Commit
a82923c7
authored
Mar 03, 2022
by
wenmo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
任务监控的savepoint等操作
parent
6fd1a0fe
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
290 additions
and
115 deletions
+290
-115
StudioController.java
.../src/main/java/com/dlink/controller/StudioController.java
+2
-2
JobInfoDetail.java
dlink-admin/src/main/java/com/dlink/model/JobInfoDetail.java
+0
-9
JobInstanceStatus.java
...dmin/src/main/java/com/dlink/model/JobInstanceStatus.java
+63
-9
StudioService.java
...-admin/src/main/java/com/dlink/service/StudioService.java
+1
-1
JobInstanceServiceImpl.java
...n/java/com/dlink/service/impl/JobInstanceServiceImpl.java
+26
-1
StudioServiceImpl.java
...c/main/java/com/dlink/service/impl/StudioServiceImpl.java
+8
-4
LogUtil.java
dlink-common/src/main/java/com/dlink/utils/LogUtil.java
+24
-13
FlinkRestResultConstant.java
...main/java/com/dlink/constant/FlinkRestResultConstant.java
+1
-0
JobStatus.tsx
dlink-web/src/components/Common/JobStatus.tsx
+32
-3
DDL.ts
dlink-web/src/components/Studio/StudioEvent/DDL.ts
+2
-2
index.tsx
dlink-web/src/pages/DevOps/JobInfo/Config/index.tsx
+14
-2
index.tsx
dlink-web/src/pages/DevOps/JobInfo/index.tsx
+80
-27
index.tsx
dlink-web/src/pages/DevOps/JobInstanceTable/index.tsx
+2
-28
data.d.ts
dlink-web/src/pages/DevOps/data.d.ts
+11
-3
index.tsx
dlink-web/src/pages/DevOps/index.tsx
+24
-11
No files found.
dlink-admin/src/main/java/com/dlink/controller/StudioController.java
View file @
a82923c7
...
@@ -153,7 +153,7 @@ public class StudioController {
...
@@ -153,7 +153,7 @@ public class StudioController {
*/
*/
@GetMapping
(
"/savepoint"
)
@GetMapping
(
"/savepoint"
)
public
Result
savepoint
(
@RequestParam
Integer
clusterId
,
@RequestParam
String
jobId
,
public
Result
savepoint
(
@RequestParam
Integer
clusterId
,
@RequestParam
String
jobId
,
@RequestParam
String
savePointType
,
@RequestParam
String
name
)
{
@RequestParam
String
savePointType
,
@RequestParam
String
name
,
@RequestParam
Integer
taskId
)
{
return
Result
.
succeed
(
studioService
.
savepoint
(
clusterId
,
jobId
,
savePointType
,
name
),
"savepoint 成功"
);
return
Result
.
succeed
(
studioService
.
savepoint
(
taskId
,
clusterId
,
jobId
,
savePointType
,
name
),
"savepoint 成功"
);
}
}
}
}
dlink-admin/src/main/java/com/dlink/model/JobInfoDetail.java
View file @
a82923c7
...
@@ -12,7 +12,6 @@ public class JobInfoDetail {
...
@@ -12,7 +12,6 @@ public class JobInfoDetail {
private
JobInstance
instance
;
private
JobInstance
instance
;
private
Cluster
cluster
;
private
Cluster
cluster
;
private
ClusterConfiguration
clusterConfiguration
;
private
ClusterConfiguration
clusterConfiguration
;
private
Task
task
;
private
History
history
;
private
History
history
;
private
JobHistory
jobHistory
;
private
JobHistory
jobHistory
;
...
@@ -52,14 +51,6 @@ public class JobInfoDetail {
...
@@ -52,14 +51,6 @@ public class JobInfoDetail {
this
.
clusterConfiguration
=
clusterConfiguration
;
this
.
clusterConfiguration
=
clusterConfiguration
;
}
}
public
Task
getTask
()
{
return
task
;
}
public
void
setTask
(
Task
task
)
{
this
.
task
=
task
;
}
public
History
getHistory
()
{
public
History
getHistory
()
{
return
history
;
return
history
;
}
}
...
...
dlink-admin/src/main/java/com/dlink/model/JobInstanceStatus.java
View file @
a82923c7
...
@@ -13,19 +13,17 @@ public class JobInstanceStatus {
...
@@ -13,19 +13,17 @@ public class JobInstanceStatus {
private
Integer
finished
=
0
;
private
Integer
finished
=
0
;
private
Integer
failed
=
0
;
private
Integer
failed
=
0
;
private
Integer
canceled
=
0
;
private
Integer
canceled
=
0
;
private
Integer
restarting
=
0
;
private
Integer
created
=
0
;
private
Integer
failing
=
0
;
private
Integer
cancelling
=
0
;
private
Integer
suspended
=
0
;
private
Integer
reconciling
=
0
;
private
Integer
unknown
=
0
;
public
JobInstanceStatus
()
{
public
JobInstanceStatus
()
{
}
}
public
JobInstanceStatus
(
Integer
all
,
Integer
initializing
,
Integer
running
,
Integer
finished
,
Integer
failed
,
Integer
canceled
)
{
this
.
all
=
all
;
this
.
initializing
=
initializing
;
this
.
running
=
running
;
this
.
finished
=
finished
;
this
.
failed
=
failed
;
this
.
canceled
=
canceled
;
}
public
Integer
getAll
()
{
public
Integer
getAll
()
{
return
all
;
return
all
;
}
}
...
@@ -73,4 +71,60 @@ public class JobInstanceStatus {
...
@@ -73,4 +71,60 @@ public class JobInstanceStatus {
public
void
setCanceled
(
Integer
canceled
)
{
public
void
setCanceled
(
Integer
canceled
)
{
this
.
canceled
=
canceled
;
this
.
canceled
=
canceled
;
}
}
public
Integer
getRestarting
()
{
return
restarting
;
}
public
void
setRestarting
(
Integer
restarting
)
{
this
.
restarting
=
restarting
;
}
public
Integer
getCreated
()
{
return
created
;
}
public
void
setCreated
(
Integer
created
)
{
this
.
created
=
created
;
}
public
Integer
getFailing
()
{
return
failing
;
}
public
void
setFailing
(
Integer
failing
)
{
this
.
failing
=
failing
;
}
public
Integer
getCancelling
()
{
return
cancelling
;
}
public
void
setCancelling
(
Integer
cancelling
)
{
this
.
cancelling
=
cancelling
;
}
public
Integer
getSuspended
()
{
return
suspended
;
}
public
void
setSuspended
(
Integer
suspended
)
{
this
.
suspended
=
suspended
;
}
public
Integer
getReconciling
()
{
return
reconciling
;
}
public
void
setReconciling
(
Integer
reconciling
)
{
this
.
reconciling
=
reconciling
;
}
public
Integer
getUnknown
()
{
return
unknown
;
}
public
void
setUnknown
(
Integer
unknown
)
{
this
.
unknown
=
unknown
;
}
}
}
dlink-admin/src/main/java/com/dlink/service/StudioService.java
View file @
a82923c7
...
@@ -54,5 +54,5 @@ public interface StudioService {
...
@@ -54,5 +54,5 @@ public interface StudioService {
boolean
cancel
(
Integer
clusterId
,
String
jobId
);
boolean
cancel
(
Integer
clusterId
,
String
jobId
);
boolean
savepoint
(
Integer
clusterId
,
String
jobId
,
String
savePointType
,
String
name
);
boolean
savepoint
(
Integer
taskId
,
Integer
clusterId
,
String
jobId
,
String
savePointType
,
String
name
);
}
}
dlink-admin/src/main/java/com/dlink/service/impl/JobInstanceServiceImpl.java
View file @
a82923c7
...
@@ -67,6 +67,27 @@ public class JobInstanceServiceImpl extends SuperServiceImpl<JobInstanceMapper,
...
@@ -67,6 +67,27 @@ public class JobInstanceServiceImpl extends SuperServiceImpl<JobInstanceMapper,
break
;
break
;
case
CANCELED:
case
CANCELED:
jobInstanceStatus
.
setCanceled
(
counts
);
jobInstanceStatus
.
setCanceled
(
counts
);
break
;
case
RESTARTING:
jobInstanceStatus
.
setRestarting
(
counts
);
break
;
case
CREATED:
jobInstanceStatus
.
setCreated
(
counts
);
break
;
case
FAILING:
jobInstanceStatus
.
setFailed
(
counts
);
break
;
case
CANCELLING:
jobInstanceStatus
.
setCancelling
(
counts
);
break
;
case
SUSPENDED:
jobInstanceStatus
.
setSuspended
(
counts
);
break
;
case
RECONCILING:
jobInstanceStatus
.
setReconciling
(
counts
);
break
;
case
UNKNOWN:
jobInstanceStatus
.
setUnknown
(
counts
);
}
}
}
}
jobInstanceStatus
.
setAll
(
total
);
jobInstanceStatus
.
setAll
(
total
);
...
@@ -83,7 +104,6 @@ public class JobInstanceServiceImpl extends SuperServiceImpl<JobInstanceMapper,
...
@@ -83,7 +104,6 @@ public class JobInstanceServiceImpl extends SuperServiceImpl<JobInstanceMapper,
Asserts
.
checkNull
(
jobInstance
,
"该任务实例不存在"
);
Asserts
.
checkNull
(
jobInstance
,
"该任务实例不存在"
);
JobInfoDetail
jobInfoDetail
=
new
JobInfoDetail
(
jobInstance
.
getId
());
JobInfoDetail
jobInfoDetail
=
new
JobInfoDetail
(
jobInstance
.
getId
());
jobInfoDetail
.
setInstance
(
jobInstance
);
jobInfoDetail
.
setInstance
(
jobInstance
);
jobInfoDetail
.
setTask
(
taskService
.
getTaskInfoById
(
jobInstance
.
getTaskId
()));
jobInfoDetail
.
setCluster
(
clusterService
.
getById
(
jobInstance
.
getClusterId
()));
jobInfoDetail
.
setCluster
(
clusterService
.
getById
(
jobInstance
.
getClusterId
()));
jobInfoDetail
.
setJobHistory
(
jobHistoryService
.
getJobHistory
(
jobInstance
.
getId
()));
jobInfoDetail
.
setJobHistory
(
jobHistoryService
.
getJobHistory
(
jobInstance
.
getId
()));
History
history
=
historyService
.
getById
(
jobInstance
.
getHistoryId
());
History
history
=
historyService
.
getById
(
jobInstance
.
getHistoryId
());
...
@@ -105,6 +125,11 @@ public class JobInstanceServiceImpl extends SuperServiceImpl<JobInstanceMapper,
...
@@ -105,6 +125,11 @@ public class JobInstanceServiceImpl extends SuperServiceImpl<JobInstanceMapper,
Cluster
cluster
=
clusterService
.
getById
(
jobInstance
.
getClusterId
());
Cluster
cluster
=
clusterService
.
getById
(
jobInstance
.
getClusterId
());
JobHistory
jobHistoryJson
=
jobHistoryService
.
refreshJobHistory
(
id
,
cluster
.
getJobManagerHost
(),
jobInstance
.
getJid
());
JobHistory
jobHistoryJson
=
jobHistoryService
.
refreshJobHistory
(
id
,
cluster
.
getJobManagerHost
(),
jobInstance
.
getJid
());
JobHistory
jobHistory
=
jobHistoryService
.
getJobHistoryInfo
(
jobHistoryJson
);
JobHistory
jobHistory
=
jobHistoryService
.
getJobHistoryInfo
(
jobHistoryJson
);
if
(
jobHistory
.
getJob
().
has
(
FlinkRestResultConstant
.
ERRORS
)){
jobInstance
.
setStatus
(
JobStatus
.
UNKNOWN
.
getValue
());
updateById
(
jobInstance
);
return
jobInstance
;
}
jobInstance
.
setDuration
(
jobHistory
.
getJob
().
get
(
FlinkRestResultConstant
.
JOB_DURATION
).
asLong
()/
1000
);
jobInstance
.
setDuration
(
jobHistory
.
getJob
().
get
(
FlinkRestResultConstant
.
JOB_DURATION
).
asLong
()/
1000
);
jobInstance
.
setStatus
(
jobHistory
.
getJob
().
get
(
FlinkRestResultConstant
.
JOB_STATE
).
asText
());
jobInstance
.
setStatus
(
jobHistory
.
getJob
().
get
(
FlinkRestResultConstant
.
JOB_STATE
).
asText
());
updateById
(
jobInstance
);
updateById
(
jobInstance
);
...
...
dlink-admin/src/main/java/com/dlink/service/impl/StudioServiceImpl.java
View file @
a82923c7
...
@@ -295,9 +295,10 @@ public class StudioServiceImpl implements StudioService {
...
@@ -295,9 +295,10 @@ public class StudioServiceImpl implements StudioService {
}
}
@Override
@Override
public
boolean
savepoint
(
Integer
clusterId
,
String
jobId
,
String
savePointType
,
String
name
)
{
public
boolean
savepoint
(
Integer
taskId
,
Integer
clusterId
,
String
jobId
,
String
savePointType
,
String
name
)
{
Cluster
cluster
=
clusterService
.
getById
(
clusterId
);
Cluster
cluster
=
clusterService
.
getById
(
clusterId
);
Asserts
.
checkNotNull
(
cluster
,
"该集群不存在"
);
Asserts
.
checkNotNull
(
cluster
,
"该集群不存在"
);
boolean
useGateway
=
false
;
JobConfig
jobConfig
=
new
JobConfig
();
JobConfig
jobConfig
=
new
JobConfig
();
jobConfig
.
setAddress
(
cluster
.
getJobManagerHost
());
jobConfig
.
setAddress
(
cluster
.
getJobManagerHost
());
jobConfig
.
setType
(
cluster
.
getType
());
jobConfig
.
setType
(
cluster
.
getType
());
...
@@ -306,18 +307,21 @@ public class StudioServiceImpl implements StudioService {
...
@@ -306,18 +307,21 @@ public class StudioServiceImpl implements StudioService {
jobConfig
.
buildGatewayConfig
(
gatewayConfig
);
jobConfig
.
buildGatewayConfig
(
gatewayConfig
);
jobConfig
.
getGatewayConfig
().
getClusterConfig
().
setAppId
(
cluster
.
getName
());
jobConfig
.
getGatewayConfig
().
getClusterConfig
().
setAppId
(
cluster
.
getName
());
jobConfig
.
setTaskId
(
cluster
.
getTaskId
());
jobConfig
.
setTaskId
(
cluster
.
getTaskId
());
useGateway
=
true
;
}
else
{
jobConfig
.
setTaskId
(
taskId
);
}
}
JobManager
jobManager
=
JobManager
.
build
(
jobConfig
);
JobManager
jobManager
=
JobManager
.
build
(
jobConfig
);
jobManager
.
setUseGateway
(
true
);
jobManager
.
setUseGateway
(
useGateway
);
SavePointResult
savePointResult
=
jobManager
.
savepoint
(
jobId
,
savePointType
,
null
);
SavePointResult
savePointResult
=
jobManager
.
savepoint
(
jobId
,
savePointType
,
null
);
if
(
Asserts
.
isNotNull
(
savePointResult
))
{
if
(
Asserts
.
isNotNull
(
savePointResult
))
{
for
(
JobInfo
item
:
savePointResult
.
getJobInfos
())
{
for
(
JobInfo
item
:
savePointResult
.
getJobInfos
())
{
if
(
Asserts
.
isEqualsIgnoreCase
(
jobId
,
item
.
getJobId
()))
{
if
(
Asserts
.
isEqualsIgnoreCase
(
jobId
,
item
.
getJobId
())
&&
Asserts
.
isNotNull
(
jobConfig
.
getTaskId
())
)
{
Savepoints
savepoints
=
new
Savepoints
();
Savepoints
savepoints
=
new
Savepoints
();
savepoints
.
setName
(
name
);
savepoints
.
setName
(
name
);
savepoints
.
setType
(
savePointType
);
savepoints
.
setType
(
savePointType
);
savepoints
.
setPath
(
item
.
getSavePoint
());
savepoints
.
setPath
(
item
.
getSavePoint
());
savepoints
.
setTaskId
(
cluster
.
getTaskId
());
savepoints
.
setTaskId
(
jobConfig
.
getTaskId
());
savepointsService
.
save
(
savepoints
);
savepointsService
.
save
(
savepoints
);
}
}
}
}
...
...
dlink-common/src/main/java/com/dlink/utils/LogUtil.java
View file @
a82923c7
...
@@ -3,6 +3,7 @@ package com.dlink.utils;
...
@@ -3,6 +3,7 @@ package com.dlink.utils;
import
org.slf4j.Logger
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.slf4j.LoggerFactory
;
import
java.io.IOException
;
import
java.io.PrintWriter
;
import
java.io.PrintWriter
;
import
java.io.StringWriter
;
import
java.io.StringWriter
;
import
java.time.LocalDateTime
;
import
java.time.LocalDateTime
;
...
@@ -19,22 +20,32 @@ public class LogUtil {
...
@@ -19,22 +20,32 @@ public class LogUtil {
public
static
String
getError
(
Exception
e
){
public
static
String
getError
(
Exception
e
){
// e.printStackTrace();
// e.printStackTrace();
StringWriter
sw
=
new
StringWriter
();
String
error
=
null
;
PrintWriter
pw
=
new
PrintWriter
(
sw
);
try
(
StringWriter
sw
=
new
StringWriter
();
e
.
printStackTrace
(
pw
);
PrintWriter
pw
=
new
PrintWriter
(
sw
)){
String
error
=
sw
.
toString
();
e
.
printStackTrace
(
pw
);
logger
.
error
(
sw
.
toString
());
error
=
sw
.
toString
();
return
error
;
logger
.
error
(
error
);
}
catch
(
IOException
ioe
)
{
ioe
.
printStackTrace
();
}
finally
{
return
error
;
}
}
}
public
static
String
getError
(
String
msg
,
Exception
e
){
public
static
String
getError
(
String
msg
,
Exception
e
){
// e.printStackTrace();
// e.printStackTrace();
StringWriter
sw
=
new
StringWriter
();
String
error
=
null
;
PrintWriter
pw
=
new
PrintWriter
(
sw
);
try
(
StringWriter
sw
=
new
StringWriter
();
e
.
printStackTrace
(
pw
);
PrintWriter
pw
=
new
PrintWriter
(
sw
)){
LocalDateTime
now
=
LocalDateTime
.
now
();
e
.
printStackTrace
(
pw
);
String
error
=
now
.
toString
()
+
": "
+
msg
+
" \nError message:\n "
+
sw
.
toString
();
LocalDateTime
now
=
LocalDateTime
.
now
();
logger
.
error
(
error
);
error
=
now
.
toString
()
+
": "
+
msg
+
" \nError message:\n "
+
sw
.
toString
();
return
error
;
logger
.
error
(
error
);
}
catch
(
IOException
ioe
)
{
ioe
.
printStackTrace
();
}
finally
{
return
error
;
}
}
}
}
}
dlink-core/src/main/java/com/dlink/constant/FlinkRestResultConstant.java
View file @
a82923c7
...
@@ -8,6 +8,7 @@ package com.dlink.constant;
...
@@ -8,6 +8,7 @@ package com.dlink.constant;
**/
**/
public
final
class
FlinkRestResultConstant
{
public
final
class
FlinkRestResultConstant
{
public
static
final
String
ERRORS
=
"errors"
;
public
static
final
String
JOB_DURATION
=
"duration"
;
public
static
final
String
JOB_DURATION
=
"duration"
;
public
static
final
String
JOB_STATE
=
"state"
;
public
static
final
String
JOB_STATE
=
"state"
;
...
...
dlink-web/src/components/Common/JobStatus.tsx
View file @
a82923c7
...
@@ -12,22 +12,51 @@ export type JobStatusFormProps = {
...
@@ -12,22 +12,51 @@ export type JobStatusFormProps = {
status
:
string
|
undefined
;
status
:
string
|
undefined
;
};
};
export
const
JOB_STATUS
=
{
FINISHED
:
'FINISHED'
,
RUNNING
:
'RUNNING'
,
FAILED
:
'FAILED'
,
CANCELED
:
'CANCELED'
,
INITIALIZING
:
'INITIALIZING'
,
RESTARTING
:
'RESTARTING'
,
CREATED
:
'CREATED'
,
FAILING
:
'FAILING'
,
SUSPENDED
:
'SUSPENDED'
,
CANCELLING
:
'CANCELLING'
,
UNKNOWN
:
'UNKNOWN'
,
};
export
function
isStatusDone
(
type
:
string
){
if
(
!
type
){
return
true
;
}
switch
(
type
)
{
case
JOB_STATUS
.
FAILED
:
case
JOB_STATUS
.
CANCELED
:
case
JOB_STATUS
.
FINISHED
:
case
JOB_STATUS
.
UNKNOWN
:
return
true
;
default
:
return
false
;
}
};
const
JobStatus
=
(
props
:
JobStatusFormProps
)
=>
{
const
JobStatus
=
(
props
:
JobStatusFormProps
)
=>
{
const
{
status
}
=
props
;
const
{
status
}
=
props
;
return
(<>
return
(<>
{
(
status
===
'FINISHED'
)
?
{
(
status
===
'FINISHED'
)
?
(<
Tag
icon=
{
<
CheckCircleOutlined
/>
}
color=
"
success
"
>
(<
Tag
icon=
{
<
CheckCircleOutlined
/>
}
color=
"
blue
"
>
FINISHED
FINISHED
</
Tag
>)
:
(
status
===
'RUNNING'
)
?
</
Tag
>)
:
(
status
===
'RUNNING'
)
?
(<
Tag
icon=
{
<
SyncOutlined
spin
/>
}
color=
"
processing
"
>
(<
Tag
icon=
{
<
SyncOutlined
spin
/>
}
color=
"
green
"
>
RUNNING
RUNNING
</
Tag
>)
:
(
status
===
'FAILED'
)
?
</
Tag
>)
:
(
status
===
'FAILED'
)
?
(<
Tag
icon=
{
<
CloseCircleOutlined
/>
}
color=
"error"
>
(<
Tag
icon=
{
<
CloseCircleOutlined
/>
}
color=
"error"
>
FAILED
FAILED
</
Tag
>)
:
(
status
===
'CANCELED'
)
?
</
Tag
>)
:
(
status
===
'CANCELED'
)
?
(<
Tag
icon=
{
<
MinusCircleOutlined
/>
}
color=
"
default
"
>
(<
Tag
icon=
{
<
MinusCircleOutlined
/>
}
color=
"
orange
"
>
CANCELED
CANCELED
</
Tag
>)
:
(
status
===
'INITIALIZING'
)
?
</
Tag
>)
:
(
status
===
'INITIALIZING'
)
?
(<
Tag
icon=
{
<
ClockCircleOutlined
/>
}
color=
"default"
>
(<
Tag
icon=
{
<
ClockCircleOutlined
/>
}
color=
"default"
>
...
...
dlink-web/src/components/Studio/StudioEvent/DDL.ts
View file @
a82923c7
...
@@ -187,8 +187,8 @@ export function cancelJob(clusterId:number,jobId:string) {
...
@@ -187,8 +187,8 @@ export function cancelJob(clusterId:number,jobId:string) {
return
getData
(
'api/studio/cancel'
,{
clusterId
:
clusterId
,
jobId
:
jobId
});
return
getData
(
'api/studio/cancel'
,{
clusterId
:
clusterId
,
jobId
:
jobId
});
}
}
/*--- 停止 SavePoint Jobs ---*/
/*--- 停止 SavePoint Jobs ---*/
export
function
savepointJob
(
clusterId
:
number
,
jobId
:
string
,
savePointType
:
string
,
name
:
string
)
{
export
function
savepointJob
(
clusterId
:
number
,
jobId
:
string
,
savePointType
:
string
,
name
:
string
,
taskId
:
number
)
{
return
getData
(
'api/studio/savepoint'
,{
clusterId
,
jobId
,
savePointType
,
name
});
return
getData
(
'api/studio/savepoint'
,{
clusterId
,
jobId
,
savePointType
,
name
,
taskId
});
}
}
/*--- 根据版本号获取所有自动补全的文档 ---*/
/*--- 根据版本号获取所有自动补全的文档 ---*/
export
function
getFillAllByVersion
(
version
:
string
,
dispatch
:
any
)
{
export
function
getFillAllByVersion
(
version
:
string
,
dispatch
:
any
)
{
...
...
dlink-web/src/pages/DevOps/JobInfo/Config/index.tsx
View file @
a82923c7
...
@@ -3,7 +3,7 @@ import {
...
@@ -3,7 +3,7 @@ import {
RocketOutlined
RocketOutlined
}
from
'@ant-design/icons'
;
}
from
'@ant-design/icons'
;
const
{
Link
}
=
Typography
;
const
{
Text
,
Link
}
=
Typography
;
const
Config
=
(
props
:
any
)
=>
{
const
Config
=
(
props
:
any
)
=>
{
...
@@ -28,10 +28,22 @@ const Config = (props: any) => {
...
@@ -28,10 +28,22 @@ const Config = (props: any) => {
</
Descriptions
.
Item
>
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"片段机制"
>
{
job
?.
history
?.
config
.
useSqlFragment
?
'启用'
:
'禁用'
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"片段机制"
>
{
job
?.
history
?.
config
.
useSqlFragment
?
'启用'
:
'禁用'
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"语句集"
>
{
job
?.
history
?.
config
.
useStatementSet
?
'启用'
:
'禁用'
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"语句集"
>
{
job
?.
history
?.
config
.
useStatementSet
?
'启用'
:
'禁用'
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"任务类型"
>
{
job
?.
history
?.
config
.
isJarTask
?
'Jar'
:
'FlinkSQL'
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"批模式"
>
{
job
?.
history
?.
config
.
useBatchModel
?
'启用'
:
'禁用'
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"批模式"
>
{
job
?.
history
?.
config
.
useBatchModel
?
'启用'
:
'禁用'
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"CheckPoint"
>
{
job
?.
history
?.
config
.
checkpoint
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"SavePoint机制"
>
<
Descriptions
.
Item
label=
"SavePoint机制"
>
{
job
?.
history
?.
config
.
savePointStrategy
?
'启用'
:
'禁用'
}
{
job
?.
history
?.
config
.
savePointStrategy
==
'NONE'
?
'禁用'
:
job
?.
history
?.
config
.
savePointStrategy
==
'LATEST'
?
'最近一次'
:
job
?.
history
?.
config
.
savePointStrategy
==
'EARLIEST'
?
'最早一次'
:
job
?.
history
?.
config
.
savePointStrategy
==
'CUSTOM'
?
'指定一次'
:
'禁用'
}
</
Descriptions
.
Item
>
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"SavePoint"
span=
{
2
}
>
{
job
?.
history
?.
config
.
savePointPath
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"Flink Configuration"
span=
{
3
}
><
Text
code
>
{
JSON
.
stringify
(
job
?.
history
?.
config
.
config
)
}
</
Text
></
Descriptions
.
Item
>
{
job
?.
jar
?<>
<
Descriptions
.
Item
label=
"Jar 路径"
>
{
job
?.
jar
?.
path
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"Jar 主类"
>
{
job
?.
jar
?.
mainClass
}
</
Descriptions
.
Item
>
<
Descriptions
.
Item
label=
"Jar 入参"
>
{
job
?.
jar
?.
paras
}
</
Descriptions
.
Item
>
</>:
undefined
}
</
Descriptions
>
</
Descriptions
>
</>)
</>)
};
};
...
...
dlink-web/src/pages/DevOps/JobInfo/index.tsx
View file @
a82923c7
...
@@ -4,7 +4,7 @@ import {
...
@@ -4,7 +4,7 @@ import {
EllipsisOutlined
,
RedoOutlined
,
EllipsisOutlined
,
RedoOutlined
,
FireOutlined
,
ClusterOutlined
,
RocketOutlined
FireOutlined
,
ClusterOutlined
,
RocketOutlined
}
from
'@ant-design/icons'
;
}
from
'@ant-design/icons'
;
import
{
Button
,
Dropdown
,
Menu
,
Tag
,
Space
,
Typography
}
from
'antd'
;
import
{
Button
,
Dropdown
,
Menu
,
Tag
,
Space
,
Typography
,
message
,
Modal
}
from
'antd'
;
import
{
PageContainer
}
from
'@ant-design/pro-layout'
;
import
{
PageContainer
}
from
'@ant-design/pro-layout'
;
import
ProCard
from
'@ant-design/pro-card'
;
import
ProCard
from
'@ant-design/pro-card'
;
import
{
JobInfoDetail
}
from
"@/pages/DevOps/data"
;
import
{
JobInfoDetail
}
from
"@/pages/DevOps/data"
;
...
@@ -12,7 +12,8 @@ import {getJobInfoDetail, refreshJobInfoDetail} from "@/pages/DevOps/service";
...
@@ -12,7 +12,8 @@ import {getJobInfoDetail, refreshJobInfoDetail} from "@/pages/DevOps/service";
import
moment
from
"moment"
;
import
moment
from
"moment"
;
import
BaseInfo
from
"@/pages/DevOps/JobInfo/BaseInfo"
;
import
BaseInfo
from
"@/pages/DevOps/JobInfo/BaseInfo"
;
import
Config
from
"@/pages/DevOps/JobInfo/Config"
;
import
Config
from
"@/pages/DevOps/JobInfo/Config"
;
import
JobStatus
from
"@/components/Common/JobStatus"
;
import
JobStatus
,
{
isStatusDone
}
from
"@/components/Common/JobStatus"
;
import
{
cancelJob
,
savepointJob
}
from
"@/components/Studio/StudioEvent/DDL"
;
const
{
Link
}
=
Typography
;
const
{
Link
}
=
Typography
;
...
@@ -53,36 +54,88 @@ const JobInfo = (props: any) => {
...
@@ -53,36 +54,88 @@ const JobInfo = (props: any) => {
history
.
goBack
();
history
.
goBack
();
};
};
const
handleSavepoint
=
(
key
:
string
)
=>
{
if
(
key
==
'canceljob'
){
Modal
.
confirm
({
title
:
'停止任务'
,
content
:
`确定只停止该作业,不进行 SavePoint 操作吗?`
,
okText
:
'确认'
,
cancelText
:
'取消'
,
onOk
:
async
()
=>
{
if
(
!
job
?.
cluster
?.
id
)
return
;
const
res
=
cancelJob
(
job
?.
cluster
?.
id
,
job
?.
instance
?.
jid
);
res
.
then
((
result
)
=>
{
if
(
result
.
datas
==
true
)
{
message
.
success
(
key
+
"成功"
);
handleGetJobInfoDetail
();
}
else
{
message
.
error
(
key
+
"失败"
);
}
});
}
});
return
;
}
Modal
.
confirm
({
title
:
key
+
'任务'
,
content
:
`确定
${
key
}
该作业吗?`
,
okText
:
'确认'
,
cancelText
:
'取消'
,
onOk
:
async
()
=>
{
if
(
!
job
?.
cluster
?.
id
)
return
;
const
res
=
savepointJob
(
job
?.
cluster
?.
id
,
job
?.
instance
?.
jid
,
key
,
key
,
job
?.
instance
?.
taskId
);
res
.
then
((
result
)
=>
{
if
(
result
.
datas
==
true
)
{
message
.
success
(
key
+
"成功"
);
handleGetJobInfoDetail
();
}
else
{
message
.
error
(
key
+
"失败"
);
}
});
}
});
};
const
getButtons
=
()
=>
{
let
buttons
=
[
<
Button
key=
"back"
type=
"dashed"
onClick=
{
handleBack
}
>
返回
</
Button
>,
];
if
(
!
isStatusDone
(
job
?.
instance
?.
status
as
string
)){
buttons
.
push
(<
Button
key=
"refresh"
icon=
{
<
RedoOutlined
/>
}
onClick=
{
handleRefreshJobInfoDetail
}
/>);
buttons
.
push
(<
Button
key=
"flinkwebui"
>
<
Link
href=
{
`http://${job?.history?.jobManagerAddress}/#/job/${job?.instance?.jid}/overview`
}
target=
"_blank"
>
FlinkWebUI
</
Link
></
Button
>);
}
buttons
.
push
(<
Button
key=
"autorestart"
type=
"primary"
>
智能重启
</
Button
>);
if
(
!
isStatusDone
(
job
?.
instance
?.
status
as
string
)){
buttons
.
push
(<
Button
key=
"autostop"
type=
"primary"
danger
onClick=
{
()
=>
{
handleSavepoint
(
'cancel'
)}
}
>
智能停止
</
Button
>);
buttons
.
push
(<
Dropdown
key=
"dropdown"
trigger=
{
[
'click'
]
}
overlay=
{
<
Menu
onClick=
{
({
key
})
=>
handleSavepoint
(
key
)
}
>
<
Menu
.
Item
key=
"trigger"
>
SavePoint触发
</
Menu
.
Item
>
<
Menu
.
Item
key=
"stop"
>
SavePoint暂停
</
Menu
.
Item
>
<
Menu
.
Item
key=
"cancel"
>
SavePoint停止
</
Menu
.
Item
>
<
Menu
.
Item
key=
"canceljob"
>
普通停止
</
Menu
.
Item
>
</
Menu
>
}
>
<
Button
key=
"4"
style=
{
{
padding
:
'0 8px'
}
}
>
<
EllipsisOutlined
/>
</
Button
>
</
Dropdown
>);
}
return
buttons
;
}
return
(
return
(
<
PageContainer
<
PageContainer
header=
{
{
header=
{
{
title
:
`${job?.instance?.name}`
,
title
:
`${job?.instance?.name}`
,
ghost
:
true
,
ghost
:
true
,
extra
:
[
extra
:
getButtons
(),
<
Button
key=
"back"
type=
"dashed"
onClick=
{
handleBack
}
>
返回
</
Button
>,
<
Button
key=
"refresh"
icon=
{
<
RedoOutlined
/>
}
onClick=
{
handleRefreshJobInfoDetail
}
/>,
<
Button
key=
"flinkwebui"
>
<
Link
href=
{
`http://${job?.history?.jobManagerAddress}/#/job/${job?.instance?.jid}/overview`
}
target=
"_blank"
>
FlinkWebUI
</
Link
></
Button
>,
<
Button
key=
"autorestart"
type=
"primary"
>
智能重启
</
Button
>,
<
Button
key=
"autostop"
type=
"primary"
danger
>
智能停止
</
Button
>,
<
Dropdown
key=
"dropdown"
trigger=
{
[
'click'
]
}
overlay=
{
<
Menu
>
<
Menu
.
Item
key=
"1"
>
普通停止
</
Menu
.
Item
>
<
Menu
.
Item
key=
"2"
>
SavePoint停止
</
Menu
.
Item
>
<
Menu
.
Item
key=
"3"
>
SavePoint暂停
</
Menu
.
Item
>
</
Menu
>
}
>
<
Button
key=
"4"
style=
{
{
padding
:
'0 8px'
}
}
>
<
EllipsisOutlined
/>
</
Button
>
</
Dropdown
>,
],
}
}
}
}
content=
{
<>
content=
{
<>
<
Space
size=
{
0
}
>
<
Space
size=
{
0
}
>
...
...
dlink-web/src/pages/DevOps/JobInstanceTable/index.tsx
View file @
a82923c7
...
@@ -11,6 +11,7 @@ import ProTable from "@ant-design/pro-table";
...
@@ -11,6 +11,7 @@ import ProTable from "@ant-design/pro-table";
import
{
JobInstanceTableListItem
}
from
"@/pages/DevOps/data"
;
import
{
JobInstanceTableListItem
}
from
"@/pages/DevOps/data"
;
import
moment
from
'moment'
;
import
moment
from
'moment'
;
import
{
RUN_MODE
}
from
"@/components/Studio/conf"
;
import
{
RUN_MODE
}
from
"@/components/Studio/conf"
;
import
JobStatus
from
"@/components/Common/JobStatus"
;
const
url
=
'/api/jobInstance'
;
const
url
=
'/api/jobInstance'
;
const
JobInstanceTable
=
(
props
:
any
)
=>
{
const
JobInstanceTable
=
(
props
:
any
)
=>
{
...
@@ -74,34 +75,7 @@ const JobInstanceTable = (props: any) => {
...
@@ -74,34 +75,7 @@ const JobInstanceTable = (props: any) => {
hideInSearch
:
true
,
hideInSearch
:
true
,
render
:
(
_
,
row
)
=>
{
render
:
(
_
,
row
)
=>
{
return
(
return
(
<>
<
JobStatus
status=
{
row
.
status
}
/>)
{
(
row
.
status
==
'FINISHED'
)
?
(<
Tag
icon=
{
<
CheckCircleOutlined
/>
}
color=
"success"
>
FINISHED
</
Tag
>)
:
(
row
.
status
==
'RUNNING'
)
?
(<
Tag
icon=
{
<
SyncOutlined
spin
/>
}
color=
"processing"
>
RUNNING
</
Tag
>)
:
(
row
.
status
==
'FAILED'
)
?
(<
Tag
icon=
{
<
CloseCircleOutlined
/>
}
color=
"error"
>
FAILED
</
Tag
>)
:
(
row
.
status
==
'CANCELED'
)
?
(<
Tag
icon=
{
<
MinusCircleOutlined
/>
}
color=
"default"
>
CANCELED
</
Tag
>)
:
(
row
.
status
==
'INITIALIZING'
)
?
(<
Tag
icon=
{
<
ClockCircleOutlined
/>
}
color=
"default"
>
INITIALIZING
</
Tag
>)
:(
row
.
status
==
'RESTARTING'
)
?
(<
Tag
icon=
{
<
ClockCircleOutlined
/>
}
color=
"default"
>
RESTARTING
</
Tag
>)
:
(<
Tag
color=
"default"
>
UNKNOWEN
</
Tag
>)
}
</>)
;
;
}
}
},
{
},
{
...
...
dlink-web/src/pages/DevOps/data.d.ts
View file @
a82923c7
import
{
ClusterTableListItem
}
from
"@/pages/Cluster/data"
;
import
{
ClusterTableListItem
}
from
"@/pages/Cluster/data"
;
import
{
ClusterConfigurationTableListItem
}
from
"@/pages/ClusterConfiguration/data"
;
import
{
ClusterConfigurationTableListItem
}
from
"@/pages/ClusterConfiguration/data"
;
import
{
HistoryItem
}
from
"@/components/Studio/StudioConsole/StudioHistory/data"
;
import
{
HistoryItem
}
from
"@/components/Studio/StudioConsole/StudioHistory/data"
;
import
{
JarTableListItem
}
from
"@/pages/Jar/data"
;
export
type
JobInstanceTableListItem
=
{
export
type
JobInstanceTableListItem
=
{
id
:
number
,
id
:
number
,
...
@@ -28,6 +29,13 @@ export type StatusCount = {
...
@@ -28,6 +29,13 @@ export type StatusCount = {
finished
:
number
,
finished
:
number
,
failed
:
number
,
failed
:
number
,
canceled
:
number
,
canceled
:
number
,
restarting
:
number
,
created
:
number
,
failing
:
number
,
cancelling
:
number
,
suspended
:
number
,
reconciling
:
number
,
unknown
:
number
,
}
}
export
type
JobInfoDetail
=
{
export
type
JobInfoDetail
=
{
...
@@ -35,8 +43,8 @@ export type JobInfoDetail = {
...
@@ -35,8 +43,8 @@ export type JobInfoDetail = {
instance
:
JobInstanceTableListItem
,
instance
:
JobInstanceTableListItem
,
cluster
:
ClusterTableListItem
,
cluster
:
ClusterTableListItem
,
clusterConfiguration
:
ClusterConfigurationTableListItem
,
clusterConfiguration
:
ClusterConfigurationTableListItem
,
task
:
TaskTableList
Item
,
history
:
History
Item
,
history
:
History
Item
jar
:
JarTableList
Item
}
}
export
type
VerticesTableListItem
=
{
export
type
VerticesTableListItem
=
{
...
@@ -47,5 +55,5 @@ export type VerticesTableListItem = {
...
@@ -47,5 +55,5 @@ export type VerticesTableListItem = {
startTime
:
string
,
startTime
:
string
,
duration
:
number
,
duration
:
number
,
endTime
:
string
,
endTime
:
string
,
tasks
:
any
,
tasks
:
any
,
}
}
dlink-web/src/pages/DevOps/index.tsx
View file @
a82923c7
...
@@ -5,6 +5,7 @@ import JobInstanceTable from "./JobInstanceTable";
...
@@ -5,6 +5,7 @@ import JobInstanceTable from "./JobInstanceTable";
import
{
getStatusCount
}
from
"@/pages/DevOps/service"
;
import
{
getStatusCount
}
from
"@/pages/DevOps/service"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
StatusCount
}
from
"@/pages/DevOps/data"
;
import
{
StatusCount
}
from
"@/pages/DevOps/data"
;
import
{
JOB_STATUS
}
from
"@/components/Common/JobStatus"
;
const
{
Statistic
}
=
StatisticCard
;
const
{
Statistic
}
=
StatisticCard
;
...
@@ -13,11 +14,17 @@ const DevOps = (props:any) => {
...
@@ -13,11 +14,17 @@ const DevOps = (props:any) => {
// const {current} = props;
// const {current} = props;
const
statusCountDefault
=
[
const
statusCountDefault
=
[
{
key
:
''
,
title
:
'全部'
,
value
:
0
,
total
:
true
},
{
key
:
''
,
title
:
'全部'
,
value
:
0
,
total
:
true
},
{
key
:
'INITIALIZING'
,
status
:
'default'
,
title
:
'初始化'
,
value
:
0
},
{
key
:
JOB_STATUS
.
INITIALIZING
,
status
:
'default'
,
title
:
'初始化'
,
value
:
0
},
{
key
:
'RUNNING'
,
status
:
'success'
,
title
:
'运行中'
,
value
:
0
},
{
key
:
JOB_STATUS
.
CREATED
,
status
:
'default'
,
title
:
'创建中'
,
value
:
0
},
{
key
:
'FINISHED'
,
status
:
'processing'
,
title
:
'已完成'
,
value
:
0
},
{
key
:
JOB_STATUS
.
RUNNING
,
status
:
'success'
,
title
:
'运行中'
,
value
:
0
},
{
key
:
'FAILED'
,
status
:
'error'
,
title
:
'发生异常'
,
value
:
0
},
{
key
:
JOB_STATUS
.
FINISHED
,
status
:
'processing'
,
title
:
'已完成'
,
value
:
0
},
{
key
:
'CANCELED'
,
status
:
'warning'
,
title
:
'停止'
,
value
:
0
},
{
key
:
JOB_STATUS
.
FAILING
,
status
:
'error'
,
title
:
'异常中'
,
value
:
0
},
{
key
:
JOB_STATUS
.
FAILED
,
status
:
'error'
,
title
:
'已异常'
,
value
:
0
},
{
key
:
JOB_STATUS
.
SUSPENDED
,
status
:
'warning'
,
title
:
'已暂停'
,
value
:
0
},
{
key
:
JOB_STATUS
.
CANCELLING
,
status
:
'warning'
,
title
:
'停止中'
,
value
:
0
},
{
key
:
JOB_STATUS
.
CANCELED
,
status
:
'warning'
,
title
:
'已停止'
,
value
:
0
},
{
key
:
JOB_STATUS
.
RESTARTING
,
status
:
'default'
,
title
:
'重启中'
,
value
:
0
},
{
key
:
JOB_STATUS
.
UNKNOWN
,
status
:
'default'
,
title
:
'未知'
,
value
:
0
},
];
];
const
[
statusCount
,
setStatusCount
]
=
useState
<
any
[]
>
(
statusCountDefault
);
const
[
statusCount
,
setStatusCount
]
=
useState
<
any
[]
>
(
statusCountDefault
);
const
[
activeKey
,
setActiveKey
]
=
useState
<
string
>
(
''
);
const
[
activeKey
,
setActiveKey
]
=
useState
<
string
>
(
''
);
...
@@ -28,11 +35,17 @@ const DevOps = (props:any) => {
...
@@ -28,11 +35,17 @@ const DevOps = (props:any) => {
const
statusCountData
:
StatusCount
=
result
.
datas
;
const
statusCountData
:
StatusCount
=
result
.
datas
;
const
items
:
any
=
[
const
items
:
any
=
[
{
key
:
''
,
title
:
'全部'
,
value
:
statusCountData
.
all
,
total
:
true
},
{
key
:
''
,
title
:
'全部'
,
value
:
statusCountData
.
all
,
total
:
true
},
{
key
:
'INITIALIZING'
,
status
:
'default'
,
title
:
'初始化'
,
value
:
statusCountData
.
initializing
},
{
key
:
JOB_STATUS
.
INITIALIZING
,
status
:
'default'
,
title
:
'初始化'
,
value
:
statusCountData
.
initializing
},
{
key
:
'RUNNING'
,
status
:
'success'
,
title
:
'运行中'
,
value
:
statusCountData
.
running
},
{
key
:
JOB_STATUS
.
CREATED
,
status
:
'default'
,
title
:
'创建中'
,
value
:
statusCountData
.
created
},
{
key
:
'FINISHED'
,
status
:
'processing'
,
title
:
'已完成'
,
value
:
statusCountData
.
finished
},
{
key
:
JOB_STATUS
.
RUNNING
,
status
:
'success'
,
title
:
'运行中'
,
value
:
statusCountData
.
running
},
{
key
:
'FAILED'
,
status
:
'error'
,
title
:
'发生异常'
,
value
:
statusCountData
.
failed
},
{
key
:
JOB_STATUS
.
FINISHED
,
status
:
'processing'
,
title
:
'已完成'
,
value
:
statusCountData
.
finished
},
{
key
:
'CANCELED'
,
status
:
'warning'
,
title
:
'停止'
,
value
:
statusCountData
.
canceled
},
{
key
:
JOB_STATUS
.
FAILING
,
status
:
'error'
,
title
:
'异常中'
,
value
:
statusCountData
.
failing
},
{
key
:
JOB_STATUS
.
FAILED
,
status
:
'error'
,
title
:
'已异常'
,
value
:
statusCountData
.
failed
},
{
key
:
JOB_STATUS
.
SUSPENDED
,
status
:
'warning'
,
title
:
'已暂停'
,
value
:
statusCountData
.
suspended
},
{
key
:
JOB_STATUS
.
CANCELLING
,
status
:
'warning'
,
title
:
'停止中'
,
value
:
statusCountData
.
cancelling
},
{
key
:
JOB_STATUS
.
CANCELED
,
status
:
'warning'
,
title
:
'停止'
,
value
:
statusCountData
.
canceled
},
{
key
:
JOB_STATUS
.
RESTARTING
,
status
:
'default'
,
title
:
'重启中'
,
value
:
statusCountData
.
restarting
},
{
key
:
JOB_STATUS
.
UNKNOWN
,
status
:
'default'
,
title
:
'未知'
,
value
:
statusCountData
.
unknown
},
];
];
setStatusCount
(
items
);
setStatusCount
(
items
);
});
});
...
@@ -64,7 +77,7 @@ const DevOps = (props:any) => {
...
@@ -64,7 +77,7 @@ const DevOps = (props:any) => {
title=
{
item
.
title
}
title=
{
item
.
title
}
value=
{
item
.
value
}
value=
{
item
.
value
}
status=
{
item
.
status
as
StatisticProps
[
'status'
]
}
status=
{
item
.
status
as
StatisticProps
[
'status'
]
}
style=
{
{
width
:
12
0
,
borderRight
:
item
.
total
?
'1px solid #f0f0f0'
:
undefined
}
}
style=
{
{
width
:
8
0
,
borderRight
:
item
.
total
?
'1px solid #f0f0f0'
:
undefined
}
}
/>
/>
}
}
>
>
...
...
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