feat:CN-1522 插件管理页开发(细节需要再调整),及代码整理
This commit is contained in:
@@ -81,6 +81,8 @@
|
|||||||
@import 'views/administration/AdministrationTabs';
|
@import 'views/administration/AdministrationTabs';
|
||||||
@import 'views/administration/Appearance.scss';
|
@import 'views/administration/Appearance.scss';
|
||||||
|
|
||||||
|
@import 'views/system/Plugin';
|
||||||
|
|
||||||
@import 'views/setting/knowledgeBase';
|
@import 'views/setting/knowledgeBase';
|
||||||
@import 'views/charts2/entityDetailLine';
|
@import 'views/charts2/entityDetailLine';
|
||||||
@import 'views/charts2/EntityDetailSubscriberKpi.scss';
|
@import 'views/charts2/EntityDetailSubscriberKpi.scss';
|
||||||
|
|||||||
46
src/assets/css/components/views/system/Plugin.scss
Normal file
46
src/assets/css/components/views/system/Plugin.scss
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
.plugin {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #353636;
|
||||||
|
line-height: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
.type-tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 10px;
|
||||||
|
background-color: #EBF7FA;
|
||||||
|
color: #046ECA;
|
||||||
|
box-shadow: 0 2px 4px 0 rgba(51,51,51,0.02);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
.plugin-name {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: left;
|
||||||
|
align-items: center;
|
||||||
|
.icon-background {
|
||||||
|
display:flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width:32px;
|
||||||
|
height:32px;
|
||||||
|
background: #ECECEC;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-right:6px;
|
||||||
|
.plugin-name-icon {
|
||||||
|
width:25px;
|
||||||
|
height:25px;
|
||||||
|
color:red;
|
||||||
|
display:flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.two-line {
|
||||||
|
overflow: hidden; //超出的文本隐藏
|
||||||
|
text-overflow: ellipsis; //溢出用省略号显示
|
||||||
|
display: -webkit-box;
|
||||||
|
line-clamp:2 ;
|
||||||
|
-webkit-line-clamp: 2; // 超出多少行
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "cn-icon"; /* Project id 2614877 */
|
font-family: "cn-icon"; /* Project id 2614877 */
|
||||||
src: url('iconfont.woff2?t=1701934971951') format('woff2'),
|
src: url('iconfont.woff2?t=1703561754372') format('woff2'),
|
||||||
url('iconfont.woff?t=1701934971951') format('woff'),
|
url('iconfont.woff?t=1703561754372') format('woff'),
|
||||||
url('iconfont.ttf?t=1701934971951') format('truetype');
|
url('iconfont.ttf?t=1703561754372') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.cn-icon {
|
.cn-icon {
|
||||||
@@ -13,6 +13,14 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cn-icon-system:before {
|
||||||
|
content: "\e6cc";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-icon-plugin:before {
|
||||||
|
content: "\e6cd";
|
||||||
|
}
|
||||||
|
|
||||||
.cn-icon-IMSI:before {
|
.cn-icon-IMSI:before {
|
||||||
content: "\e812";
|
content: "\e812";
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -132,7 +132,7 @@
|
|||||||
:show-close="false"
|
:show-close="false"
|
||||||
>
|
>
|
||||||
<div class="cn-menu__left" v-if="otherMenu">
|
<div class="cn-menu__left" v-if="otherMenu">
|
||||||
<div class="left-menu" v-for="menu in otherMenu" :key="menu.id" @click="jump(menu.route,'','',0)">
|
<div class="left-menu" v-for="menu in otherMenu" :key="menu.id" @click="jumpOther(menu.route,'','',0)">
|
||||||
<i :class="menu.icon"></i>
|
<i :class="menu.icon"></i>
|
||||||
<span>{{ $t(menu.i18n || menu.name) }}</span>
|
<span>{{ $t(menu.i18n || menu.name) }}</span>
|
||||||
<i class="cn-icon cn-icon-right"></i>
|
<i class="cn-icon cn-icon-right"></i>
|
||||||
|
|||||||
@@ -464,7 +464,7 @@ export default {
|
|||||||
this.myChart.setOption(this.chartOption)
|
this.myChart.setOption(this.chartOption)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
init (val, show, active, n) {
|
init () {
|
||||||
this.psiphon3Loading = true
|
this.psiphon3Loading = true
|
||||||
const endTime = window.$dayJs.tz().valueOf()
|
const endTime = window.$dayJs.tz().valueOf()
|
||||||
const params = {
|
const params = {
|
||||||
|
|||||||
148
src/components/table/system/PluginTable.vue
Normal file
148
src/components/table/system/PluginTable.vue
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
<template>
|
||||||
|
<el-table
|
||||||
|
id="pluginTable"
|
||||||
|
ref="dataTable"
|
||||||
|
:data="tableData"
|
||||||
|
:height="height"
|
||||||
|
empty-text=" "
|
||||||
|
border
|
||||||
|
class="plugin"
|
||||||
|
@header-dragend="dragend"
|
||||||
|
@sort-change="tableDataSort"
|
||||||
|
@selection-change="selectionChange"
|
||||||
|
>
|
||||||
|
<el-table-column
|
||||||
|
v-for="(item, index) in customTableTitles"
|
||||||
|
:key="item.prop+index"
|
||||||
|
:fixed="item.fixed"
|
||||||
|
:label="item.label"
|
||||||
|
:min-width="`${item.minWidth}`"
|
||||||
|
:prop="item.prop"
|
||||||
|
:resizable="true"
|
||||||
|
:sort-orders="['ascending', 'descending']"
|
||||||
|
:sortable="item.sortable"
|
||||||
|
:width="`${item.width}`"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<span class="data-column__span">{{item.label}}</span>
|
||||||
|
<div class="col-resize-area"></div>
|
||||||
|
</template>
|
||||||
|
<template #default="scope" :column="item">
|
||||||
|
<template v-if="item.prop === 'status'">
|
||||||
|
<el-switch
|
||||||
|
v-if="hasPermission('editUser')"
|
||||||
|
v-model="scope.row.status"
|
||||||
|
active-value="1"
|
||||||
|
inactive-value="0"
|
||||||
|
@change="()=>{statusChange(scope.row)}">
|
||||||
|
</el-switch>
|
||||||
|
<template v-else>
|
||||||
|
<span v-if="scope.row.status === '1'">{{$t('detection.create.enabled')}}</span>
|
||||||
|
<span v-else>{{$t('detection.create.disabled')}}</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.prop === 'type'">
|
||||||
|
<span class="type-tag">{{tagSourceText(scope.row[item.prop])}}</span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.prop === 'name'">
|
||||||
|
<div class="plugin-name">
|
||||||
|
<div class="icon-background"><img class="plugin-name-icon" :src="getIconUrl(scope.row['knowledgeId'])"/></div>
|
||||||
|
{{scope.row[item.prop] || '-'}}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.prop === 'description'">
|
||||||
|
<div class="two-line" :title="getDescription(scope.row['knowledgeId'])">{{getDescription(scope.row['knowledgeId'])}}</div>
|
||||||
|
</template>
|
||||||
|
<span v-else>{{scope.row[item.prop] || '-'}}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<template v-slot:empty >
|
||||||
|
<div class="table-no-data" v-if="isNoData">
|
||||||
|
<div class="table-no-data__title">{{ $t('npm.noData') }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import table from '@/mixins/table'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { storageKey, knowledgeBaseSource, builtInKnowledgeBaseBasicInfo } from '@/utils/constants'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'pluginTable',
|
||||||
|
props: {
|
||||||
|
isNoData: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mixins: [table],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
loginName: localStorage.getItem(storageKey.username),
|
||||||
|
tableTitle: [ // 原始table列
|
||||||
|
{
|
||||||
|
label: this.$t('overall.name'),
|
||||||
|
prop: 'name',
|
||||||
|
show: true,
|
||||||
|
minWidth: 100
|
||||||
|
}, {
|
||||||
|
label: this.$t('overall.remark'),
|
||||||
|
prop: 'description',
|
||||||
|
show: true,
|
||||||
|
minWidth: 150
|
||||||
|
}, {
|
||||||
|
label: this.$t('overall.type'),
|
||||||
|
prop: 'type',
|
||||||
|
show: true,
|
||||||
|
minWidth: 150
|
||||||
|
}, {
|
||||||
|
label: this.$t('config.plugin.schedule'),
|
||||||
|
prop: 'schedule',
|
||||||
|
show: true,
|
||||||
|
minWidth: 150
|
||||||
|
}, {
|
||||||
|
label: this.$t('overall.status'),
|
||||||
|
prop: 'status',
|
||||||
|
show: true,
|
||||||
|
minWidth: 200
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
tagSourceText () {
|
||||||
|
return function (type) {
|
||||||
|
const t = knowledgeBaseSource.find(t => t.value === type)
|
||||||
|
return t ? t.name : 'Unknown Tag'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getIconUrl () {
|
||||||
|
return function (knowledgeId) {
|
||||||
|
const basicInfo = builtInKnowledgeBaseBasicInfo.find(bi => bi.knowledgeId === knowledgeId)
|
||||||
|
return basicInfo ? basicInfo.iconUrl : ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getDescription () {
|
||||||
|
return function (knowledgeId) {
|
||||||
|
const basicInfo = builtInKnowledgeBaseBasicInfo.find(bi => bi.knowledgeId === knowledgeId)
|
||||||
|
return basicInfo ? this.$t(basicInfo.desc) : '-'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
statusChange (plugin) {
|
||||||
|
/*
|
||||||
|
axios.put('sys/plugin', user).then(response => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
this.$message({ duration: 1000, type: 'success', message: this.$t('tip.saveSuccess') })
|
||||||
|
} else {
|
||||||
|
this.$message.error(response.data.message)
|
||||||
|
}
|
||||||
|
this.$emit('reload')
|
||||||
|
}) */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -353,7 +353,9 @@ export default {
|
|||||||
},
|
},
|
||||||
dragend () {
|
dragend () {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.$refs.dataTable && this.$refs.dataTable.$refs.dataTable) {
|
if (this.$refs.dataTable && this.$refs.dataTable.$refs &&
|
||||||
|
this.$refs.dataTable.$refs.dataTable
|
||||||
|
) {
|
||||||
this.$refs.dataTable.$refs.dataTable.doLayout()
|
this.$refs.dataTable.$refs.dataTable.doLayout()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export const fromRoute = {
|
|||||||
dnsServiceInsights: 'dnsServiceInsights',
|
dnsServiceInsights: 'dnsServiceInsights',
|
||||||
linkMonitor: 'linkMonitor',
|
linkMonitor: 'linkMonitor',
|
||||||
user: 'user',
|
user: 'user',
|
||||||
|
plugin: 'plugin',
|
||||||
galaxyProxy: 'galaxyProxy',
|
galaxyProxy: 'galaxyProxy',
|
||||||
chart: 'chart',
|
chart: 'chart',
|
||||||
cryptocurrency: 'cryptocurrency',
|
cryptocurrency: 'cryptocurrency',
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ export default {
|
|||||||
}
|
}
|
||||||
this.toggleLoading(true)
|
this.toggleLoading(true)
|
||||||
try {
|
try {
|
||||||
if(this.chartDateObject.length > 0) {
|
if (this.chartDateObject.length > 0) {
|
||||||
this.initData(this.chartDateObject, val, active, show)
|
this.initData(this.chartDateObject, val, active, show)
|
||||||
} else {
|
} else {
|
||||||
this.init(val, show, active)
|
this.init(val, show, active)
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ export default {
|
|||||||
const newVal = val ? _.clone(val) : this.metric
|
const newVal = val ? _.clone(val) : this.metric
|
||||||
this.toggleLoading(true)
|
this.toggleLoading(true)
|
||||||
try {
|
try {
|
||||||
if(this.chartDateObject.length > 0) {
|
if (this.chartDateObject.length > 0) {
|
||||||
this.initData(this.chartDateObject, newVal, active, show, n)
|
this.initData(this.chartDateObject, newVal, active, show, n)
|
||||||
} else {
|
} else {
|
||||||
this.init(val, show, active, n)
|
this.init(val, show, active, n)
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ export default {
|
|||||||
const newVal = val ? _.clone(val) : this.metric
|
const newVal = val ? _.clone(val) : this.metric
|
||||||
this.toggleLoading(true)
|
this.toggleLoading(true)
|
||||||
try {
|
try {
|
||||||
if(this.chartDateObject.length > 0) {
|
if (this.chartDateObject.length > 0) {
|
||||||
this.initData(this.chartDateObject, newVal, active)
|
this.initData(this.chartDateObject, newVal, active)
|
||||||
} else {
|
} else {
|
||||||
this.init(val, active)
|
this.init(val, active)
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ export default {
|
|||||||
const newVal = val ? _.clone(val) : this.metric
|
const newVal = val ? _.clone(val) : this.metric
|
||||||
this.toggleLoading(true)
|
this.toggleLoading(true)
|
||||||
try {
|
try {
|
||||||
if(this.chartDateObject.length > 0) {
|
if (this.chartDateObject.length > 0) {
|
||||||
this.initData(this.chartDateObject, newVal, active, show, n)
|
this.initData(this.chartDateObject, newVal, active, show, n)
|
||||||
} else {
|
} else {
|
||||||
this.init(val, show, active, n)
|
this.init(val, show, active, n)
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ export default {
|
|||||||
const newVal = val ? _.clone(val) : this.metric
|
const newVal = val ? _.clone(val) : this.metric
|
||||||
this.toggleLoading(true)
|
this.toggleLoading(true)
|
||||||
try {
|
try {
|
||||||
if(this.chartDateObject.length > 0) {
|
if (this.chartDateObject.length > 0) {
|
||||||
this.initData(this.chartDateObject, newVal, active, show, n)
|
this.initData(this.chartDateObject, newVal, active, show, n)
|
||||||
} else {
|
} else {
|
||||||
this.init(val, show, active, n)
|
this.init(val, show, active, n)
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ export default {
|
|||||||
}
|
}
|
||||||
this.toggleLoading(true)
|
this.toggleLoading(true)
|
||||||
try {
|
try {
|
||||||
if(this.chartDateObject.length > 0) {
|
if (this.chartDateObject.length > 0) {
|
||||||
this.initLineData(this.chartDateObject, val)
|
this.initLineData(this.chartDateObject, val)
|
||||||
} else {
|
} else {
|
||||||
this.init(val)
|
this.init(val)
|
||||||
|
|||||||
37
src/views/system/Index.vue
Normal file
37
src/views/system/Index.vue
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<div class="administration entity-explorer entity-explorer--show-list">
|
||||||
|
<!-- 顶部工具栏,在列表页显示 -->
|
||||||
|
<div class="explorer-top-tools explorer-detection-top-tools">
|
||||||
|
<div class="explorer-top-tools-title">{{$t('overall.system')}}</div>
|
||||||
|
</div>
|
||||||
|
<div style="width: 100%;padding-bottom: 26px;">
|
||||||
|
<chart-tabs :data="tabsData" router></chart-tabs>
|
||||||
|
</div>
|
||||||
|
<!-- 内容区 -->
|
||||||
|
<div class="explorer-container administration-container">
|
||||||
|
<router-view />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ChartTabs from '@/components/common/ChartTabs'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
export default {
|
||||||
|
name: 'index',
|
||||||
|
components: {
|
||||||
|
ChartTabs
|
||||||
|
},
|
||||||
|
setup () {
|
||||||
|
const store = useStore()
|
||||||
|
const menu = store.getters.menuList.find(m => m.code === 'system')
|
||||||
|
const tabsData = menu.children.map(l => ({
|
||||||
|
...l,
|
||||||
|
path: l.route
|
||||||
|
})).sort((a, b) => a.sort - b.sort)
|
||||||
|
return {
|
||||||
|
tabsData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
108
src/views/system/Plugin.vue
Normal file
108
src/views/system/Plugin.vue
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<template>
|
||||||
|
<div style="height: 100%;">
|
||||||
|
<cn-data-list
|
||||||
|
ref="dataList"
|
||||||
|
:tableId="tableId"
|
||||||
|
v-model:custom-table-title="tools.customTableTitle"
|
||||||
|
:api="url"
|
||||||
|
:from="fromRoute.plugin"
|
||||||
|
:layout="['columnCustomize','search']"
|
||||||
|
@search="search"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<loading :loading="loading"></loading>
|
||||||
|
<plugin-table
|
||||||
|
ref="dataTable"
|
||||||
|
:api="url"
|
||||||
|
:isNoData="isNoData"
|
||||||
|
:custom-table-title="tools.customTableTitle"
|
||||||
|
:height="mainTableHeight"
|
||||||
|
:table-data="tableData"
|
||||||
|
@delete="del"
|
||||||
|
@edit="edit"
|
||||||
|
@orderBy="tableDataSort"
|
||||||
|
@reload="getTableData"
|
||||||
|
@selectionChange="selectionChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #pagination>
|
||||||
|
<pagination ref="pagination" :page-obj="pageObj" :table-id="tableId" @pageNo='pageNo' @pageSize='pageSize'></pagination>
|
||||||
|
</template>
|
||||||
|
</cn-data-list>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import cnDataList from '@/components/table/CnDataList'
|
||||||
|
import dataListMixin from '@/mixins/data-list'
|
||||||
|
import pluginTable from '@/components/table/system/PluginTable'
|
||||||
|
import { api } from '@/utils/api'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Plugin',
|
||||||
|
mixins: [dataListMixin],
|
||||||
|
components: {
|
||||||
|
cnDataList,
|
||||||
|
pluginTable
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
url: api.knowledgeBaseList,
|
||||||
|
blankObject: { // 空白对象
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
remark: '',
|
||||||
|
type: '',
|
||||||
|
schedule: '',
|
||||||
|
status: 1
|
||||||
|
},
|
||||||
|
tableId: 'userTable'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getTableData (params, isAll, isClearType) {
|
||||||
|
if (isAll) {
|
||||||
|
this.searchLabel = null
|
||||||
|
} else if (isClearType) {
|
||||||
|
this.searchLabel.type = ''// 换新接口需要修改的属性名称
|
||||||
|
}
|
||||||
|
if (params) {
|
||||||
|
this.searchLabel = { ...this.searchLabel, ...params }
|
||||||
|
}
|
||||||
|
this.searchLabel = { ...this.searchLabel, ...this.pageObj }
|
||||||
|
this.isNoData = false
|
||||||
|
// this.tableData = []
|
||||||
|
this.toggleLoading(true)
|
||||||
|
delete this.searchLabel.total
|
||||||
|
let listUrl = this.url
|
||||||
|
if (this.listUrl) {
|
||||||
|
listUrl = this.listUrl
|
||||||
|
}
|
||||||
|
this.searchLabel.category = 'ai_tagging'
|
||||||
|
axios.get(listUrl, { params: this.searchLabel }).then(response => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
for (let i = 0; i < response.data.data.list.length; i++) {
|
||||||
|
response.data.data.list[i].status = response.data.data.list[i].status + ''
|
||||||
|
}
|
||||||
|
this.tableData = response.data.data.list
|
||||||
|
this.pageObj.total = response.data.data.total
|
||||||
|
} else {
|
||||||
|
console.error(response.data)
|
||||||
|
this.isNoData = true
|
||||||
|
if (response.data.message) {
|
||||||
|
this.$message.error(response.data.message)
|
||||||
|
} else {
|
||||||
|
this.$message.error(this.$t('tip.somethingWentWrong'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
this.toggleLoading(false)
|
||||||
|
this.isNoData = !this.tableData || this.tableData.length === 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unmounted () {
|
||||||
|
this.isNoData = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user