Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
40c626cb
authored
Nov 01, 2023
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
完善令牌界面
parent
607358c9
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
229 additions
and
163 deletions
+229
-163
web/src/components/TokensTable.js
+34
-8
web/src/pages/Token/EditToken.js
+195
-155
No files found.
web/src/components/TokensTable.js
View file @
40c626cb
...
@@ -5,6 +5,7 @@ import {API, copy, isAdmin, showError, showSuccess, showWarning, timestamp2strin
...
@@ -5,6 +5,7 @@ import {API, copy, isAdmin, showError, showSuccess, showWarning, timestamp2strin
import
{
ITEMS_PER_PAGE
}
from
'../constants'
;
import
{
ITEMS_PER_PAGE
}
from
'../constants'
;
import
{
renderQuota
,
stringToColor
}
from
'../helpers/render'
;
import
{
renderQuota
,
stringToColor
}
from
'../helpers/render'
;
import
{
Avatar
,
Tag
,
Table
,
Button
,
Popover
,
Form
,
Modal
,
Popconfirm
}
from
"@douyinfe/semi-ui"
;
import
{
Avatar
,
Tag
,
Table
,
Button
,
Popover
,
Form
,
Modal
,
Popconfirm
}
from
"@douyinfe/semi-ui"
;
import
EditToken
from
"../pages/Token/EditToken"
;
const
{
Column
}
=
Table
;
const
{
Column
}
=
Table
;
...
@@ -77,7 +78,7 @@ const TokensTable = () => {
...
@@ -77,7 +78,7 @@ const TokensTable = () => {
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
(
<
div
>
<
div
>
{
re
nderQuota
(
parseInt
(
text
))
}
{
re
cord
.
unlimited_quota
?
<
Tag
size
=
{
'large'
}
color
=
{
'white'
}
>
无限制
<
/Tag> : <Tag size={'large'} color={'light-blue'}>{renderQuota
(
parseInt
(
text
))
}</
Tag
>
}
<
/div
>
<
/div
>
);
);
},
},
...
@@ -95,11 +96,11 @@ const TokensTable = () => {
...
@@ -95,11 +96,11 @@ const TokensTable = () => {
},
},
{
{
title
:
'过期时间'
,
title
:
'过期时间'
,
dataIndex
:
'
access
ed_time'
,
dataIndex
:
'
expir
ed_time'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
(
<
div
>
<
div
>
{
renderTimestamp
(
text
)}
{
re
cord
.
expired_time
===
-
1
?
"永不过期"
:
re
nderTimestamp
(
text
)}
<
/div
>
<
/div
>
);
);
},
},
...
@@ -159,12 +160,18 @@ const TokensTable = () => {
...
@@ -159,12 +160,18 @@ const TokensTable = () => {
}
}
}
>
启用
<
/Button
>
}
>
启用
<
/Button
>
}
}
<
Button
theme
=
'light'
type
=
'tertiary'
style
=
{{
marginRight
:
1
}}
>
编辑
<
/Button
>
<
Button
theme
=
'light'
type
=
'tertiary'
style
=
{{
marginRight
:
1
}}
onClick
=
{
()
=>
{
setEditingToken
(
record
);
setShowEdit
(
true
);
}
}
>
编辑
<
/Button
>
<
/div
>
<
/div
>
),
),
},
},
];
];
const
[
showEdit
,
setShowEdit
]
=
useState
(
false
);
const
[
tokens
,
setTokens
]
=
useState
([]);
const
[
tokens
,
setTokens
]
=
useState
([]);
const
[
selectedKeys
,
setSelectedKeys
]
=
useState
([]);
const
[
selectedKeys
,
setSelectedKeys
]
=
useState
([]);
const
[
tokenCount
,
setTokenCount
]
=
useState
(
ITEMS_PER_PAGE
);
const
[
tokenCount
,
setTokenCount
]
=
useState
(
ITEMS_PER_PAGE
);
...
@@ -174,6 +181,16 @@ const TokensTable = () => {
...
@@ -174,6 +181,16 @@ const TokensTable = () => {
const
[
searching
,
setSearching
]
=
useState
(
false
);
const
[
searching
,
setSearching
]
=
useState
(
false
);
const
[
showTopUpModal
,
setShowTopUpModal
]
=
useState
(
false
);
const
[
showTopUpModal
,
setShowTopUpModal
]
=
useState
(
false
);
const
[
targetTokenIdx
,
setTargetTokenIdx
]
=
useState
(
0
);
const
[
targetTokenIdx
,
setTargetTokenIdx
]
=
useState
(
0
);
const
[
editingToken
,
setEditingToken
]
=
useState
({
id
:
undefined
,
});
const
closeEdit
=
()
=>
{
setShowEdit
(
false
);
setEditingToken
({
id
:
undefined
,
});
}
const
setTokensFormat
=
(
tokens
)
=>
{
const
setTokensFormat
=
(
tokens
)
=>
{
setTokens
(
tokens
);
setTokens
(
tokens
);
...
@@ -409,19 +426,17 @@ const TokensTable = () => {
...
@@ -409,19 +426,17 @@ const TokensTable = () => {
const
rowSelection
=
{
const
rowSelection
=
{
onSelect
:
(
record
,
selected
)
=>
{
onSelect
:
(
record
,
selected
)
=>
{
// console.log(`select row: ${selected}`, record);
},
},
onSelectAll
:
(
selected
,
selectedRows
)
=>
{
onSelectAll
:
(
selected
,
selectedRows
)
=>
{
// console.log(`select all rows: ${selected}`, selectedRows);
},
},
onChange
:
(
selectedRowKeys
,
selectedRows
)
=>
{
onChange
:
(
selectedRowKeys
,
selectedRows
)
=>
{
// console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
setSelectedKeys
(
selectedRows
);
setSelectedKeys
(
selectedRows
);
},
},
};
};
return
(
return
(
<>
<>
<
EditToken
refresh
=
{
refresh
}
editingToken
=
{
editingToken
}
visiable
=
{
showEdit
}
handleClose
=
{
closeEdit
}
><
/EditToken
>
<
Form
layout
=
'horizontal'
style
=
{{
marginTop
:
10
}}
labelPosition
=
{
'left'
}
>
<
Form
layout
=
'horizontal'
style
=
{{
marginTop
:
10
}}
labelPosition
=
{
'left'
}
>
<
Form
.
Input
<
Form
.
Input
field
=
"keyword"
field
=
"keyword"
...
@@ -443,9 +458,20 @@ const TokensTable = () => {
...
@@ -443,9 +458,20 @@ const TokensTable = () => {
onPageChange
:
handlePageChange
,
onPageChange
:
handlePageChange
,
}}
loading
=
{
loading
}
rowSelection
=
{
rowSelection
}
>
}}
loading
=
{
loading
}
rowSelection
=
{
rowSelection
}
>
<
/Table
>
<
/Table
>
<
Button
theme
=
'light'
type
=
'primary'
style
=
{{
marginRight
:
8
}}
>
添加令牌
<
/Button
>
<
Button
theme
=
'light'
type
=
'primary'
style
=
{{
marginRight
:
8
}}
onClick
=
{
()
=>
{
setEditingToken
({
id
:
undefined
,
});
setShowEdit
(
true
);
}
}
>
添加令牌
<
/Button
>
<
Button
label
=
'复制所选令牌'
type
=
"warning"
onClick
=
{
<
Button
label
=
'复制所选令牌'
type
=
"warning"
onClick
=
{
async
()
=>
{
async
()
=>
{
if
(
selectedKeys
.
length
===
0
)
{
showError
(
'请至少选择一个令牌!'
);
return
;
}
let
keys
=
""
;
let
keys
=
""
;
for
(
let
i
=
0
;
i
<
selectedKeys
.
length
;
i
++
)
{
for
(
let
i
=
0
;
i
<
selectedKeys
.
length
;
i
++
)
{
keys
+=
selectedKeys
[
i
].
name
+
" sk-"
+
selectedKeys
[
i
].
key
+
"\n"
;
keys
+=
selectedKeys
[
i
].
name
+
" sk-"
+
selectedKeys
[
i
].
key
+
"\n"
;
...
...
web/src/pages/Token/EditToken.js
View file @
40c626cb
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useRef
,
useState
}
from
'react'
;
import
{
Button
,
Form
,
Header
,
Message
,
Segment
}
from
'semantic-ui-react'
;
// import {Button, Form, Header, Message, Segment} from 'semantic-ui-react';
import
{
useParams
,
useNavigate
}
from
'react-router-dom'
;
import
{
useParams
,
useNavigate
}
from
'react-router-dom'
;
import
{
API
,
showError
,
showSuccess
,
timestamp2string
}
from
'../../helpers'
;
import
{
API
,
isMobile
,
showError
,
showSuccess
,
timestamp2string
}
from
'../../helpers'
;
import
{
renderQuota
,
renderQuotaWithPrompt
}
from
'../../helpers/render'
;
import
{
renderQuota
,
renderQuotaWithPrompt
}
from
'../../helpers/render'
;
import
{
Layout
,
SideSheet
,
Button
,
Space
,
Spin
,
Banner
,
Input
,
DatePicker
,
AutoComplete
,
Typography
}
from
"@douyinfe/semi-ui"
;
import
Title
from
"@douyinfe/semi-ui/lib/es/typography/title"
;
import
{
Divider
}
from
"semantic-ui-react"
;
const
EditToken
=
()
=>
{
const
EditToken
=
(
props
)
=>
{
const
params
=
useParams
();
const
isEdit
=
props
.
editingToken
.
id
!==
undefined
;
const
tokenId
=
params
.
id
;
const
[
loading
,
setLoading
]
=
useState
(
isEdit
);
const
isEdit
=
tokenId
!==
undefined
;
const
originInputs
=
{
const
[
loading
,
setLoading
]
=
useState
(
isEdit
);
name
:
''
,
const
originInputs
=
{
remain_quota
:
isEdit
?
0
:
500000
,
name
:
''
,
expired_time
:
-
1
,
remain_quota
:
isEdit
?
0
:
500000
,
unlimited_quota
:
false
expired_time
:
-
1
,
};
unlimited_quota
:
false
const
[
inputs
,
setInputs
]
=
useState
(
originInputs
);
};
const
{
name
,
remain_quota
,
expired_time
,
unlimited_quota
}
=
inputs
;
const
[
inputs
,
setInputs
]
=
useState
(
originInputs
);
// const [visible, setVisible] = useState(false);
const
{
name
,
remain_quota
,
expired_time
,
unlimited_quota
}
=
inputs
;
const
navigate
=
useNavigate
();
const
navigate
=
useNavigate
();
const
handleInputChange
=
(
name
,
value
)
=>
{
const
handleInputChange
=
(
e
,
{
name
,
value
})
=>
{
setInputs
((
inputs
)
=>
({...
inputs
,
[
name
]:
value
}));
setInputs
((
inputs
)
=>
({
...
inputs
,
[
name
]:
value
}));
};
};
const
handleCancel
=
()
=>
{
const
handleCancel
=
()
=>
{
props
.
handleClose
();
navigate
(
"/token"
);
}
const
setExpiredTime
=
(
month
,
day
,
hour
,
minute
)
=>
{
let
now
=
new
Date
();
let
timestamp
=
now
.
getTime
()
/
1000
;
let
seconds
=
month
*
30
*
24
*
60
*
60
;
seconds
+=
day
*
24
*
60
*
60
;
seconds
+=
hour
*
60
*
60
;
seconds
+=
minute
*
60
;
if
(
seconds
!==
0
)
{
timestamp
+=
seconds
;
setInputs
({
...
inputs
,
expired_time
:
timestamp2string
(
timestamp
)
});
}
else
{
setInputs
({
...
inputs
,
expired_time
:
-
1
});
}
}
};
const
setExpiredTime
=
(
month
,
day
,
hour
,
minute
)
=>
{
let
now
=
new
Date
();
let
timestamp
=
now
.
getTime
()
/
1000
;
let
seconds
=
month
*
30
*
24
*
60
*
60
;
seconds
+=
day
*
24
*
60
*
60
;
seconds
+=
hour
*
60
*
60
;
seconds
+=
minute
*
60
;
if
(
seconds
!==
0
)
{
timestamp
+=
seconds
;
setInputs
({...
inputs
,
expired_time
:
timestamp2string
(
timestamp
)});
}
else
{
setInputs
({...
inputs
,
expired_time
:
-
1
});
}
};
const
setUnlimitedQuota
=
()
=>
{
const
setUnlimitedQuota
=
()
=>
{
setInputs
({
...
inputs
,
unlimited_quota
:
!
unlimited_quota
});
setInputs
({...
inputs
,
unlimited_quota
:
!
unlimited_quota
});
};
};
const
loadToken
=
async
()
=>
{
const
loadToken
=
async
()
=>
{
let
res
=
await
API
.
get
(
`/api/token/
${
tokenId
}
`
);
setLoading
(
true
);
const
{
success
,
message
,
data
}
=
res
.
data
;
let
res
=
await
API
.
get
(
`/api/token/
${
props
.
editingToken
.
id
}
`
);
if
(
success
)
{
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
data
.
expired_time
!==
-
1
)
{
if
(
success
)
{
data
.
expired_time
=
timestamp2string
(
data
.
expired_time
);
if
(
data
.
expired_time
!==
-
1
)
{
}
data
.
expired_time
=
timestamp2string
(
data
.
expired_time
);
setInputs
(
data
);
}
}
else
{
setInputs
(
data
);
showError
(
message
);
}
else
{
}
showError
(
message
);
setLoading
(
false
);
}
};
setLoading
(
false
);
useEffect
(()
=>
{
};
if
(
isEdit
)
{
useEffect
(()
=>
{
loadToken
().
then
();
if
(
isEdit
)
{
}
loadToken
().
then
(
},
[]);
()
=>
{
console
.
log
(
inputs
);
}
);
}
else
{
setInputs
(
originInputs
);
}
},
[
props
.
editingToken
.
id
]);
const
submit
=
async
()
=>
{
const
submit
=
async
()
=>
{
if
(
!
isEdit
&&
inputs
.
name
===
''
)
return
;
setLoading
(
true
);
let
localInputs
=
inputs
;
if
(
!
isEdit
&&
inputs
.
name
===
''
)
return
;
localInputs
.
remain_quota
=
parseInt
(
localInputs
.
remain_quota
);
let
localInputs
=
inputs
;
if
(
localInputs
.
expired_time
!==
-
1
)
{
localInputs
.
remain_quota
=
parseInt
(
localInputs
.
remain_quota
);
let
time
=
Date
.
parse
(
localInputs
.
expired_time
);
if
(
localInputs
.
expired_time
!==
-
1
)
{
if
(
isNaN
(
time
))
{
let
time
=
Date
.
parse
(
localInputs
.
expired_time
);
showError
(
'过期时间格式错误!'
);
if
(
isNaN
(
time
))
{
return
;
showError
(
'过期时间格式错误!'
);
}
return
;
localInputs
.
expired_time
=
Math
.
ceil
(
time
/
1000
);
}
}
localInputs
.
expired_time
=
Math
.
ceil
(
time
/
1000
);
let
res
;
}
if
(
isEdit
)
{
let
res
;
res
=
await
API
.
put
(
`/api/token/`
,
{
...
localInputs
,
id
:
parseInt
(
tokenId
)
});
if
(
isEdit
)
{
}
else
{
res
=
await
API
.
put
(
`/api/token/`
,
{...
localInputs
,
id
:
parseInt
(
props
.
editingToken
.
id
)});
res
=
await
API
.
post
(
`/api/token/`
,
localInputs
);
}
else
{
}
res
=
await
API
.
post
(
`/api/token/`
,
localInputs
);
const
{
success
,
message
}
=
res
.
data
;
}
if
(
success
)
{
const
{
success
,
message
}
=
res
.
data
;
if
(
isEdit
)
{
if
(
success
)
{
showSuccess
(
'令牌更新成功!'
);
if
(
isEdit
)
{
}
else
{
showSuccess
(
'令牌更新成功!'
);
showSuccess
(
'令牌创建成功,请在列表页面点击复制获取令牌!'
);
props
.
refresh
();
setInputs
(
originInputs
);
props
.
handleClose
();
}
}
else
{
}
else
{
showSuccess
(
'令牌创建成功,请在列表页面点击复制获取令牌!'
);
showError
(
message
);
setInputs
(
originInputs
);
}
props
.
refresh
();
};
props
.
handleClose
();
}
}
else
{
showError
(
message
);
}
setLoading
(
false
);
};
return
(
<>
<
SideSheet
title
=
{
<
Title
level
=
{
3
}
>
{
isEdit
?
'更新令牌信息'
:
'创建新的令牌'
}
<
/Title>
}
headerStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
}}
bodyStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
}}
visible
=
{
props
.
visiable
}
footer
=
{
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'flex-end'
}}
>
<
Space
>
<
Button
theme
=
'solid'
size
=
{
'large'
}
onClick
=
{
submit
}
>
提交
<
/Button
>
<
Button
theme
=
'solid'
size
=
{
'large'
}
type
=
{
'tertiary'
}
onClick
=
{
handleCancel
}
>
取消
<
/Button
>
<
/Space
>
<
/div
>
}
closeIcon
=
{
null
}
onCancel
=
{()
=>
handleCancel
()}
width
=
{
isMobile
()
?
'100%'
:
600
}
>
<
Spin
spinning
=
{
loading
}
>
<
Input
style
=
{{
marginTop
:
20
}}
label
=
'名称'
name
=
'name'
placeholder
=
{
'请输入名称'
}
onChange
=
{(
value
)
=>
handleInputChange
(
'name'
,
value
)}
value
=
{
name
}
autoComplete
=
'new-password'
required
=
{
!
isEdit
}
/
>
<
Divider
/>
<
DatePicker
label
=
'过期时间'
name
=
'expired_time'
placeholder
=
{
'请选择过期时间'
}
onChange
=
{(
value
)
=>
handleInputChange
(
'expired_time'
,
value
)}
value
=
{
expired_time
}
autoComplete
=
'new-password'
type
=
'dateTime'
/>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Space
>
<
Button
type
=
{
'tertiary'
}
onClick
=
{()
=>
{
setExpiredTime
(
0
,
0
,
0
,
0
);
}}
>
永不过期
<
/Button
>
<
Button
type
=
{
'tertiary'
}
onClick
=
{()
=>
{
setExpiredTime
(
0
,
0
,
1
,
0
);
}}
>
一小时
<
/Button
>
<
Button
type
=
{
'tertiary'
}
onClick
=
{()
=>
{
setExpiredTime
(
1
,
0
,
0
,
0
);
}}
>
一个月
<
/Button
>
<
Button
type
=
{
'tertiary'
}
onClick
=
{()
=>
{
setExpiredTime
(
0
,
1
,
0
,
0
);
}}
>
一天
<
/Button
>
<
/Space
>
<
/div
>
return
(
<
Divider
/>
<>
<
Banner
type
=
{
'warning'
}
description
=
{
'注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。'
}
><
/Banner
>
<
Segment
loading
=
{
loading
}
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Header
as
=
'h3'
>
{
isEdit
?
'更新令牌信息'
:
'创建新的令牌'
}
<
/Header
>
<
Typography
.
Text
>
{
`额度
${
renderQuotaWithPrompt
(
remain_quota
)}
`
}
<
/Typography.Text
>
<
Form
autoComplete
=
'new-password'
>
<
/div
>
<
Form
.
Field
>
<
AutoComplete
<
Form
.
Input
style
=
{{
marginTop
:
8
}}
label
=
'名称'
name
=
'remain_quota'
name
=
'name'
placeholder
=
{
'请输入额度'
}
placeholder
=
{
'请输入名称'
}
onChange
=
{(
value
)
=>
handleInputChange
(
'remain_quota'
,
value
)}
onChange
=
{
handleInputChange
}
value
=
{
remain_quota
}
value
=
{
name
}
autoComplete
=
'new-password'
autoComplete
=
'new-password'
type
=
'number'
required
=
{
!
isEdit
}
position
=
{
'top'
}
/
>
data
=
{[
<
/Form.Field
>
{
value
:
500000
,
label
:
'1$'
},
<
Form
.
Field
>
{
value
:
5000000
,
label
:
'10$'
},
<
Form
.
Input
{
value
:
25000000
,
label
:
'50$'
},
label
=
'过期时间'
{
value
:
50000000
,
label
:
'100$'
},
name
=
'expired_time'
{
value
:
250000000
,
label
:
'500$'
},
placeholder
=
{
'请输入过期时间,格式为 yyyy-MM-dd HH:mm:ss,-1 表示无限制'
}
{
value
:
500000000
,
label
:
'1000$'
},
onChange
=
{
handleInputChange
}
]}
value
=
{
expired_time
}
disabled
=
{
unlimited_quota
}
autoComplete
=
'new-password'
/
>
type
=
'datetime-local'
<
div
>
/>
<
Button
style
=
{{
marginTop
:
8
}}
type
=
{
'warning'
}
onClick
=
{()
=>
{
<
/Form.Field
>
setUnlimitedQuota
();
<
div
style
=
{{
lineHeight
:
'40px'
}}
>
}}
>
{
unlimited_quota
?
'取消无限额度'
:
'设为无限额度'
}
<
/Button
>
<
Button
type
=
{
'button'
}
onClick
=
{()
=>
{
<
/div
>
setExpiredTime
(
0
,
0
,
0
,
0
);
<
/Spin
>
}}
>
永不过期
<
/Button
>
<
/SideSheet
>
<
Button
type
=
{
'button'
}
onClick
=
{()
=>
{
<
/
>
setExpiredTime
(
1
,
0
,
0
,
0
);
);
}}
>
一个月后过期
<
/Button
>
<
Button
type
=
{
'button'
}
onClick
=
{()
=>
{
setExpiredTime
(
0
,
1
,
0
,
0
);
}}
>
一天后过期
<
/Button
>
<
Button
type
=
{
'button'
}
onClick
=
{()
=>
{
setExpiredTime
(
0
,
0
,
1
,
0
);
}}
>
一小时后过期
<
/Button
>
<
Button
type
=
{
'button'
}
onClick
=
{()
=>
{
setExpiredTime
(
0
,
0
,
0
,
1
);
}}
>
一分钟后过期
<
/Button
>
<
/div
>
<
Message
>
注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。
<
/Message
>
<
Form
.
Field
>
<
Form
.
Input
label
=
{
`额度
${
renderQuotaWithPrompt
(
remain_quota
)}
`
}
name
=
'remain_quota'
placeholder
=
{
'请输入额度'
}
onChange
=
{
handleInputChange
}
value
=
{
remain_quota
}
autoComplete
=
'new-password'
type
=
'number'
disabled
=
{
unlimited_quota
}
/
>
<
/Form.Field
>
<
Button
type
=
{
'button'
}
onClick
=
{()
=>
{
setUnlimitedQuota
();
}}
>
{
unlimited_quota
?
'取消无限额度'
:
'设为无限额度'
}
<
/Button
>
<
Button
floated
=
'right'
positive
onClick
=
{
submit
}
>
提交
<
/Button
>
<
Button
floated
=
'right'
onClick
=
{
handleCancel
}
>
取消
<
/Button
>
<
/Form
>
<
/Segment
>
<
/
>
);
};
};
export
default
EditToken
;
export
default
EditToken
;
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