Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
Bacula Community Edition
Bacula Community
Commits
ea4363a0
Commit
ea4363a0
authored
Nov 08, 2022
by
Marcin Haba
Browse files
baculum: Add time range parameters to jobs endpoint
parent
95ad1fd7
Changes
8
Hide whitespace changes
Inline
Side-by-side
gui/baculum/protected/API/Modules/Database.php
View file @
ea4363a0
...
...
@@ -161,35 +161,41 @@ class Database extends APIModule {
$where
=
''
;
$parameters
=
array
();
if
(
count
(
$params
)
>
0
)
{
$condition
=
array
()
;
$condition
=
[]
;
foreach
(
$params
as
$key
=>
$value
)
{
$cond
=
array
();
$vals
=
array
();
$kval
=
str_replace
(
'.'
,
'_'
,
$key
);
if
(
is_array
(
$value
[
'vals'
]))
{
if
(
$value
[
'operator'
]
==
'IN'
)
{
// IN operator is treated separately
$tcond
=
[];
for
(
$i
=
0
;
$i
<
count
(
$value
[
'vals'
]);
$i
++
)
{
$tcond
[]
=
":
{
$kval
}{
$i
}
"
;
$vals
[
":
{
$kval
}{
$i
}
"
]
=
$value
[
'vals'
][
$i
];
for
(
$i
=
0
;
$i
<
count
(
$value
);
$i
++
)
{
$cond
=
[];
$vals
=
[];
$kval
=
str_replace
(
'.'
,
'_'
,
$key
);
if
(
is_array
(
$value
[
$i
][
'vals'
]))
{
if
(
$value
[
$i
][
'operator'
]
==
'IN'
)
{
// IN operator is treated separately
$tcond
=
[];
for
(
$j
=
0
;
$j
<
count
(
$value
[
$i
][
'vals'
]);
$j
++
)
{
$tcond
[]
=
":
{
$kval
}{
$i
}{
$j
}
"
;
$vals
[
":
{
$kval
}{
$i
}{
$j
}
"
]
=
$value
[
$i
][
'vals'
][
$j
];
}
$cond
[]
=
"
{
$key
}
{
$value
[
$i
][
'operator'
]
}
("
.
implode
(
','
,
$tcond
)
.
')'
;
}
else
{
// other operators
for
(
$j
=
0
;
$j
<
count
(
$value
[
$i
][
'vals'
]);
$j
++
)
{
$cond
[]
=
"
{
$key
}
= :
{
$kval
}{
$i
}{
$j
}
"
;
$vals
[
":
{
$kval
}{
$i
}{
$j
}
"
]
=
$value
[
$i
][
'vals'
][
$j
];
}
}
$cond
[]
=
"
{
$key
}
{
$value
[
'operator'
]
}
("
.
implode
(
','
,
$tcond
)
.
')'
;
}
elseif
(
isset
(
$value
[
$i
][
'operator'
])
&&
in_array
(
$value
[
$i
][
'operator'
],
[
'>'
,
'<'
,
'>='
,
'<='
]))
{
$cond
[]
=
"
{
$key
}
{
$value
[
$i
][
'operator'
]
}
:
{
$kval
}{
$i
}
"
;
$vals
[
":
{
$kval
}{
$i
}
"
]
=
$value
[
$i
][
'vals'
];
$value
[
$i
][
'operator'
]
=
''
;
}
else
{
// other operators
for
(
$i
=
0
;
$i
<
count
(
$value
[
'vals'
]);
$i
++
)
{
$cond
[]
=
"
{
$key
}
= :
{
$kval
}{
$i
}
"
;
$vals
[
":
{
$kval
}{
$i
}
"
]
=
$value
[
'vals'
][
$i
];
}
$cond
[]
=
"
$key
= :
{
$kval
}{
$i
}
"
;
$vals
[
":
{
$kval
}{
$i
}
"
]
=
$value
[
$i
][
'vals'
];
$value
[
$i
][
'operator'
]
=
''
;
}
$condition
[]
=
implode
(
' '
.
$value
[
$i
][
'operator'
]
.
' '
,
$cond
);
foreach
(
$vals
as
$pkey
=>
$pval
)
{
$parameters
[
$pkey
]
=
$pval
;
}
}
else
{
$cond
[]
=
"
$key
= :
$kval
"
;
$vals
[
":
$kval
"
]
=
$value
[
'vals'
];
$value
[
'operator'
]
=
''
;
}
$condition
[]
=
implode
(
' '
.
$value
[
'operator'
]
.
' '
,
$cond
);
foreach
(
$vals
as
$pkey
=>
$pval
)
{
$parameters
[
$pkey
]
=
$pval
;
}
}
if
(
count
(
$condition
)
>
0
)
{
...
...
gui/baculum/protected/API/Modules/JobManager.php
View file @
ea4363a0
...
...
@@ -63,10 +63,10 @@ LEFT JOIN FileSet USING (FilesetId)'
public
function
getJobById
(
$jobid
)
{
$job
=
$this
->
getJobs
(
array
(
'Job.JobId'
=>
array
(
'vals'
=>
array
(
$jobid
)
,
'Job.JobId'
=>
[[
'vals'
=>
[
$jobid
]
,
'operator'
=>
'AND'
)
]]
),
1
);
if
(
is_array
(
$job
)
&&
count
(
$job
)
>
0
)
{
$job
=
array_shift
(
$job
);
...
...
@@ -231,10 +231,10 @@ WHERE JobMedia.MediaId='$mediaid' $jobs_criteria";
$where
=
''
;
if
(
count
(
$allowed_jobs
)
>
0
)
{
$criteria
=
[
'Job.Name'
=>
[
'Job.Name'
=>
[
[
'vals'
=>
$allowed_jobs
,
'operator'
=>
'OR'
]
]
]
];
$where
=
Database
::
getWhere
(
$criteria
,
true
);
$wh
=
''
;
...
...
gui/baculum/protected/API/Modules/ObjectManager.php
View file @
ea4363a0
...
...
@@ -56,10 +56,9 @@ LEFT JOIN Job USING (JobId) '
public
function
getObjectById
(
$objectid
)
{
$params
=
[
'Object.ObjectId'
=>
[
'vals'
=>
$objectid
,
'operator'
=>
''
]
'Object.ObjectId'
=>
[[
'vals'
=>
$objectid
]]
];
$obj
=
$this
->
getObjects
(
$params
,
1
);
if
(
is_array
(
$obj
)
&&
count
(
$obj
)
>
0
)
{
...
...
gui/baculum/protected/API/Modules/VolumeManager.php
View file @
ea4363a0
...
...
@@ -66,10 +66,10 @@ LEFT JOIN Storage USING (StorageId)
public
function
getVolumesByPoolId
(
$poolid
)
{
$volumes
=
$this
->
getVolumes
(
array
(
'Media.PoolId'
=>
array
(
'vals'
=>
array
(
$poolid
)
,
'Media.PoolId'
=>
[[
'vals'
=>
[
$poolid
]
,
'operator'
=>
'AND'
)
]]
));
$this
->
setExtraVariables
(
$volumes
);
return
$volumes
;
...
...
@@ -77,10 +77,10 @@ LEFT JOIN Storage USING (StorageId)
public
function
getVolumeByPoolId
(
$poolid
)
{
$volume
=
$this
->
getVolumes
(
array
(
'Media.PoolId'
=>
array
(
'vals'
=>
array
(
$poolid
)
,
'Media.PoolId'
=>
[[
'vals'
=>
[
$poolid
]
,
'operator'
=>
'AND'
)
]]
),
1
);
if
(
is_array
(
$volume
)
&&
count
(
$volume
)
>
0
)
{
$volume
=
array_shift
(
$volume
);
...
...
@@ -91,10 +91,10 @@ LEFT JOIN Storage USING (StorageId)
public
function
getVolumeByName
(
$volume_name
)
{
$volume
=
$this
->
getVolumes
(
array
(
'Media.VolumeName'
=>
array
(
'vals'
=>
array
(
$volume_name
)
,
'Media.VolumeName'
=>
[[
'vals'
=>
[
$volume_name
]
,
'operator'
=>
'AND'
)
]]
),
1
);
if
(
is_array
(
$volume
)
&&
count
(
$volume
)
>
0
)
{
$volume
=
array_shift
(
$volume
);
...
...
@@ -105,10 +105,10 @@ LEFT JOIN Storage USING (StorageId)
public
function
getVolumeById
(
$volume_id
)
{
$volume
=
$this
->
getVolumes
(
array
(
'Media.MediaId'
=>
array
(
'vals'
=>
array
(
$volume_id
)
,
'Media.MediaId'
=>
[[
'vals'
=>
[
$volume_id
]
,
'operator'
=>
'AND'
)
]]
));
if
(
is_array
(
$volume
)
&&
count
(
$volume
)
>
0
)
{
$volume
=
array_shift
(
$volume
);
...
...
gui/baculum/protected/API/Pages/API/Events.php
View file @
ea4363a0
...
...
@@ -45,22 +45,34 @@ class Events extends BaculumAPIServer {
$params
=
$time_scope
=
[];
if
(
!
empty
(
$eventscode
))
{
$params
[
'Events.EventsCode'
][
'vals'
]
=
$eventscode
;
$params
[
'Events.EventsCode'
]
=
[[
'vals'
=>
$eventscode
]];
}
if
(
!
empty
(
$eventstype
))
{
$params
[
'Events.EventsType'
][
'vals'
]
=
$eventstype
;
$params
[
'Events.EventsType'
]
=
[[
'vals'
=>
$eventstype
]];
}
if
(
!
empty
(
$eventsdaemon
))
{
$params
[
'Events.EventsDaemon'
][
'vals'
]
=
$eventsdaemon
;
$params
[
'Events.EventsDaemon'
]
=
[[
'vals'
=>
$eventsdaemon
]];
}
if
(
!
empty
(
$eventssource
))
{
$params
[
'Events.EventsSource'
][
'vals'
]
=
$eventssource
;
$params
[
'Events.EventsSource'
]
=
[[
'vals'
=>
$eventssource
]];
}
if
(
!
empty
(
$eventsref
))
{
$params
[
'Events.EventsRef'
][
'vals'
]
=
$eventsref
;
$params
[
'Events.EventsRef'
]
=
[[
'vals'
=>
$eventsref
]];
}
if
(
!
empty
(
$eventstext
))
{
$params
[
'Events.EventsText'
][
'vals'
]
=
$eventstext
;
$params
[
'Events.EventsText'
]
=
[[
'vals'
=>
$eventstext
]];
}
if
(
!
empty
(
$eventstimestart
))
{
$time_scope
[
'eventstimestart'
]
=
$eventstimestart
;
...
...
gui/baculum/protected/API/Pages/API/Jobs.php
View file @
ea4363a0
...
...
@@ -39,6 +39,14 @@ class Jobs extends BaculumAPIServer {
$type
=
$this
->
Request
->
contains
(
'type'
)
&&
$misc
->
isValidJobType
(
$this
->
Request
[
'type'
])
?
$this
->
Request
[
'type'
]
:
''
;
$jobname
=
$this
->
Request
->
contains
(
'name'
)
&&
$misc
->
isValidName
(
$this
->
Request
[
'name'
])
?
$this
->
Request
[
'name'
]
:
''
;
$clientid
=
$this
->
Request
->
contains
(
'clientid'
)
?
$this
->
Request
[
'clientid'
]
:
''
;
$schedtime_from
=
$this
->
Request
->
contains
(
'schedtime_from'
)
&&
$misc
->
isValidInteger
(
$this
->
Request
[
'schedtime_from'
])
?
(
int
)
$this
->
Request
[
'schedtime_from'
]
:
null
;
$schedtime_to
=
$this
->
Request
->
contains
(
'schedtime_to'
)
&&
$misc
->
isValidInteger
(
$this
->
Request
[
'schedtime_to'
])
?
(
int
)
$this
->
Request
[
'schedtime_to'
]
:
null
;
$starttime_from
=
$this
->
Request
->
contains
(
'starttime_from'
)
&&
$misc
->
isValidInteger
(
$this
->
Request
[
'starttime_from'
])
?
(
int
)
$this
->
Request
[
'starttime_from'
]
:
null
;
$starttime_to
=
$this
->
Request
->
contains
(
'starttime_to'
)
&&
$misc
->
isValidInteger
(
$this
->
Request
[
'starttime_to'
])
?
(
int
)
$this
->
Request
[
'starttime_to'
]
:
null
;
$endtime_from
=
$this
->
Request
->
contains
(
'endtime_from'
)
&&
$misc
->
isValidInteger
(
$this
->
Request
[
'endtime_from'
])
?
(
int
)
$this
->
Request
[
'endtime_from'
]
:
null
;
$endtime_to
=
$this
->
Request
->
contains
(
'endtime_to'
)
&&
$misc
->
isValidInteger
(
$this
->
Request
[
'endtime_to'
])
?
(
int
)
$this
->
Request
[
'endtime_to'
]
:
null
;
$realendtime_from
=
$this
->
Request
->
contains
(
'realendtime_from'
)
&&
$misc
->
isValidInteger
(
$this
->
Request
[
'realendtime_from'
])
?
(
int
)
$this
->
Request
[
'realendtime_from'
]
:
null
;
$realendtime_to
=
$this
->
Request
->
contains
(
'realendtime_to'
)
&&
$misc
->
isValidInteger
(
$this
->
Request
[
'realendtime_to'
])
?
(
int
)
$this
->
Request
[
'realendtime_to'
]
:
null
;
if
(
!
empty
(
$clientid
)
&&
!
$misc
->
isValidId
(
$clientid
))
{
$this
->
output
=
JobError
::
MSG_ERROR_CLIENT_DOES_NOT_EXISTS
;
...
...
@@ -56,23 +64,100 @@ class Jobs extends BaculumAPIServer {
$params
=
[];
$jobstatuses
=
array_keys
(
$misc
->
getJobState
());
$sts
=
str_split
(
$jobstatus
);
$counter
=
0
;
for
(
$i
=
0
;
$i
<
count
(
$sts
);
$i
++
)
{
if
(
in_array
(
$sts
[
$i
],
$jobstatuses
))
{
if
(
!
key_exists
(
'Job.JobStatus'
,
$params
))
{
$params
[
'Job.JobStatus'
]
=
array
(
'operator'
=>
'OR'
,
'vals'
=>
array
());
$params
[
'Job.JobStatus'
][
$counter
]
=
[
'operator'
=>
'OR'
,
'vals'
=>
[]
];
}
$params
[
'Job.JobStatus'
][
'vals'
][]
=
$sts
[
$i
];
$params
[
'Job.JobStatus'
][
$counter
][
'vals'
][]
=
$sts
[
$i
];
$counter
++
;
}
}
if
(
!
empty
(
$level
))
{
$params
[
'Job.Level'
][
'operator'
]
=
''
;
$params
[
'Job.Level'
][
'vals'
]
=
$level
;
$params
[
'Job.Level'
]
=
[];
$params
[
'Job.Level'
][]
=
[
'vals'
=>
$level
];
}
if
(
!
empty
(
$type
))
{
$params
[
'Job.Type'
][
'operator'
]
=
''
;
$params
[
'Job.Type'
][
'vals'
]
=
$type
;
$params
[
'Job.Type'
]
=
[];
$params
[
'Job.Type'
][]
=
[
'vals'
=>
$type
];
}
$allowed
=
[];
// Scheduled time range
if
(
!
empty
(
$schedtime_from
)
||
!
empty
(
$schedtime_to
))
{
$params
[
'Job.SchedTime'
]
=
[];
if
(
!
empty
(
$schedtime_from
))
{
$params
[
'Job.SchedTime'
][]
=
[
'operator'
=>
'>='
,
'vals'
=>
date
(
'Y-m-d H:m:s'
,
$schedtime_from
)
];
}
if
(
!
empty
(
$schedtime_to
))
{
$params
[
'Job.SchedTime'
][]
=
[
'operator'
=>
'<='
,
'vals'
=>
date
(
'Y-m-d H:m:s'
,
$schedtime_to
)
];
}
}
// Start time range
if
(
!
empty
(
$starttime_from
)
||
!
empty
(
$starttime_to
))
{
$params
[
'Job.StartTime'
]
=
[];
if
(
!
empty
(
$starttime_from
))
{
$params
[
'Job.StartTime'
][]
=
[
'operator'
=>
'>='
,
'vals'
=>
date
(
'Y-m-d H:m:s'
,
$starttime_from
)
];
}
if
(
!
empty
(
$starttime_to
))
{
$params
[
'Job.StartTime'
][]
=
[
'operator'
=>
'<='
,
'vals'
=>
date
(
'Y-m-d H:m:s'
,
$starttime_to
)
];
}
}
// End time range
if
(
!
empty
(
$endtime_from
)
||
!
empty
(
$endtime_to
))
{
$params
[
'Job.EndTime'
]
=
[];
if
(
!
empty
(
$endtime_from
))
{
$params
[
'Job.EndTime'
][]
=
[
'operator'
=>
'>='
,
'vals'
=>
date
(
'Y-m-d H:m:s'
,
$endtime_from
)
];
}
if
(
!
empty
(
$endtime_to
))
{
$params
[
'Job.EndTime'
][]
=
[
'operator'
=>
'<='
,
'vals'
=>
date
(
'Y-m-d H:m:s'
,
$endtime_to
)
];
}
}
// Real end time range
if
(
!
empty
(
$realendtime_from
)
||
!
empty
(
$realendtime_to
))
{
$params
[
'Job.RealEndTime'
]
=
[];
if
(
!
empty
(
$realendtime_from
))
{
$params
[
'Job.RealEndTime'
][]
=
[
'operator'
=>
'>='
,
'vals'
=>
date
(
'Y-m-d H:m:s'
,
$realendtime_from
)
];
}
if
(
!
empty
(
$realendtime_to
))
{
$params
[
'Job.RealEndTime'
][]
=
[
'operator'
=>
'<='
,
'vals'
=>
date
(
'Y-m-d H:m:s'
,
$realendtime_to
)
];
}
}
$result
=
$this
->
getModule
(
'bconsole'
)
->
bconsoleCommand
(
$this
->
director
,
[
'.jobs'
],
...
...
@@ -93,8 +178,11 @@ class Jobs extends BaculumAPIServer {
return
;
}
$params
[
'Job.Name'
][
'operator'
]
=
'OR'
;
$params
[
'Job.Name'
][
'vals'
]
=
$vals
;
$params
[
'Job.Name'
]
=
[];
$params
[
'Job.Name'
][]
=
[
'operator'
=>
'OR'
,
'vals'
=>
$vals
];
$error
=
false
;
// Client name and clientid filter
...
...
@@ -109,8 +197,11 @@ class Jobs extends BaculumAPIServer {
$cli
=
$this
->
getModule
(
'client'
)
->
getClientById
(
$clientid
);
}
if
(
is_object
(
$cli
)
&&
in_array
(
$cli
->
name
,
$result
->
output
))
{
$params
[
'Job.ClientId'
][
'operator'
]
=
'AND'
;
$params
[
'Job.ClientId'
][
'vals'
]
=
[
$cli
->
clientid
];
$params
[
'Job.ClientId'
]
=
[];
$params
[
'Job.ClientId'
][]
=
[
'operator'
=>
'AND'
,
'vals'
=>
[
$cli
->
clientid
]
];
}
else
{
$error
=
true
;
$this
->
output
=
JobError
::
MSG_ERROR_CLIENT_DOES_NOT_EXISTS
;
...
...
gui/baculum/protected/API/Pages/API/Objects.php
View file @
ea4363a0
...
...
@@ -45,36 +45,53 @@ class Objects extends BaculumAPIServer {
$params
=
[];
if
(
!
empty
(
$objecttype
))
{
$params
[
'Object.ObjectType'
][
'operator'
]
=
''
;
$params
[
'Object.ObjectType'
][
'vals'
]
=
$objecttype
;
$params
[
'Object.ObjectType'
]
=
[];
$params
[
'Object.ObjectType'
][]
=
[
'vals'
=>
$objecttype
];
}
if
(
!
empty
(
$objectname
))
{
$params
[
'Object.ObjectName'
][
'operator'
]
=
''
;
$params
[
'Object.ObjectName'
][
'vals'
]
=
$objectname
;
$params
[
'Object.ObjectName'
]
=
[];
$params
[
'Object.ObjectName'
][]
=
[
'vals'
=>
$objectname
];
}
if
(
!
empty
(
$objectcategory
))
{
$params
[
'Object.ObjectCategory'
][
'operator'
]
=
''
;
$params
[
'Object.ObjectCategory'
][
'vals'
]
=
$objectcategory
;
$params
[
'Object.ObjectCategory'
]
=
[];
$params
[
'Object.ObjectCategory'
][]
=
[
'vals'
=>
$objectcategory
];
}
if
(
!
empty
(
$objectsource
))
{
$params
[
'Object.ObjectSource'
][
'operator'
]
=
''
;
$params
[
'Object.ObjectSource'
][
'vals'
]
=
$objectsource
;
$params
[
'Object.ObjectSource'
]
=
[];
$params
[
'Object.ObjectSource'
][]
=
[
'vals'
=>
$objectsource
];
}
if
(
!
empty
(
$objectuuid
))
{
$params
[
'Object.ObjectUUID'
][
'operator'
]
=
''
;
$params
[
'Object.ObjectUUID'
][
'vals'
]
=
$objectuuid
;
$params
[
'Object.ObjectUUID'
]
=
[];
$params
[
'Object.ObjectUUID'
][]
=
[
'vals'
=>
$objectuuid
];
}
if
(
!
empty
(
$objectstatus
))
{
$params
[
'Object.ObjectStatus'
][
'operator'
]
=
''
;
$params
[
'Object.ObjectStatus'
][
'vals'
]
=
$objectstatus
;
$params
[
'Object.ObjectStatus'
]
=
[];
$params
[
'Object.ObjectStatus'
][]
=
[
'vals'
=>
$objectstatus
];
}
if
(
!
empty
(
$jobname
))
{
$params
[
'Job.Name'
][
'operator'
]
=
''
;
$params
[
'Job.Name'
][
'vals'
]
=
$jobname
;
$params
[
'Job.Name'
]
=
[];
$params
[
'Job.Name'
][]
=
[
'vals'
=>
$jobname
];
}
if
(
count
(
$jobids
)
>
0
)
{
$params
[
'Job.JobId'
][
'operator'
]
=
'IN'
;
$params
[
'Job.JobId'
][
'vals'
]
=
$jobids
;
$params
[
'Job.JobId'
]
=
[];
$params
[
'Job.JobId'
][]
=
[
'operator'
=>
'IN'
,
'vals'
=>
$jobids
];
}
$objects
=
$this
->
getModule
(
'object'
)
->
getObjects
(
$params
,
$limit
);
...
...
gui/baculum/protected/API/openapi_baculum.json
View file @
ea4363a0
...
...
@@ -877,6 +877,78 @@
"schema"
:
{
"type"
:
"string"
}
},
{
"name"
:
"schedtime_from"
,
"in"
:
"query"
,
"required"
:
false
,
"description"
:
"Scheduled time from (UNIX timestamp format, seconds)"
,
"schema"
:
{
"type"
:
"integer"
}
},
{
"name"
:
"schedtime_to"
,
"in"
:
"query"
,
"required"
:
false
,
"description"
:
"Scheduled time to (UNIX timestamp format, seconds)"
,
"schema"
:
{
"type"
:
"integer"
}
},
{
"name"
:
"starttime_from"
,
"in"
:
"query"
,
"required"
:
false
,
"description"
:
"Start time from (UNIX timestamp format, seconds)"
,
"schema"
:
{
"type"
:
"integer"
}
},
{
"name"
:
"starttime_to"
,
"in"
:
"query"
,
"required"
:
false
,
"description"
:
"Start time to (UNIX timestamp format, seconds)"
,
"schema"
:
{
"type"
:
"integer"
}
},
{
"name"
:
"endtime_from"
,
"in"
:
"query"
,
"required"
:
false
,
"description"
:
"End time from (UNIX timestamp format, seconds)"
,
"schema"
:
{
"type"
:
"integer"
}
},
{
"name"
:
"endtime_to"
,
"in"
:
"query"
,
"required"
:
false
,
"description"
:
"End time to (UNIX timestamp format, seconds)"
,
"schema"
:
{
"type"
:
"integer"
}
},
{
"name"
:
"realendtime_from"
,
"in"
:
"query"
,
"required"
:
false
,
"description"
:
"Real end time from (UNIX timestamp format, seconds)"
,
"schema"
:
{
"type"
:
"integer"
}
},
{
"name"
:
"realendtime_to"
,
"in"
:
"query"
,
"required"
:
false
,
"description"
:
"Real end time to (UNIX timestamp format, seconds)"
,
"schema"
:
{
"type"
:
"integer"
}
}
]
}
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new 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