Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
haskell-gargantext
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
195
Issues
195
List
Board
Labels
Milestones
Merge Requests
12
Merge Requests
12
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
gargantext
haskell-gargantext
Commits
5585a91b
Verified
Commit
5585a91b
authored
Sep 04, 2024
by
Przemyslaw Kaminski
1
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[config] gc_frames type instead of separate urls in GargConfig
parent
3b3f5109
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
48 additions
and
56 deletions
+48
-56
Ini.hs
bin/gargantext-cli/CLI/Ini.hs
+6
-6
Searx.hs
src/Gargantext/API/Node/Corpus/Searx.hs
+5
-3
Config.hs
src/Gargantext/Core/Config.hs
+22
-41
Types.hs
src/Gargantext/Core/Config/Types.hs
+8
-0
Node.hs
src/Gargantext/Database/Action/Node.hs
+4
-4
ReverseProxy.hs
src/Gargantext/MicroServices/ReverseProxy.hs
+3
-2
No files found.
bin/gargantext-cli/CLI/Ini.hs
View file @
5585a91b
...
@@ -60,11 +60,6 @@ convertConfigs ini@(Ini.GargConfig { .. }) iniMail nlpConfig connInfo =
...
@@ -60,11 +60,6 @@ convertConfigs ini@(Ini.GargConfig { .. }) iniMail nlpConfig connInfo =
,
_gc_masteruser
,
_gc_masteruser
,
_gc_secretkey
,
_gc_secretkey
,
_gc_datafilepath
,
_gc_datafilepath
,
_gc_frame_write_url
,
_gc_frame_calc_url
,
_gc_frame_visio_url
,
_gc_frame_searx_url
,
_gc_frame_istex_url
,
_gc_max_docs_parsers
,
_gc_max_docs_parsers
,
_gc_max_docs_scrapers
,
_gc_max_docs_scrapers
,
_gc_js_job_timeout
,
_gc_js_job_timeout
...
@@ -75,7 +70,12 @@ convertConfigs ini@(Ini.GargConfig { .. }) iniMail nlpConfig connInfo =
...
@@ -75,7 +70,12 @@ convertConfigs ini@(Ini.GargConfig { .. }) iniMail nlpConfig connInfo =
,
_gc_nlp_config
=
nlpConfig
,
_gc_nlp_config
=
nlpConfig
,
_gc_frontend_config
=
mkFrontendConfig
ini
,
_gc_frontend_config
=
mkFrontendConfig
ini
,
_gc_database_config
=
connInfo
,
_gc_database_config
=
connInfo
,
_gc_notifications_config
=
defaultNotificationsConfig
}
,
_gc_notifications_config
=
defaultNotificationsConfig
,
_gc_frames
=
CTypes
.
FramesConfig
{
_f_write_url
=
_gc_frame_write_url
,
_f_calc_url
=
_gc_frame_calc_url
,
_f_visio_url
=
_gc_frame_visio_url
,
_f_searx_url
=
_gc_frame_searx_url
,
_f_istex_url
=
_gc_frame_istex_url
}
}
mkFrontendConfig
::
Ini
.
GargConfig
->
CTypes
.
FrontendConfig
mkFrontendConfig
::
Ini
.
GargConfig
->
CTypes
.
FrontendConfig
mkFrontendConfig
(
Ini
.
GargConfig
{
..
})
=
mkFrontendConfig
(
Ini
.
GargConfig
{
..
})
=
...
...
src/Gargantext/API/Node/Corpus/Searx.hs
View file @
5585a91b
...
@@ -21,7 +21,10 @@ import Data.Text qualified as Text
...
@@ -21,7 +21,10 @@ import Data.Text qualified as Text
import
Data.Time.Calendar
(
Day
,
toGregorian
)
import
Data.Time.Calendar
(
Day
,
toGregorian
)
import
Data.Time.Format
(
defaultTimeLocale
,
formatTime
,
parseTimeM
)
import
Data.Time.Format
(
defaultTimeLocale
,
formatTime
,
parseTimeM
)
import
Data.Tuple.Select
(
sel1
,
sel2
,
sel3
)
import
Data.Tuple.Select
(
sel1
,
sel2
,
sel3
)
import
Gargantext.API.Admin.Types
(
HasSettings
)
import
Gargantext.Core
(
Lang
(
..
))
import
Gargantext.Core
(
Lang
(
..
))
import
Gargantext.Core.Config
(
GargConfig
(
..
))
import
Gargantext.Core.Config.Types
(
FramesConfig
(
..
))
import
Gargantext.Core.NLP
(
HasNLPServer
,
nlpServerGet
)
import
Gargantext.Core.NLP
(
HasNLPServer
,
nlpServerGet
)
import
Gargantext.Core.NodeStory.Types
(
HasNodeStory
)
import
Gargantext.Core.NodeStory.Types
(
HasNodeStory
)
import
Gargantext.Core.Text.Corpus.Query
qualified
as
Query
import
Gargantext.Core.Text.Corpus.Query
qualified
as
Query
...
@@ -43,12 +46,11 @@ import Gargantext.Database.Query.Table.Node.Error (HasNodeError)
...
@@ -43,12 +46,11 @@ import Gargantext.Database.Query.Table.Node.Error (HasNodeError)
import
Gargantext.Database.Query.Tree.Error
(
HasTreeError
)
import
Gargantext.Database.Query.Tree.Error
(
HasTreeError
)
import
Gargantext.Database.Query.Tree.Root
(
getOrMkRootWithCorpus
,
MkCorpusUser
(
MkCorpusUserMaster
))
import
Gargantext.Database.Query.Tree.Root
(
getOrMkRootWithCorpus
,
MkCorpusUser
(
MkCorpusUserMaster
))
import
Gargantext.Prelude
hiding
(
All
)
import
Gargantext.Prelude
hiding
(
All
)
import
Gargantext.Core.Config
(
GargConfig
(
..
))
import
Gargantext.Utils.Jobs.Monad
(
JobHandle
,
MonadJobStatus
(
..
))
import
Gargantext.Utils.Jobs.Monad
(
JobHandle
,
MonadJobStatus
(
..
))
import
Network.HTTP.Client
import
Network.HTTP.Client
import
Network.HTTP.Client.TLS
(
tlsManagerSettings
)
import
Network.HTTP.Client.TLS
(
tlsManagerSettings
)
import
Prelude
qualified
import
Prelude
qualified
import
Gargantext.API.Admin.Types
(
HasSettings
)
langToSearx
::
Lang
->
Text
langToSearx
::
Lang
->
Text
langToSearx
x
=
Text
.
toLower
acronym
<>
"-"
<>
acronym
langToSearx
x
=
Text
.
toLower
acronym
<>
"-"
<>
acronym
...
@@ -188,7 +190,7 @@ triggerSearxSearch user cId q l jobHandle = do
...
@@ -188,7 +190,7 @@ triggerSearxSearch user cId q l jobHandle = do
-- printDebug "[triggerSearxSearch] l" l
-- printDebug "[triggerSearxSearch] l" l
cfg
<-
view
hasConfig
cfg
<-
view
hasConfig
uId
<-
getUserId
user
uId
<-
getUserId
user
let
surl
=
_
gc_frame_searx_url
cfg
let
surl
=
_
f_searx_url
$
_gc_frames
cfg
-- printDebug "[triggerSearxSearch] surl" surl
-- printDebug "[triggerSearxSearch] surl" surl
listId
<-
getOrMkList
cId
uId
listId
<-
getOrMkList
cId
uId
...
...
src/Gargantext/Core/Config.hs
View file @
5585a91b
...
@@ -21,11 +21,6 @@ module Gargantext.Core.Config (
...
@@ -21,11 +21,6 @@ module Gargantext.Core.Config (
,
gc_backend_name
,
gc_backend_name
,
gc_datafilepath
,
gc_datafilepath
,
gc_epo_api_url
,
gc_epo_api_url
,
gc_frame_calc_url
,
gc_frame_istex_url
,
gc_frame_searx_url
,
gc_frame_visio_url
,
gc_frame_write_url
,
gc_js_id_timeout
,
gc_js_id_timeout
,
gc_js_job_timeout
,
gc_js_job_timeout
,
gc_masteruser
,
gc_masteruser
...
@@ -40,6 +35,7 @@ module Gargantext.Core.Config (
...
@@ -40,6 +35,7 @@ module Gargantext.Core.Config (
,
gc_database_config
,
gc_database_config
,
gc_nlp_config
,
gc_nlp_config
,
gc_notifications_config
,
gc_notifications_config
,
gc_frames
,
mkProxyUrl
,
mkProxyUrl
)
where
)
where
...
@@ -58,38 +54,32 @@ import Toml.Schema
...
@@ -58,38 +54,32 @@ import Toml.Schema
-- stripRight :: Char -> T.Text -> T.Text
-- stripRight :: Char -> T.Text -> T.Text
-- stripRight c s = if T.last s == c then stripRight c (T.take (T.length s - 1) s) else s
-- stripRight c s = if T.last s == c then stripRight c (T.take (T.length s - 1) s) else s
data
GargConfig
=
GargConfig
{
_gc_backend_name
::
!
T
.
Text
data
GargConfig
=
GargConfig
{
_gc_backend_name
::
!
T
.
Text
,
_gc_url
::
!
T
.
Text
,
_gc_url
::
!
T
.
Text
,
_gc_url_backend_api
::
!
T
.
Text
,
_gc_url_backend_api
::
!
T
.
Text
,
_gc_masteruser
::
!
T
.
Text
,
_gc_masteruser
::
!
T
.
Text
,
_gc_secretkey
::
!
T
.
Text
,
_gc_secretkey
::
!
T
.
Text
,
_gc_datafilepath
::
!
FilePath
,
_gc_datafilepath
::
!
FilePath
-- , _gc_repofilepath :: !FilePath
-- , _gc_repofilepath :: !FilePath
,
_gc_frame_write_url
::
!
T
.
Text
,
_gc_max_docs_parsers
::
!
Integer
,
_gc_frame_calc_url
::
!
T
.
Text
,
_gc_max_docs_scrapers
::
!
Integer
,
_gc_frame_visio_url
::
!
T
.
Text
,
_gc_
frame_searx_url
::
!
T
.
Text
,
_gc_
js_job_timeout
::
!
Integer
,
_gc_
frame_istex_url
::
!
T
.
Text
,
_gc_
js_id_timeout
::
!
Integer
,
_gc_max_docs_parsers
::
!
Integer
,
_gc_pubmed_api_key
::
!
T
.
Text
,
_gc_max_docs_scrapers
::
!
Integer
,
_gc_js_job_timeout
::
!
Integer
,
_gc_epo_api_url
::
!
T
.
Text
,
_gc_js_id_timeout
::
!
Integer
,
_gc_pubmed_api_key
::
!
T
.
Text
,
_gc_frontend_config
::
!
FrontendConfig
,
_gc_mail_config
::
!
MailConfig
,
_gc_epo_api_url
::
!
T
.
Text
,
_gc_database_config
::
!
PSQL
.
ConnectInfo
,
_gc_nlp_config
::
!
NLPConfig
,
_gc_frontend_config
::
!
FrontendConfig
,
_gc_mail_config
::
!
MailConfig
,
_gc_database_config
::
!
PSQL
.
ConnectInfo
,
_gc_nlp_config
::
!
NLPConfig
,
_gc_notifications_config
::
!
NotificationsConfig
,
_gc_notifications_config
::
!
NotificationsConfig
,
_gc_frames
::
!
FramesConfig
}
}
deriving
(
Generic
,
Show
)
deriving
(
Generic
,
Show
)
...
@@ -103,7 +93,7 @@ instance FromValue GargConfig where
...
@@ -103,7 +93,7 @@ instance FromValue GargConfig where
_gc_nlp_config
<-
reqKey
"nlp"
_gc_nlp_config
<-
reqKey
"nlp"
secrets
<-
reqKey
"secrets"
secrets
<-
reqKey
"secrets"
_gc_datafilepath
<-
reqKeyOf
"paths"
$
parseTableFromValue
$
reqKey
"data_filepath"
_gc_datafilepath
<-
reqKeyOf
"paths"
$
parseTableFromValue
$
reqKey
"data_filepath"
frames
<-
reqKeyOf
"external"
$
parseTableFromValue
$
reqKey
"frames"
_gc_
frames
<-
reqKeyOf
"external"
$
parseTableFromValue
$
reqKey
"frames"
jobs
<-
reqKey
"jobs"
jobs
<-
reqKey
"jobs"
apis
<-
reqKey
"apis"
apis
<-
reqKey
"apis"
_gc_notifications_config
<-
reqKey
"notifications"
_gc_notifications_config
<-
reqKey
"notifications"
...
@@ -113,11 +103,6 @@ instance FromValue GargConfig where
...
@@ -113,11 +103,6 @@ instance FromValue GargConfig where
,
_gc_masteruser
=
_s_master_user
secrets
,
_gc_masteruser
=
_s_master_user
secrets
,
_gc_secretkey
=
_s_secret_key
secrets
,
_gc_secretkey
=
_s_secret_key
secrets
,
_gc_datafilepath
,
_gc_datafilepath
,
_gc_frame_write_url
=
_f_write_url
frames
,
_gc_frame_calc_url
=
_f_calc_url
frames
,
_gc_frame_visio_url
=
_f_visio_url
frames
,
_gc_frame_searx_url
=
_f_searx_url
frames
,
_gc_frame_istex_url
=
_f_istex_url
frames
,
_gc_max_docs_parsers
=
_jc_max_docs_parsers
jobs
,
_gc_max_docs_parsers
=
_jc_max_docs_parsers
jobs
,
_gc_max_docs_scrapers
=
_jc_max_docs_scrapers
jobs
,
_gc_max_docs_scrapers
=
_jc_max_docs_scrapers
jobs
,
_gc_js_job_timeout
=
_jc_js_job_timeout
jobs
,
_gc_js_job_timeout
=
_jc_js_job_timeout
jobs
...
@@ -128,7 +113,8 @@ instance FromValue GargConfig where
...
@@ -128,7 +113,8 @@ instance FromValue GargConfig where
,
_gc_mail_config
,
_gc_mail_config
,
_gc_database_config
=
unTOMLConnectInfo
db_config
,
_gc_database_config
=
unTOMLConnectInfo
db_config
,
_gc_nlp_config
,
_gc_nlp_config
,
_gc_notifications_config
}
,
_gc_notifications_config
,
_gc_frames
}
instance
ToValue
GargConfig
where
instance
ToValue
GargConfig
where
toValue
=
defaultTableToValue
toValue
=
defaultTableToValue
instance
ToTable
GargConfig
where
instance
ToTable
GargConfig
where
...
@@ -137,7 +123,7 @@ instance ToTable GargConfig where
...
@@ -137,7 +123,7 @@ instance ToTable GargConfig where
,
"secrets"
.=
secrets
,
"secrets"
.=
secrets
,
"paths"
.=
table
[
"data_filepath"
.=
_gc_datafilepath
]
,
"paths"
.=
table
[
"data_filepath"
.=
_gc_datafilepath
]
,
"apis"
.=
apis
,
"apis"
.=
apis
,
"external"
.=
table
[
"frames"
.=
frames
]
,
"external"
.=
table
[
"frames"
.=
_gc_
frames
]
,
"jobs"
.=
jobs
,
"jobs"
.=
jobs
,
"database"
.=
TOMLConnectInfo
_gc_database_config
,
"database"
.=
TOMLConnectInfo
_gc_database_config
,
"mail"
.=
_gc_mail_config
,
"mail"
.=
_gc_mail_config
...
@@ -149,11 +135,6 @@ instance ToTable GargConfig where
...
@@ -149,11 +135,6 @@ instance ToTable GargConfig where
,
_s_secret_key
=
_gc_secretkey
}
,
_s_secret_key
=
_gc_secretkey
}
apis
=
APIsConfig
{
_ac_pubmed_api_key
=
_gc_pubmed_api_key
apis
=
APIsConfig
{
_ac_pubmed_api_key
=
_gc_pubmed_api_key
,
_ac_epo_api_url
=
_gc_epo_api_url
}
,
_ac_epo_api_url
=
_gc_epo_api_url
}
frames
=
FramesConfig
{
_f_write_url
=
_gc_frame_write_url
,
_f_calc_url
=
_gc_frame_calc_url
,
_f_visio_url
=
_gc_frame_visio_url
,
_f_searx_url
=
_gc_frame_searx_url
,
_f_istex_url
=
_gc_frame_istex_url
}
jobs
=
JobsConfig
{
_jc_max_docs_parsers
=
_gc_max_docs_parsers
jobs
=
JobsConfig
{
_jc_max_docs_parsers
=
_gc_max_docs_parsers
,
_jc_max_docs_scrapers
=
_gc_max_docs_scrapers
,
_jc_max_docs_scrapers
=
_gc_max_docs_scrapers
,
_jc_js_job_timeout
=
_gc_js_job_timeout
,
_jc_js_job_timeout
=
_gc_js_job_timeout
...
...
src/Gargantext/Core/Config/Types.hs
View file @
5585a91b
...
@@ -26,6 +26,12 @@ module Gargantext.Core.Config.Types
...
@@ -26,6 +26,12 @@ module Gargantext.Core.Config.Types
,
NotificationsConfig
(
..
)
,
NotificationsConfig
(
..
)
,
SecretsConfig
(
..
)
,
SecretsConfig
(
..
)
,
f_write_url
,
f_calc_url
,
f_visio_url
,
f_searx_url
,
f_istex_url
,
corsUseOriginsForHosts
,
corsUseOriginsForHosts
,
corsAllowedOrigins
,
corsAllowedOrigins
,
corsAllowedHosts
,
corsAllowedHosts
...
@@ -159,6 +165,8 @@ instance ToTable FramesConfig where
...
@@ -159,6 +165,8 @@ instance ToTable FramesConfig where
,
"searx_url"
.=
_f_searx_url
,
"searx_url"
.=
_f_searx_url
,
"istex_url"
.=
_f_istex_url
]
,
"istex_url"
.=
_f_istex_url
]
makeLenses
''
F
ramesConfig
data
FrontendConfig
=
data
FrontendConfig
=
FrontendConfig
{
_fc_url
::
!
Text
FrontendConfig
{
_fc_url
::
!
Text
...
...
src/Gargantext/Database/Action/Node.hs
View file @
5585a91b
...
@@ -25,7 +25,7 @@ import Data.Text qualified as T
...
@@ -25,7 +25,7 @@ import Data.Text qualified as T
import
Gargantext.API.Admin.Types
(
settings
,
_microservicesSettings
,
HasSettings
)
import
Gargantext.API.Admin.Types
(
settings
,
_microservicesSettings
,
HasSettings
)
import
Gargantext.Core
import
Gargantext.Core
import
Gargantext.Core.Config
(
GargConfig
(
..
),
mkProxyUrl
)
import
Gargantext.Core.Config
(
GargConfig
(
..
),
mkProxyUrl
)
import
Gargantext.Core.Config.Types
(
MicroServicesSettings
(
..
))
import
Gargantext.Core.Config.Types
(
FramesConfig
(
..
),
MicroServicesSettings
(
..
))
import
Gargantext.Core.Types
(
Name
)
import
Gargantext.Core.Types
(
Name
)
import
Gargantext.Database.Admin.Types.Hyperdata
import
Gargantext.Database.Admin.Types.Hyperdata
import
Gargantext.Database.Admin.Types.Hyperdata.Default
import
Gargantext.Database.Admin.Types.Hyperdata.Default
...
@@ -101,7 +101,7 @@ mkNodeWithParent_ConfigureHyperdata _ _ _ _ = nodeError NotImplYet
...
@@ -101,7 +101,7 @@ mkNodeWithParent_ConfigureHyperdata _ _ _ _ = nodeError NotImplYet
internalNotesProxy
::
GargConfig
->
MicroServicesSettings
->
T
.
Text
internalNotesProxy
::
GargConfig
->
MicroServicesSettings
->
T
.
Text
internalNotesProxy
cfg
msSettings
internalNotesProxy
cfg
msSettings
|
_msProxyEnabled
msSettings
=
T
.
pack
$
showBaseUrl
proxyUrl
<>
"/notes"
|
_msProxyEnabled
msSettings
=
T
.
pack
$
showBaseUrl
proxyUrl
<>
"/notes"
|
otherwise
=
_
gc_frame_write_url
cfg
|
otherwise
=
_
f_write_url
$
_gc_frames
cfg
where
where
proxyUrl
=
mkProxyUrl
cfg
msSettings
proxyUrl
=
mkProxyUrl
cfg
msSettings
...
@@ -123,8 +123,8 @@ mkNodeWithParent_ConfigureHyperdata' nt (Just i) uId name = do
...
@@ -123,8 +123,8 @@ mkNodeWithParent_ConfigureHyperdata' nt (Just i) uId name = do
stt
<-
view
settings
stt
<-
view
settings
u
<-
case
nt
of
u
<-
case
nt
of
Notes
->
pure
$
internalNotesProxy
cfg
(
_microservicesSettings
stt
)
Notes
->
pure
$
internalNotesProxy
cfg
(
_microservicesSettings
stt
)
Calc
->
pure
$
_
gc_frame_calc_url
cfg
Calc
->
pure
$
_
f_calc_url
$
_gc_frames
cfg
NodeFrameVisio
->
pure
$
_
gc_frame_visio_url
cfg
NodeFrameVisio
->
pure
$
_
f_visio_url
$
_gc_frames
cfg
_
->
nodeError
NeedsConfiguration
_
->
nodeError
NeedsConfiguration
let
let
s
=
_gc_secretkey
cfg
s
=
_gc_secretkey
cfg
...
...
src/Gargantext/MicroServices/ReverseProxy.hs
View file @
5585a91b
...
@@ -38,7 +38,8 @@ import Gargantext.API.Node.ShareURL qualified as Share
...
@@ -38,7 +38,8 @@ import Gargantext.API.Node.ShareURL qualified as Share
import
Gargantext.API.Routes.Named.Private
import
Gargantext.API.Routes.Named.Private
import
Gargantext.API.Routes.Named.Share
(
ShareLink
(
..
))
import
Gargantext.API.Routes.Named.Share
(
ShareLink
(
..
))
import
Gargantext.API.ThrowAll
(
throwAllRoutes
)
import
Gargantext.API.ThrowAll
(
throwAllRoutes
)
import
Gargantext.Core.Config
(
gc_frame_write_url
,
mkProxyUrl
)
import
Gargantext.Core.Config
(
gc_frames
,
mkProxyUrl
)
import
Gargantext.Core.Config.Types
(
f_write_url
)
import
Gargantext.Database.Admin.Types.Node
(
NodeType
(
..
),
NodeId
(
..
))
import
Gargantext.Database.Admin.Types.Node
(
NodeType
(
..
),
NodeId
(
..
))
import
Gargantext.Database.Prelude
(
hasConfig
)
import
Gargantext.Database.Prelude
(
hasConfig
)
import
Gargantext.Prelude
hiding
(
Handler
)
import
Gargantext.Prelude
hiding
(
Handler
)
...
@@ -274,7 +275,7 @@ proxyPassServer sty env = defaultForwardServer sty id id env
...
@@ -274,7 +275,7 @@ proxyPassServer sty env = defaultForwardServer sty id id env
mkProxyDestination
::
Env
->
ProxyDestination
mkProxyDestination
::
Env
->
ProxyDestination
mkProxyDestination
env
=
fromMaybe
(
panicTrace
"Invalid URI found in the proxied Request."
)
$
do
mkProxyDestination
env
=
fromMaybe
(
panicTrace
"Invalid URI found in the proxied Request."
)
$
do
baseUrl
<-
parseBaseUrl
(
T
.
unpack
$
env
^.
hasConfig
.
gc_frame_write_url
)
baseUrl
<-
parseBaseUrl
(
T
.
unpack
$
env
^.
hasConfig
.
gc_frame
s
.
f
_write_url
)
pure
$
ProxyDestination
baseUrl
pure
$
ProxyDestination
baseUrl
--
--
...
...
Przemyslaw Kaminski
@cgenie
mentioned in commit
5660aec0
·
Oct 08, 2024
mentioned in commit
5660aec0
mentioned in commit 5660aec07ec5a0a0a5468f440092c1a8f57a864e
Toggle commit list
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