Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
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
c9e00d97
authored
Jan 06, 2025
by
alwayssuper
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat:deviceLog and deviceSimulator
parent
b71fa84a
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
138 additions
and
137 deletions
+138
-137
.vscode/settings.json
+1
-1
src/api/iot/device/device/index.ts
+5
-1
src/api/iot/thingmodel/index.ts
+7
-0
src/views/iot/device/device/detail/DeviceDetailsLog.vue
+36
-39
src/views/iot/device/device/detail/DeviceDetailsSimulator.vue
+88
-95
src/views/iot/device/device/detail/index.vue
+1
-1
No files found.
.vscode/settings.json
View file @
c9e00d97
...
@@ -87,7 +87,7 @@
...
@@ -87,7 +87,7 @@
"source.fixAll.stylelint"
:
"explicit"
"source.fixAll.stylelint"
:
"explicit"
},
},
"[vue]"
:
{
"[vue]"
:
{
"editor.defaultFormatter"
:
"
esbenp.prettier-vscode
"
"editor.defaultFormatter"
:
"
octref.vetur
"
},
},
"i18n-ally.localesPaths"
:
[
"src/locales"
],
"i18n-ally.localesPaths"
:
[
"src/locales"
],
"i18n-ally.keystyle"
:
"nested"
,
"i18n-ally.keystyle"
:
"nested"
,
...
...
src/api/iot/device/device/index.ts
View file @
c9e00d97
...
@@ -69,7 +69,7 @@ export interface SimulatorDataVO {
...
@@ -69,7 +69,7 @@ export interface SimulatorDataVO {
deviceKey
:
string
deviceKey
:
string
type
:
string
type
:
string
subType
:
string
subType
:
string
reportTime
:
string
reportTime
:
number
// 时间戳
content
:
string
// 存储 JSON 字符串
content
:
string
// 存储 JSON 字符串
}
}
...
@@ -151,5 +151,9 @@ export const DeviceApi = {
...
@@ -151,5 +151,9 @@ export const DeviceApi = {
// 模拟设备
// 模拟设备
simulatorDevice
:
async
(
data
:
SimulatorDataVO
)
=>
{
simulatorDevice
:
async
(
data
:
SimulatorDataVO
)
=>
{
return
await
request
.
post
({
url
:
`/iot/device/data/simulator`
,
data
})
return
await
request
.
post
({
url
:
`/iot/device/data/simulator`
,
data
})
},
//查询设备日志分页
getDeviceLogPage
:
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
`/iot/device/data/log/page`
,
params
})
}
}
}
}
src/api/iot/thingmodel/index.ts
View file @
c9e00d97
...
@@ -18,6 +18,13 @@ export interface ThingModelData {
...
@@ -18,6 +18,13 @@ export interface ThingModelData {
}
}
/**
/**
* IoT 模拟设备
*/
export
interface
SimulatorData
extends
ThingModelData
{
simulateValue
?:
string
|
number
// 用于存储模拟值
}
/**
* ThingModelProperty 类型
* ThingModelProperty 类型
*/
*/
export
interface
ThingModelProperty
{
export
interface
ThingModelProperty
{
...
...
src/views/iot/device/device/detail/DeviceDetailsLog.vue
View file @
c9e00d97
...
@@ -29,27 +29,18 @@
...
@@ -29,27 +29,18 @@
{{
formatDate
(
scope
.
row
.
time
)
}}
{{
formatDate
(
scope
.
row
.
time
)
}}
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
label=
"类型"
align=
"center"
prop=
"type"
width=
"120"
>
<el-table-column
label=
"类型"
align=
"center"
prop=
"type"
width=
"120"
/>
<
template
#
default=
"scope"
>
<el-table-column
label=
"名称(标识符)"
align=
"center"
prop=
"subType"
width=
"120"
/>
<dict-tag
:type=
"DICT_TYPE.IOT_MESSAGE_TYPE"
:value=
"scope.row.type"
/>
</
template
>
</el-table-column>
<el-table-column
label=
"名称(标识符)"
align=
"center"
prop=
"name"
/>
<el-table-column
label=
"内容"
align=
"center"
prop=
"content"
:show-overflow-tooltip=
"true"
/>
<el-table-column
label=
"内容"
align=
"center"
prop=
"content"
:show-overflow-tooltip=
"true"
/>
</el-table>
</el-table>
<!-- 分页 -->
<!-- 分页 -->
<div
class=
"mt-10px flex justify-end"
>
<div
class=
"mt-10px flex justify-end"
>
<el-pagination
<Pagination
v-model:current-page=
"queryParams.pageNo"
v-model:page-size=
"queryParams.pageSize"
:total=
"total"
:total=
"total"
:page-sizes=
"[10, 20, 50, 100]"
v-model:page=
"queryParams.pageNo"
small
v-model:limit=
"queryParams.pageSize"
background
@
pagination=
"getLogList"
layout=
"total, sizes, prev, pager, next, jumper"
@
size-change=
"handleQuery"
@
current-change=
"handleQuery"
/>
/>
</div>
</div>
</ContentWrap>
</ContentWrap>
...
@@ -61,15 +52,17 @@ import { DICT_TYPE } from '@/utils/dict'
...
@@ -61,15 +52,17 @@ import { DICT_TYPE } from '@/utils/dict'
import
{
formatDate
}
from
'@/utils/formatTime'
import
{
formatDate
}
from
'@/utils/formatTime'
const
props
=
defineProps
<
{
const
props
=
defineProps
<
{
device
Id
:
number
device
Key
:
number
}
>
()
}
>
()
//TODO:后续看看使用什么查询条件 目前后端是留了时间范围 type subType
// 查询参数
// 查询参数
const
queryParams
=
reactive
({
const
queryParams
=
reactive
({
type
:
''
,
deviceKey
:
props
.
deviceKey
,
keyword
:
''
,
// type: '',
// keyword: '',
pageNo
:
1
,
pageNo
:
1
,
pageSize
:
2
0
pageSize
:
1
0
})
})
// 列表数据
// 列表数据
...
@@ -90,23 +83,24 @@ const typeMap = {
...
@@ -90,23 +83,24 @@ const typeMap = {
/** 查询日志列表 */
/** 查询日志列表 */
const
getLogList
=
async
()
=>
{
const
getLogList
=
async
()
=>
{
// if (!props.deviceId) return
if
(
!
props
.
deviceKey
)
return
// loading.value = true
loading
.
value
=
true
// try {
try
{
// const res = await DeviceApi.getDeviceLogs(props.deviceId, queryParams)
const
res
=
await
DeviceApi
.
getDeviceLogPage
(
queryParams
)
// total.value = res.total
total
.
value
=
res
.
total
// logList.value = res.list.map((item: any) => {
logList
.
value
=
res
.
list
.
map
((
item
:
any
)
=>
{
// const log = {
const
log
=
{
// time: item.time,
time
:
item
.
reportTime
,
// type: typeMap[item.type as keyof typeof typeMap] || item.type,
type
:
item
.
type
,
// name: getLogName(item),
subType
:
item
.
subType
,
// content: item.content
content
:
item
.
content
// }
}
// return log
return
log
// })
})
// } finally {
console
.
log
(
logList
.
value
)
// loading.value = false
}
finally
{
// }
loading
.
value
=
false
}
}
}
/** 获取日志名称 */
/** 获取日志名称 */
...
@@ -146,11 +140,14 @@ watch(autoRefresh, (newValue) => {
...
@@ -146,11 +140,14 @@ watch(autoRefresh, (newValue) => {
})
})
/** 监听设备ID变化 */
/** 监听设备ID变化 */
watch
(()
=>
props
.
deviceId
,
(
newValue
)
=>
{
watch
(
()
=>
props
.
deviceKey
,
(
newValue
)
=>
{
if
(
newValue
)
{
if
(
newValue
)
{
handleQuery
()
handleQuery
()
}
}
})
}
)
/** 组件卸载时清除定时器 */
/** 组件卸载时清除定时器 */
onBeforeUnmount
(()
=>
{
onBeforeUnmount
(()
=>
{
...
@@ -161,7 +158,7 @@ onBeforeUnmount(() => {
...
@@ -161,7 +158,7 @@ onBeforeUnmount(() => {
/** 初始化 */
/** 初始化 */
onMounted
(()
=>
{
onMounted
(()
=>
{
if
(
props
.
device
Id
)
{
if
(
props
.
device
Key
)
{
getLogList
()
getLogList
()
}
}
})
})
...
...
src/views/iot/device/device/detail/DeviceDetailsSimulator.vue
View file @
c9e00d97
...
@@ -97,7 +97,7 @@
...
@@ -97,7 +97,7 @@
<!--
事件上报
-->
<!--
事件上报
-->
<
el
-
tab
-
pane
label
=
"事件上报"
name
=
"event"
>
<
el
-
tab
-
pane
label
=
"事件上报"
name
=
"event"
>
<
ContentWrap
>
<
ContentWrap
>
<
el
-
table
v
-
loading
=
"loading"
:
data
=
"eventList"
:
stripe
=
"true"
>
<
!--
<
el
-
table
v
-
loading
=
"loading"
:
data
=
"eventList"
:
stripe
=
"true"
>
<
el
-
table
-
column
label
=
"功能名称"
align
=
"center"
prop
=
"name"
/>
<
el
-
table
-
column
label
=
"功能名称"
align
=
"center"
prop
=
"name"
/>
<
el
-
table
-
column
label
=
"标识符"
align
=
"center"
prop
=
"identifier"
/>
<
el
-
table
-
column
label
=
"标识符"
align
=
"center"
prop
=
"identifier"
/>
<
el
-
table
-
column
label
=
"数据类型"
align
=
"center"
prop
=
"dataType"
/>
<
el
-
table
-
column
label
=
"数据类型"
align
=
"center"
prop
=
"dataType"
/>
...
@@ -115,7 +115,7 @@
...
@@ -115,7 +115,7 @@
<
/el-table
>
<
/el-table
>
<
div
class
=
"mt-10px"
>
<
div
class
=
"mt-10px"
>
<
el
-
button
type
=
"primary"
@
click
=
"handleEventReport"
>
发送
<
/el-button
>
<
el
-
button
type
=
"primary"
@
click
=
"handleEventReport"
>
发送
<
/el-button
>
<
/div
>
<
/div>
--
>
<
/ContentWrap
>
<
/ContentWrap
>
<
/el-tab-pane
>
<
/el-tab-pane
>
...
@@ -141,7 +141,7 @@
...
@@ -141,7 +141,7 @@
<!--
属性调试
-->
<!--
属性调试
-->
<
el
-
tab
-
pane
label
=
"属性调试"
name
=
"propertyDebug"
>
<
el
-
tab
-
pane
label
=
"属性调试"
name
=
"propertyDebug"
>
<
ContentWrap
>
<
ContentWrap
>
<
el
-
table
v
-
loading
=
"loading"
:
data
=
"propertyList"
:
stripe
=
"true"
>
<
!--
<
el
-
table
v
-
loading
=
"loading"
:
data
=
"propertyList"
:
stripe
=
"true"
>
<
el
-
table
-
column
label
=
"功能名称"
align
=
"center"
prop
=
"name"
/>
<
el
-
table
-
column
label
=
"功能名称"
align
=
"center"
prop
=
"name"
/>
<
el
-
table
-
column
label
=
"标识符"
align
=
"center"
prop
=
"identifier"
/>
<
el
-
table
-
column
label
=
"标识符"
align
=
"center"
prop
=
"identifier"
/>
<
el
-
table
-
column
label
=
"数据类型"
align
=
"center"
prop
=
"dataType"
/>
<
el
-
table
-
column
label
=
"数据类型"
align
=
"center"
prop
=
"dataType"
/>
...
@@ -159,7 +159,7 @@
...
@@ -159,7 +159,7 @@
<
/el-table
>
<
/el-table
>
<
div
class
=
"mt-10px"
>
<
div
class
=
"mt-10px"
>
<
el
-
button
type
=
"primary"
@
click
=
"handlePropertyGet"
>
获取
<
/el-button
>
<
el
-
button
type
=
"primary"
@
click
=
"handlePropertyGet"
>
获取
<
/el-button
>
<
/div
>
<
/div>
--
>
<
/ContentWrap
>
<
/ContentWrap
>
<
/el-tab-pane
>
<
/el-tab-pane
>
...
@@ -178,7 +178,7 @@
...
@@ -178,7 +178,7 @@
<
el
-
col
:
span
=
"12"
>
<
el
-
col
:
span
=
"12"
>
<
el
-
tabs
type
=
"border-card"
>
<
el
-
tabs
type
=
"border-card"
>
<
el
-
tab
-
pane
label
=
"设备日志"
>
<
el
-
tab
-
pane
label
=
"设备日志"
>
<
DeviceDetailsLog
:
device
-
id
=
"device.id
"
/>
<
DeviceDetailsLog
:
device
Key
=
"device.deviceKey
"
/>
<
/el-tab-pane
>
<
/el-tab-pane
>
<
/el-tabs
>
<
/el-tabs
>
<
/el-col
>
<
/el-col
>
...
@@ -188,7 +188,7 @@
...
@@ -188,7 +188,7 @@
<
script
setup
lang
=
"ts"
>
<
script
setup
lang
=
"ts"
>
import
{
ProductVO
}
from
'@/api/iot/product/product'
import
{
ProductVO
}
from
'@/api/iot/product/product'
import
{
ThingModelApi
,
ThingModelData
}
from
'@/api/iot/thingmodel'
import
{
ThingModelApi
,
ThingModelData
,
SimulatorData
}
from
'@/api/iot/thingmodel'
import
{
DeviceApi
,
DeviceVO
,
SimulatorDataVO
}
from
'@/api/iot/device/device'
import
{
DeviceApi
,
DeviceVO
,
SimulatorDataVO
}
from
'@/api/iot/device/device'
import
DeviceDetailsLog
from
'./DeviceDetailsLog.vue'
import
DeviceDetailsLog
from
'./DeviceDetailsLog.vue'
import
{
import
{
...
@@ -212,9 +212,6 @@ const dataTypeOptionsLabel = computed(() => (value: string) => getDataTypeOption
...
@@ -212,9 +212,6 @@ const dataTypeOptionsLabel = computed(() => (value: string) => getDataTypeOption
const
props
=
defineProps
<
{
product
:
ProductVO
;
device
:
DeviceVO
}
>
()
const
props
=
defineProps
<
{
product
:
ProductVO
;
device
:
DeviceVO
}
>
()
const
list
=
ref
<
SimulatorData
[]
>
([])
// 物模型列表的数据
const
list
=
ref
<
SimulatorData
[]
>
([])
// 物模型列表的数据
interface
SimulatorData
extends
ThingModelData
{
simulateValue
?:
string
|
number
// 用于存储模拟值
}
/** 查询列表 */
/** 查询列表 */
const
getList
=
async
()
=>
{
const
getList
=
async
()
=>
{
loading
.
value
=
true
loading
.
value
=
true
...
@@ -231,33 +228,33 @@ const getList = async () => {
...
@@ -231,33 +228,33 @@ const getList = async () => {
}
}
}
}
// 功能列表数据结构定义
//
//
功能列表数据结构定义
interface
TableItem
{
//
interface TableItem
{
name
:
string
//
name: string
identifier
:
string
//
identifier: string
value
:
string
|
number
//
value: string | number
}
//
}
// 添加计算属性来过滤物模型数据
//
//
添加计算属性来过滤物模型数据
const
propertyList
=
computed
(()
=>
{
//
const propertyList = computed(() =>
{
return
list
.
value
//
return list.value
.
filter
((
item
)
=>
item
.
type
===
'property'
)
//
.filter((item) => item.type === 'property')
.
map
((
item
)
=>
({
//
.map((item) => (
{
name
:
item
.
name
,
//
name: item.name,
identifier
:
item
.
identifier
,
//
identifier: item.identifier,
value
:
''
//
value: ''
}
))
//
}
))
}
)
//
}
)
const
eventList
=
computed
(()
=>
{
//
const eventList = computed(() =>
{
return
list
.
value
//
return list.value
.
filter
((
item
)
=>
item
.
type
===
'event'
)
//
.filter((item) => item.type === 'event')
.
map
((
item
)
=>
({
//
.map((item) => (
{
name
:
item
.
name
,
//
name: item.name,
identifier
:
item
.
identifier
,
//
identifier: item.identifier,
value
:
''
//
value: ''
}
))
//
}
))
}
)
//
}
)
// 监听标签页变化 todo:后续改成查询字典
// 监听标签页变化 todo:后续改成查询字典
watch
(
watch
(
...
@@ -291,16 +288,16 @@ watch(
...
@@ -291,16 +288,16 @@ watch(
{
immediate
:
true
}
{
immediate
:
true
}
)
)
interface
ReportData
{
//
interface ReportData
{
productKey
:
string
//
productKey: string
deviceKey
:
string
//
deviceKey: string
type
:
string
//
type: string
subType
:
string
//
subType: string
reportTime
:
string
//
reportTime: string
content
:
string
// 改为 string 类型,存储 JSON 字符串
//
content: string // 改为 string 类型,存储 JSON 字符串
}
//
}
// 处理属性上报
// 处理属性上报
TODO:数据类型效验
const
handlePropertyReport
=
async
()
=>
{
const
handlePropertyReport
=
async
()
=>
{
const
contentObj
:
Record
<
string
,
any
>
=
{
}
const
contentObj
:
Record
<
string
,
any
>
=
{
}
list
.
value
.
forEach
((
item
)
=>
{
list
.
value
.
forEach
((
item
)
=>
{
...
@@ -315,71 +312,67 @@ const handlePropertyReport = async () => {
...
@@ -315,71 +312,67 @@ const handlePropertyReport = async () => {
deviceKey
:
props
.
device
.
deviceKey
,
deviceKey
:
props
.
device
.
deviceKey
,
type
:
'property'
,
type
:
'property'
,
subType
:
'report'
,
subType
:
'report'
,
reportTime
:
new
Date
().
toISOString
(),
reportTime
:
Date
.
now
(),
// 将 reportTime 变为数字类型的时间戳
content
:
JSON
.
stringify
(
contentObj
)
// 转换为 JSON 字符串
content
:
JSON
.
stringify
(
contentObj
)
// 转换为 JSON 字符串
}
}
try
{
try
{
// TODO: 调用API发送数据
await
DeviceApi
.
simulatorDevice
(
reportData
)
console
.
log
(
'上报数据:'
,
reportData
)
message
.
success
(
'属性上报成功'
)
console
.
log
(
'reportData.content'
,
reportData
.
content
)
const
data
=
await
DeviceApi
.
simulatorDevice
(
reportData
)
console
.
log
(
data
)
message
.
success
(
'属性上报成功123'
)
}
catch
(
error
)
{
}
catch
(
error
)
{
message
.
error
(
'属性上报失败'
)
message
.
error
(
'属性上报失败'
)
}
}
}
}
// 处理事件上报
//
//
处理事件上报
const
handleEventReport
=
async
()
=>
{
//
const handleEventReport = async () =>
{
const
contentObj
:
Record
<
string
,
any
>
=
{
}
//
const contentObj: Record<string, any> =
{
}
list
.
value
//
list.value
.
filter
(
item
=>
item
.
type
===
'event'
)
//
.filter(item => item.type === 'event')
.
forEach
((
item
)
=>
{
//
.forEach((item) =>
{
if
(
item
.
simulateValue
!==
undefined
&&
item
.
simulateValue
!==
''
)
{
//
if (item.simulateValue !== undefined && item.simulateValue !== '')
{
contentObj
[
item
.
identifier
]
=
item
.
simulateValue
//
contentObj[item.identifier] = item.simulateValue
}
//
}
}
)
//
}
)
const
reportData
:
ReportData
=
{
//
const reportData: ReportData =
{
productKey
:
props
.
product
.
productKey
,
//
productKey: props.product.productKey,
deviceKey
:
props
.
device
.
deviceKey
,
//
deviceKey: props.device.deviceKey,
type
:
'event'
,
//
type: 'event',
subType
:
list
.
value
.
find
(
item
=>
item
.
type
===
'event'
)?.
identifier
||
''
,
//
subType: list.value.find(item => item.type === 'event')?.identifier || '',
reportTime
:
new
Date
().
toISOString
(),
//
reportTime: new Date().toISOString(),
content
:
JSON
.
stringify
(
contentObj
)
// 转换为 JSON 字符串
//
content: JSON.stringify(contentObj) // 转换为 JSON 字符串
}
//
}
try
{
//
try
{
// TODO: 调用API发送数据
//
// TODO: 调用API发送数据
console
.
log
(
'上报数据:'
,
reportData
)
//
console.log('上报数据:', reportData)
message
.
success
(
'事件上报成功'
)
//
message.success('事件上报成功')
}
catch
(
error
)
{
//
}
catch
(
error
)
{
message
.
error
(
'事件上报失败'
)
//
message.error('事件上报失败')
}
//
}
}
//
}
// 处理设备状态变更
//
//
处理设备状态变更
const
handleDeviceState
=
async
(
state
:
'online'
|
'offline'
)
=>
{
//
const handleDeviceState = async (state: 'online' | 'offline') =>
{
const
reportData
:
ReportData
=
{
//
const reportData: ReportData =
{
productKey
:
props
.
product
.
productKey
,
//
productKey: props.product.productKey,
deviceKey
:
props
.
device
.
deviceKey
,
//
deviceKey: props.device.deviceKey,
type
:
'status'
,
//
type: 'status',
subType
:
state
,
//
subType: state,
reportTime
:
new
Date
().
toISOString
(),
//
reportTime: new Date().toISOString(),
content
:
JSON
.
stringify
({
status
:
state
}
)
// 转换为 JSON 字符串
//
content: JSON.stringify(
{
status
:
state
}
)
// 转换为 JSON 字符串
}
//
}
try
{
//
try
{
// TODO: 调用API发送数据
//
// TODO: 调用API发送数据
console
.
log
(
'状态变更数据:'
,
reportData
)
//
console.log('状态变更数据:', reportData)
console
.
log
(
'reportData.content111111111'
,
reportData
.
content
)
//
console.log('reportData.content111111111', reportData.content)
message
.
success
(
`设备${state === 'online' ? '上线' : '下线'
}
成功`
)
//
message.success(`设备$
{
state
===
'online'
?
'上线'
:
'下线'
}成功
`)
}
catch
(
error
)
{
//
}
catch (error) {
message
.
error
(
`设备${state === 'online' ? '上线' : '下线'
}
失败`
)
//
message.error(`
设备
$
{
state
===
'online'
?
'上线'
:
'下线'
}失败
`)
}
//
}
}
//
}
// 处理属性获取
// 处理属性获取
const handlePropertyGet = async () => {
const handlePropertyGet = async () => {
...
...
src/views/iot/device/device/detail/index.vue
View file @
c9e00d97
...
@@ -17,7 +17,7 @@
...
@@ -17,7 +17,7 @@
<el-tab-pane
label=
"子设备管理"
v-if=
"product.deviceType === DeviceTypeEnum.GATEWAY"
/>
<el-tab-pane
label=
"子设备管理"
v-if=
"product.deviceType === DeviceTypeEnum.GATEWAY"
/>
<el-tab-pane
label=
"设备影子"
/>
<el-tab-pane
label=
"设备影子"
/>
<el-tab-pane
label=
"设备日志"
name=
"log"
>
<el-tab-pane
label=
"设备日志"
name=
"log"
>
<DeviceDetailsLog
v-if=
"activeTab === 'log'"
:product=
"product"
:device=
"device
"
/>
<DeviceDetailsLog
v-if=
"activeTab === 'log'"
:deviceKey=
"device.deviceKey
"
/>
</el-tab-pane>
</el-tab-pane>
<el-tab-pane
label=
"模拟设备"
name=
"simulator"
>
<el-tab-pane
label=
"模拟设备"
name=
"simulator"
>
<DeviceDetailsSimulator
v-if=
"activeTab === 'simulator'"
:product=
"product"
:device=
"device"
/>
<DeviceDetailsSimulator
v-if=
"activeTab === 'simulator'"
:product=
"product"
:device=
"device"
/>
...
...
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