Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
小 白蛋
Wecube Platform
Commits
01332172
Unverified
Commit
01332172
authored
4 years ago
by
Jingyu FENG
Committed by
GitHub
4 years ago
Browse files
Options
Download
Plain Diff
Merge pull request #2003 from WeBankPartners/2000_bug_fix
2000 bug fix
parents
2a0c62a3
7d673ef7
Changes
4
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
wecube-portal/src/pages/collaboration/components/plugin-register.vue
+43
-41
...al/src/pages/collaboration/components/plugin-register.vue
wecube-portal/src/pages/collaboration/plugin-management.vue
+47
-35
wecube-portal/src/pages/collaboration/plugin-management.vue
wecube-portal/src/pages/implementation/batch-execution copy.vue
+1484
-0
...-portal/src/pages/implementation/batch-execution copy.vue
wecube-portal/src/pages/implementation/batch-execution.vue
+100
-37
wecube-portal/src/pages/implementation/batch-execution.vue
with
1674 additions
and
113 deletions
+1674
-113
wecube-portal/src/pages/collaboration/components/plugin-register.vue
+
43
-
41
View file @
01332172
<
template
>
<div
class=
"plugin-register-page"
>
<Row>
<Col
span=
"6"
style=
"border-right: 1px solid #e8eaec"
>
<div
v-if=
"plugins.length
<
1"
>
{{
$t
(
'
no_plugin
'
)
}}
</div>
<Col
span=
"6"
style=
"border-right: 1px solid #e8eaec;"
>
<div
style=
"height: calc(100vh - 180px);overflow-y:auto;"
>
<Menu
theme=
"light"
:active-name=
"currentPlugin"
@
on-select=
"selectPlugin"
style=
"width: 100%;z-index:10"
>
<Submenu
v-for=
"(plugin, index) in plugins"
:name=
"plugin.pluginConfigName"
style=
"padding: 0;"
:key=
"index"
>
<template
slot=
"title"
>
<Icon
type=
"md-grid"
/>
<span
style=
"font-size: 15px;"
>
{{
plugin
.
pluginConfigName
}}
</span>
<div
style=
"float:right;color: #2d8cf0;margin-right:30px"
>
<Tooltip
:content=
"$t('add')"
:delay=
"1000"
>
<Icon
@
click.stop.prevent=
"addPluginConfigDto(plugin)"
style=
""
type=
"md-add"
/>
</Tooltip>
</div>
</
template
>
<MenuItem
v-for=
"(dto, index) in plugin.pluginConfigDtoList.filter(dto => dto.registerName)"
:name=
"dto.id"
<div
v-if=
"plugins.length
<
1"
>
{{
$t
(
'
no_plugin
'
)
}}
</div>
<div
style=
""
>
<Menu
theme=
"light"
:active-name=
"currentPlugin"
@
on-select=
"selectPlugin"
style=
"width: 100%;z-index:10"
>
<Submenu
v-for=
"(plugin, index) in plugins"
:name=
"plugin.pluginConfigName"
style=
"padding: 0;"
:key=
"index"
style=
"padding: 5px 30px;"
>
<span
style=
"display: inline-block;white-space: nowrap; overflow: hidden; text-overflow: ellipsis;font-size: 15px; font-weight:400"
>
{{ dto.registerName }}
</span
<template
slot=
"title"
>
<Icon
type=
"md-grid"
/>
<span
style=
"font-size: 15px;"
>
{{
plugin
.
pluginConfigName
}}
</span>
<div
style=
"float:right;color: #2d8cf0;margin-right:30px"
>
<Tooltip
:content=
"$t('add')"
:delay=
"1000"
>
<Icon
@
click.stop.prevent=
"addPluginConfigDto(plugin)"
style=
""
type=
"md-add"
/>
</Tooltip>
</div>
</
template
>
<MenuItem
v-for=
"(dto, index) in plugin.pluginConfigDtoList.filter(dto => dto.registerName)"
:name=
"dto.id"
:key=
"index"
style=
"padding: 5px 30px;"
>
<div
style=
"vertical-align: top;display: inline-block;float: right;"
>
<Tooltip
:content=
"$t('copy')"
:delay=
"500"
>
<Icon
size=
"16"
style=
"color: #19be6b;"
@
click.stop.prevent=
"copyPluginConfigDto(dto.id)"
type=
"md-copy"
/>
</Tooltip>
<Tooltip
:content=
"$t('config_permission')"
:delay=
"500"
>
<Icon
size=
"16"
style=
"color: #2db7f5;"
@
click=
"permissionsHandler(dto)"
type=
"md-contacts"
/>
</Tooltip>
</div>
</MenuItem>
</Submenu>
</Menu>
<span
style=
"display: inline-block;white-space: nowrap; overflow: hidden; text-overflow: ellipsis;font-size: 15px; font-weight:400"
>
{{ dto.registerName }}
</span
>
<div
style=
"vertical-align: top;display: inline-block;float: right;"
>
<Tooltip
:content=
"$t('copy')"
:delay=
"500"
>
<Icon
size=
"16"
style=
"color: #19be6b;"
@
click.stop.prevent=
"copyPluginConfigDto(dto.id)"
type=
"md-copy"
/>
</Tooltip>
<Tooltip
:content=
"$t('config_permission')"
:delay=
"500"
>
<Icon
size=
"16"
style=
"color: #2db7f5;"
@
click=
"permissionsHandler(dto)"
type=
"md-contacts"
/>
</Tooltip>
</div>
</MenuItem>
</Submenu>
</Menu>
</div>
</div>
<div
style=
"padding-right: 20px;margin-top: 10px;"
>
<Button
type=
"info"
long
ghost
@
click=
"batchRegist"
>
{{ $t('batch_regist') }}
</Button>
...
...
This diff is collapsed.
Click to expand it.
wecube-portal/src/pages/collaboration/plugin-management.vue
+
47
-
35
View file @
01332172
...
...
@@ -62,41 +62,45 @@
v-if=
"plugin.status !== 'DECOMMISSIONED' || isShowDecomissionedPackage"
:key=
"plugin.id"
>
<span
:class=
"plugin.status !== 'DECOMMISSIONED' ? '' : 'decomissionedPkgName'"
>
{{
plugin
.
name
+
'
_
'
+
plugin
.
version
}}
</span>
<span
style=
"float: right; margin-right: 10px"
>
<Tooltip
:content=
"$t('configuration_import')"
>
<Button
icon=
"ios-cloud-upload-outline"
v-if=
"plugin.status !== 'DECOMMISSIONED'"
size=
"small"
type=
"primary"
ghost
@
click.stop.prevent=
"importBestPractices(plugin.id)"
></Button>
</Tooltip>
<Tooltip
:content=
"$t('configuration_export')"
>
<Button
v-if=
"plugin.status !== 'DECOMMISSIONED'"
@
click.stop.prevent=
"exportBestPractices(plugin.id)"
size=
"small"
type=
"primary"
ghost
icon=
"md-download"
></Button>
</Tooltip>
<Tooltip
:content=
"$t('delete')"
>
<Button
v-if=
"plugin.status !== 'DECOMMISSIONED'"
@
click.stop.prevent=
"deletePlugin(plugin.id)"
size=
"small"
type=
"error"
ghost
icon=
"ios-trash"
></Button>
</Tooltip>
</span>
<div
style=
"float: right;width: calc(100% - 30px);"
>
<span
:class=
"plugin.status !== 'DECOMMISSIONED' ? 'plugin-title' : 'decomissionedPkgName plugin-title'"
>
{{
plugin
.
name
+
'
_
'
+
plugin
.
version
}}
</span>
<span
style=
"float: right; margin-right: 10px"
>
<Tooltip
:content=
"$t('configuration_import')"
>
<Button
icon=
"ios-cloud-upload-outline"
v-if=
"plugin.status !== 'DECOMMISSIONED'"
size=
"small"
type=
"primary"
ghost
@
click.stop.prevent=
"importBestPractices(plugin.id)"
></Button>
</Tooltip>
<Tooltip
:content=
"$t('configuration_export')"
>
<Button
v-if=
"plugin.status !== 'DECOMMISSIONED'"
@
click.stop.prevent=
"exportBestPractices(plugin.id)"
size=
"small"
type=
"primary"
ghost
icon=
"md-download"
></Button>
</Tooltip>
<Tooltip
:content=
"$t('delete')"
>
<Button
v-if=
"plugin.status !== 'DECOMMISSIONED'"
@
click.stop.prevent=
"deletePlugin(plugin.id)"
size=
"small"
type=
"error"
ghost
icon=
"ios-trash"
></Button>
</Tooltip>
</span>
</div>
<p
slot=
"content"
class=
"button-group"
>
<Button
@
click=
"configPlugin(plugin.id)"
size=
"small"
type=
"info"
ghost
icon=
"ios-checkmark-circle"
>
{{
$t
(
'
plugin_config_check
'
)
}}
...
...
@@ -952,4 +956,12 @@ export default {
.clear-default-css
{
margin-bottom
:
0
;
}
.plugin-title
{
width
:
calc
(
100%
-
110px
);
display
:
block
;
float
:
left
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
</
style
>
This diff is collapsed.
Click to expand it.
wecube-portal/src/pages/implementation/batch-execution copy.vue
0 → 100644
+
1484
-
0
View file @
01332172
This diff is collapsed.
Click to expand it.
wecube-portal/src/pages/implementation/batch-execution.vue
+
100
-
37
View file @
01332172
...
...
@@ -105,6 +105,7 @@
<Icon
type=
"ios-information-circle-outline"
/>
<div
slot=
"content"
style=
"white-space: normal;"
>
<p
style=
"word-break: break-all"
:key=
"targetIndex"
v-for=
"(target, targetIndex) in activeExecuteHistory.requestBody.resourceDatas"
>
...
...
@@ -133,7 +134,7 @@
<Button
size=
"small"
@
click=
"changePlugin"
:disabled=
"!activeExecuteHistory.requestBody.
packageName
"
:disabled=
"!activeExecuteHistory.requestBody.
resourceDatas.length > 0
"
type=
"primary"
ghost
>
{{
$t
(
'
bc_change_plugin
'
)
}}
</Button
...
...
@@ -234,8 +235,16 @@
</Row>
</section>
<Modal
v-model=
"operaModal"
:mask-closable=
"false"
:title=
"$t('bc_operation')"
:width=
"1000"
class=
"opera-modal"
>
<Modal
v-model=
"operaModal"
:mask-closable=
"false"
:title=
"$t('bc_operation')"
:width=
"1000"
:closable=
"false"
class=
"opera-modal"
>
<div
style=
"height:400px;"
>
<!-- 设置查询参数-开始 -->
<section
v-if=
"displaySearchZone"
class=
"search"
>
<Form
:label-width=
"130"
label-colon
>
<FormItem
:rules=
"
{ required: true }" :show-message="false" :label="$t('bc_query_path')">
...
...
@@ -248,11 +257,10 @@
</FormItem>
<FormItem
:label=
"$t('bc_target_type')"
>
<Input
disabled
:value=
"currentPackageName + ':' + currentEntityName"
></Input>
<!--
<span
v-if=
"currentPackageName"
></span>
-->
</FormItem>
<FormItem
:rules=
"
{ required: true }" :show-message="false" :label="$t('bc_primary_key')">
<Select
filterable
v-model=
"
currentEntit
yAttr"
>
<Option
v-for=
"entityAttr in
currentEntit
yAttrList"
:value=
"entityAttr.name"
:key=
"entityAttr.id"
>
{{
<Select
filterable
v-model=
"
primatKe
yAttr"
>
<Option
v-for=
"entityAttr in
primatKe
yAttrList"
:value=
"entityAttr.name"
:key=
"entityAttr.id"
>
{{
entityAttr
.
name
}}
</Option>
</Select>
...
...
@@ -265,7 +273,7 @@
{{
$t
(
'
bc_table_column
'
)
}}
</span>
<Select
filterable
multiple
v-model=
"userTableColumns"
>
<Option
v-for=
"entityAttr in
currentEntit
yAttrList"
:value=
"entityAttr.name"
:key=
"entityAttr.id"
>
{{
<Option
v-for=
"entityAttr in
primatKe
yAttrList"
:value=
"entityAttr.name"
:key=
"entityAttr.id"
>
{{
entityAttr
.
name
}}
</Option>
</Select>
...
...
@@ -309,6 +317,9 @@
</FormItem>
</Form>
</section>
<!-- 设置查询参数-结束 -->
<!-- 选择执行对象-开始 -->
<section
v-if=
"displayResultTableZone"
class=
"search-result-table"
style=
"margin-top:20px;"
>
<div
style=
"margin-bottom:8px"
>
<Input
v-model=
"filterTableParams"
:placeholder=
"$t('enter_search_keywords')"
style=
"width: 300px"
/>
...
...
@@ -332,16 +343,15 @@
></Table>
</div>
</Card>
<!--
<a
v-else
@
click=
"reExcute('displayResultTableZone')"
>
{{
$t
(
'
bc_find
'
)
}}
{{
tableData
.
length
}}
{{
$t
(
'
bc_instance
'
)
}}
,
{{
$t
(
'
bc_selected
'
)
}}{{
seletedRowsNum
}}{{
$t
(
'
bc_item
'
)
}}
,
{{
$t
(
'
full_word_exec
'
)
}}{{
pluginId
}}
</a>
-->
</div>
</section>
<!-- 选择执行对象-结束 -->
<!-- 选择插件配置参数-开始 -->
<section
v-if=
"batchActionModalVisible"
>
<Form
label-position=
"right"
:label-width=
"150"
>
<FormItem
:label=
"$t('plugin')"
:rules=
"
{ required: true }" :show-message="false">
<Select
filterable
clearable
v-model=
"pluginId"
>
<Select
filterable
clearable
v-model=
"pluginId"
@
on-clear=
"clearPlugin"
>
<Option
v-for=
"(item, index) in filteredPlugins"
:value=
"item.serviceName"
:key=
"index"
>
{{
item
.
serviceDisplayName
}}
</Option>
...
...
@@ -357,6 +367,9 @@
</div>
</Form>
</section>
<!-- 选择插件配置参数-结束 -->
<!-- 补充参数-开始 -->
<section
v-if=
"setPluginParamsModal"
>
<Form
label-position=
"right"
:label-width=
"150"
v-if=
"!!activeExecuteHistory.plugin"
>
<
template
v-for=
"(item, index) in activeExecuteHistory.plugin.pluginParams"
>
...
...
@@ -367,13 +380,14 @@
</
template
>
</Form>
</section>
<!-- 补充参数-结束 -->
</div>
<div
slot=
"footer"
>
<!-- 查询table数据 -->
<Button
type=
"primary"
v-if=
"displaySearchZone"
:disabled=
"!(!!currentPackageName && !!currentEntityName && !!
currentEntit
yAttr)"
:disabled=
"!(!!currentPackageName && !!currentEntityName && !!
primatKe
yAttr)"
@
click=
"excuteSearch"
>
{{ $t('bc_execute_query') }}
</Button
>
...
...
@@ -389,6 +403,10 @@
<Button
type=
"primary"
v-if=
"setPluginParamsModal"
@
click=
"executeAgain"
:loading=
"btnLoading"
>
{{ $t('full_word_exec') }}
</Button>
<!-- 放弃功能 -->
<Button
@
click=
"closeModal"
>
{{ $t('cancle') }}
</Button>
</div>
</Modal>
<Modal
v-model=
"collectionRoleManageModal"
width=
"700"
:title=
"$t('bc_edit_role')"
:mask-closable=
"false"
>
...
...
@@ -472,8 +490,8 @@ export default {
dataModelExpression
:
'
:
'
,
currentEntityName
:
''
,
currentPackageName
:
''
,
currentEntit
yAttr
:
''
,
currentEntit
yAttrList
:
[],
primatKe
yAttr
:
''
,
primatKe
yAttrList
:
[],
allEntityAttr
:
[],
targetEntityAttr
:
[],
...
...
@@ -508,7 +526,8 @@ export default {
pluginParams
:
[]
},
requestBody
:
{
searchParameters
:
[]
searchParameters
:
[],
resourceDatas
:
[]
},
filterBusinessKeySet
:
[]
},
...
...
@@ -518,7 +537,8 @@ export default {
pluginParams
:
[]
},
requestBody
:
{
searchParameters
:
[]
searchParameters
:
[],
resourceDatas
:
[]
},
filterBusinessKeySet
:
[]
},
...
...
@@ -572,7 +592,7 @@ export default {
if
(
status
===
'
OK
'
)
{
this
.
currentEntityName
=
data
.
slice
(
-
1
)[
0
].
entityName
this
.
currentPackageName
=
data
.
slice
(
-
1
)[
0
].
packageName
this
.
currentEntit
yAttrList
=
data
.
slice
(
-
1
)[
0
].
attributes
this
.
primatKe
yAttrList
=
data
.
slice
(
-
1
)[
0
].
attributes
this
.
allEntityAttr
=
[]
data
.
forEach
((
single
,
index
)
=>
{
...
...
@@ -609,7 +629,7 @@ export default {
this
.
filterParams
=
null
this
.
businessKey
=
null
if
(
!
val
)
{
this
.
activeExecuteHistory
=
this
.
defaultActiveExecuteHistory
this
.
activeExecuteHistory
=
JSON
.
parse
(
JSON
.
stringify
(
this
.
defaultActiveExecuteHistory
))
this
.
catchExecuteResult
=
{}
this
.
catchFilterBusinessKeySet
=
[]
this
.
dataModelExpression
=
'
:
'
...
...
@@ -956,12 +976,15 @@ export default {
}
// this.$refs.select.setQuery(null)
this
.
dataModelExpression
=
'
:
'
this
.
currentEntit
yAttr
=
null
this
.
currentEntit
yAttrList
=
[]
this
.
primatKe
yAttr
=
null
this
.
primatKe
yAttrList
=
[]
this
.
currentPackageName
=
''
this
.
currentEntityName
=
''
this
.
allEntityAttr
=
[]
this
.
targetEntityAttr
=
[]
this
.
filterTableParams
=
''
this
.
searchParameters
=
[]
this
.
userTableColumns
=
[]
this
.
isShowSearchConditions
=
true
},
changeEntityType
()
{
...
...
@@ -991,7 +1014,7 @@ export default {
this
.
searchParameters
=
this
.
targetEntityAttr
},
saveSearchCondition
()
{
if
(
!
this
.
currentEntit
yAttr
)
{
if
(
!
this
.
primatKe
yAttr
)
{
this
.
$Message
.
warning
(
this
.
$t
(
'
bc_primary_key
'
)
+
this
.
$t
(
'
bc_warn_empty
'
))
return
}
...
...
@@ -1134,7 +1157,15 @@ export default {
this
[
this
.
DelConfig
.
key
]
=
true
},
batchAction
()
{
this
.
activeExecuteHistory
.
requestBody
.
resourceDatas
=
this
.
seletedRows
.
map
(
_
=>
{
return
{
id
:
_
.
id
,
businessKeyValue
:
_
[
this
.
activeExecuteHistory
.
requestBody
.
primatKeyAttr
]
}
})
this
.
displayResultTableZone
=
false
this
.
getFilteredPluginInterfaceList
()
this
.
batchActionModalVisible
=
true
this
.
selectedPluginParams
=
[]
...
...
@@ -1164,6 +1195,7 @@ export default {
packageName
,
entityName
,
dataModelExpression
,
primatKeyAttr
,
searchParameters
,
businessKeyAttribute
,
resourceDatas
...
...
@@ -1172,6 +1204,7 @@ export default {
packageName
,
entityName
,
dataModelExpression
,
primatKeyAttr
,
searchParameters
,
pluginConfigInterface
:
plugin
,
inputParameterDefinitions
,
...
...
@@ -1179,19 +1212,20 @@ export default {
resourceDatas
}
}
else
{
let
currentEntity
=
this
.
currentEntit
yAttrList
.
find
(
_
=>
{
return
_
.
name
===
this
.
currentEntit
yAttr
let
currentEntity
=
this
.
primatKe
yAttrList
.
find
(
_
=>
{
return
_
.
name
===
this
.
primatKe
yAttr
})
const
resourceDatas
=
this
.
seletedRows
.
map
(
_
=>
{
return
{
id
:
_
.
id
,
businessKeyValue
:
_
[
this
.
currentEntit
yAttr
]
businessKeyValue
:
_
[
this
.
primatKe
yAttr
]
}
})
requestBody
=
{
packageName
:
this
.
currentPackageName
,
entityName
:
this
.
currentEntityName
,
dataModelExpression
:
this
.
dataModelExpression
,
primatKeyAttr
:
this
.
primatKeyAttr
,
searchParameters
:
this
.
searchParameters
,
pluginConfigInterface
:
plugin
,
inputParameterDefinitions
,
...
...
@@ -1303,8 +1337,9 @@ export default {
)
if
(
status
===
'
OK
'
)
{
this
.
filteredPlugins
=
data
this
.
selectedPluginParams
=
[]
this
.
pluginId
=
null
// this.selectedPluginParams = []
// this.pluginId = null
this
.
clearPlugin
()
this
.
displaySearchZone
=
false
this
.
displayResultTableZone
=
false
...
...
@@ -1315,39 +1350,66 @@ export default {
}
},
changeTargetObject
()
{
this
.
displaySearchZone
=
false
this
.
displayResultTableZone
=
true
this
.
batchActionModalVisible
=
false
this
.
setPluginParamsModal
=
false
this
.
clearTableSelect
()
this
.
operaModal
=
true
this
.
userTableColumns
=
[]
const
{
packageName
,
entityName
,
dataModelExpression
}
=
this
.
activeExecuteHistory
.
requestBody
this
.
currentPackageName
=
packageName
this
.
currentEntityName
=
entityName
this
.
dataModelExpression
=
dataModelExpression
this
.
excuteSearch
()
this
.
displaySearchZone
=
false
this
.
displayResultTableZone
=
true
this
.
batchActionModalVisible
=
false
this
.
setPluginParamsModal
=
false
this
.
operaModal
=
true
},
changeSearchParams
()
{
const
{
dataModelExpression
,
searchParameters
}
=
this
.
activeExecuteHistory
.
requestBody
this
.
searchParameters
=
searchParameters
this
.
dataModelExpression
=
dataModelExpression
// const { dataModelExpression, searchParameters } = this.activeExecuteHistory.requestBody
// this.searchParameters = searchParameters
// this.dataModelExpression = dataModelExpression
this
.
activeExecuteHistory
=
JSON
.
parse
(
JSON
.
stringify
(
this
.
defaultActiveExecuteHistory
))
this
.
setSearchConditions
()
this
.
displaySearchZone
=
true
this
.
displayResultTableZone
=
false
this
.
batchActionModalVisible
=
false
this
.
setPluginParamsModal
=
false
this
.
operaModal
=
true
this
.
filterTableParams
=
''
this
.
setSearchConditions
()
},
changeParams
()
{
this
.
displaySearchZone
=
false
this
.
displayResultTableZone
=
false
this
.
batchActionModalVisible
=
false
this
.
clearComplementParams
()
this
.
setPluginParamsModal
=
true
this
.
operaModal
=
true
},
clearTableSelect
()
{
this
.
seletedRows
=
[]
this
.
activeExecuteHistory
.
requestBody
.
resourceDatas
=
[]
this
.
clearPlugin
()
},
clearPlugin
()
{
this
.
pluginId
=
null
// this.clearComplementParams()
this
.
selectedPluginParams
=
[]
this
.
activeExecuteHistory
.
plugin
.
pluginName
=
''
this
.
activeExecuteHistory
.
plugin
.
pluginParams
=
[]
},
clearComplementParams
()
{
this
.
activeExecuteHistory
.
plugin
.
pluginParams
.
forEach
(
item
=>
{
item
.
bindValue
=
''
})
},
closeModal
()
{
this
.
operaModal
=
false
if
(
this
.
activeExecuteHistoryKey
)
{
this
.
changeActiveExecuteHistory
(
this
.
activeExecuteHistoryKey
)
}
}
},
components
:
{
...
...
@@ -1442,6 +1504,7 @@ pre {
padding
:
4px
16px
;
cursor
:
pointer
;
color
:
#19be6b
;
word-break
:
break-all
;
}
.active-key
{
background
:
#e5e2e2
;
...
...
This diff is collapsed.
Click to expand it.
Write
Preview
Supports
Markdown
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
Menu
Projects
Groups
Snippets
Help