NEZ-2500 feat: cortex 运行状态详情页面开发
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
#cortexDetail {
|
||||
.sub-container .nz-table-list{
|
||||
height: 100%;
|
||||
min-height: 1169px !important;
|
||||
}
|
||||
#cortexDetailTable {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 1144px !important;
|
||||
.has-gutter {
|
||||
th {
|
||||
background: $--background-color-2;
|
||||
}
|
||||
}
|
||||
.cortex-title {
|
||||
font-size: 14px;
|
||||
color: $--color-text-regular;
|
||||
letter-spacing: 0;
|
||||
font-weight: 700;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.cortex-service {
|
||||
& > div:nth-of-type(3) {
|
||||
margin-top: -1px;
|
||||
}
|
||||
td,
|
||||
th {
|
||||
.cell {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cortex-ring {
|
||||
td {
|
||||
border-bottom: none;
|
||||
}
|
||||
.cortex-store-gateway {
|
||||
margin-top: -1px;
|
||||
.has-gutter {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cortex-service,
|
||||
.cortex-ring {
|
||||
.el-table {
|
||||
border-left: none;
|
||||
.el-table__header-wrapper:hover {
|
||||
th {
|
||||
border-right-color: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
tr {
|
||||
td,
|
||||
th {
|
||||
border-left: 1px solid $--border-color-light !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cortex-config {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
.cortex-config-tab {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: calc(100% - 92px);
|
||||
.config-tab {
|
||||
overflow: hidden;
|
||||
.el-table__body-wrapper {
|
||||
height: 100%;
|
||||
tbody {
|
||||
tr:hover > td {
|
||||
background-color: $--background-color-empty;
|
||||
}
|
||||
tr > td:nth-of-type(1) {
|
||||
background: $--background-color-2;
|
||||
border-right: 1px solid $--border-color-light;
|
||||
.cell {
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
tr > td:nth-of-type(2) {
|
||||
.cell {
|
||||
color: $--color-text-primary;
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
td {
|
||||
padding: 2px 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.config-tab,
|
||||
.config-switch-tab {
|
||||
height: 100%;
|
||||
border: 1px solid $--border-color-light;
|
||||
border-bottom: none;
|
||||
.has-gutter {
|
||||
th:nth-of-type(1) {
|
||||
border-right: 1px solid $--border-color-light !important;
|
||||
border-bottom: none;
|
||||
}
|
||||
th > div {
|
||||
text-align: end;
|
||||
}
|
||||
}
|
||||
}
|
||||
.config-switch-tab {
|
||||
.has-gutter {
|
||||
th:nth-of-type(2) {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
.el-table__body-wrapper {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-no-data {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,7 @@
|
||||
@import './common/table/settings/userTable.scss';
|
||||
@import './common/table/settings/backupsTable.scss';
|
||||
@import './common/table/settings/switchTab.scss';
|
||||
@import './common/table/settings/cortexDetailTable.scss';
|
||||
@import './common/table/special/endpointQuery.scss';
|
||||
@import './common/globalSearch/globalSearch';
|
||||
@import './common/globalSearch/searchItemInfo';
|
||||
|
||||
@@ -66,7 +66,8 @@
|
||||
<!--model 下滑-->
|
||||
<asset-tab v-if="(from === fromRoute.model) && targetTab === 'asset'" :tabs="tabs.model" ref="assetTab" :from="from" :obj="obj" @changeTab="changeTab" @exit="closeSubList" :targetTab.sync="targetTab"></asset-tab>
|
||||
<!-- agent 下滑-->
|
||||
<scrape-endpoint v-if="from === fromRoute.agent && targetTab === 'agent'" :from="from" :obj="obj" :tabs="tabs.agent" @changeTab="changeTab" :targetTab.sync="targetTab"></scrape-endpoint>
|
||||
<scrape-endpoint v-if="from === fromRoute.agent && targetTab === 'scrapeEndpoint'" :from="from" :obj="obj" :tabs="tabs.agent.scrapeEndpoint" @changeTab="changeTab" :targetTab.sync="targetTab"></scrape-endpoint>
|
||||
<cortex-detail v-if="from === fromRoute.agent && targetTab === 'cortexDetail'" :from="from" :obj="obj" :tabs="tabs.agent.cortexDetail" @changeTab="changeTab" :targetTab.sync="targetTab"></cortex-detail>
|
||||
<!-- ipam -->
|
||||
<ip-details v-if="from === fromRoute.ipam && targetTab === 'ipam'" :from="from" :obj="obj" :tabs="tabs.ipam" @changeTab="changeTab" :targetTab.sync="targetTab"></ip-details>
|
||||
<!-- recordRule 下滑-->
|
||||
@@ -107,6 +108,7 @@ import LogBottomTab from '@/components/common/bottomBox/tabs/logBottomTab'
|
||||
import processBottomTab from '@/components/common/bottomBox/tabs/processBottomTab'
|
||||
import networkBottomTab from '@/components/common/bottomBox/tabs/networkBottomTab'
|
||||
import scrapeEndpoint from '@/components/common/bottomBox/tabs/scrapeEndpoint'
|
||||
import cortexDetail from '@/components/common/bottomBox/tabs/cortexDetail'
|
||||
import IpDetails from '@/components/common/bottomBox/tabs/IpDetails'
|
||||
import recordRuleEvalLog from '@/components/common/bottomBox/tabs/recordRuleEvalLog'
|
||||
import routerPathParams from '@/components/common/mixin/routerPathParams'
|
||||
@@ -116,6 +118,7 @@ export default {
|
||||
mixins: [routerPathParams],
|
||||
components: {
|
||||
scrapeEndpoint,
|
||||
cortexDetail,
|
||||
LogBottomTab,
|
||||
processBottomTab,
|
||||
networkBottomTab,
|
||||
@@ -245,9 +248,16 @@ export default {
|
||||
{ prop: 'alertMessageTab', name: this.$t('overall.alert'), active: true }
|
||||
]
|
||||
},
|
||||
agent: [
|
||||
{ prop: 'agent', name: this.$t('config.agent.scrapeEndpoint'), active: true }
|
||||
agent: {
|
||||
scrapeEndpoint: [
|
||||
{ prop: 'scrapeEndpoint', name: this.$t('config.agent.scrapeEndpoint'), active: true },
|
||||
{ prop: 'cortexDetail', name: this.$t('config.agent.cortexDetail'), active: false }
|
||||
],
|
||||
cortexDetail: [
|
||||
{ prop: 'scrapeEndpoint', name: this.$t('config.agent.scrapeEndpoint'), active: false },
|
||||
{ prop: 'cortexDetail', name: this.$t('config.agent.cortexDetail'), active: true }
|
||||
]
|
||||
},
|
||||
ipam: [
|
||||
{ prop: 'ipam', name: this.$t('ipam.subnet.ipDetails'), active: true }
|
||||
],
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<nz-bottom-data-list
|
||||
id="cortexDetail"
|
||||
:showTitle='showTitle'
|
||||
:obj='obj'
|
||||
:tableId="tableId"
|
||||
:custom-table-title.sync="tools.customTableTitle"
|
||||
:tabs="tabs"
|
||||
:targetTab="targetTab"
|
||||
:showPagination="false"
|
||||
@changeTab="changeTab"
|
||||
>
|
||||
<template v-slot:title><span :title="obj.name">{{obj.name}}</span></template>
|
||||
<template v-slot>
|
||||
<cortexDetailTable
|
||||
v-my-loading="tools.loading"
|
||||
:loading="tools.loading"
|
||||
:ringTableData='ringTableData'
|
||||
:configTableData='configTableData'
|
||||
:servicesTableData='servicesTableData'>
|
||||
</cortexDetailTable>
|
||||
</template>
|
||||
</nz-bottom-data-list>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dataListMixin from '@/components/common/mixin/dataList'
|
||||
import subDataListMixin from '@/components/common/mixin/subDataList'
|
||||
import nzBottomDataList from '@/components/common/bottomBox/nzBottomDataList'
|
||||
import cortexDetailTable from '@/components/common/table/settings/cortexDetailTable'
|
||||
|
||||
export default {
|
||||
name: 'cortexDetail',
|
||||
mixins: [dataListMixin, subDataListMixin],
|
||||
components: {
|
||||
nzBottomDataList,
|
||||
cortexDetailTable
|
||||
},
|
||||
props: {
|
||||
obj: Object,
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
obj: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
async handler (n) {
|
||||
await this.getReadyTableData()
|
||||
await this.getIngesterTableData()
|
||||
await this.getStoreGatewayTableData()
|
||||
await this.getConfigTableData()
|
||||
this.getservicesTableData()
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tableId: 'cortexDetail',
|
||||
configTableData: [],
|
||||
servicesTableData: [],
|
||||
ringTableData: [],
|
||||
readyTableData: ''
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
},
|
||||
methods: {
|
||||
async getIngesterTableData () {
|
||||
const response = await this.$get('agent/' + this.obj.id + '/cortex/ingester/ring')
|
||||
if (response.code === 200) {
|
||||
this.ringTableData.push(response.data.list)
|
||||
}
|
||||
},
|
||||
async getStoreGatewayTableData () {
|
||||
const response = await this.$get('agent/' + this.obj.id + '/cortex/store-gateway/ring')
|
||||
if (response.code === 200) {
|
||||
this.ringTableData.push(response.data.list)
|
||||
}
|
||||
},
|
||||
async getConfigTableData () {
|
||||
const response = await this.$get('agent/' + this.obj.id + '/cortex/config?mode=defaults')
|
||||
if (response.code === 200) {
|
||||
this.configTableData = response.data.conent.split(/\n/)
|
||||
this.configTableData.unshift('{')
|
||||
this.configTableData.push('}')
|
||||
}
|
||||
},
|
||||
async getReadyTableData () {
|
||||
const response = await this.$get('agent/' + this.obj.id + '/cortex/ready')
|
||||
if (response.code === 200) {
|
||||
this.readyTableData = response.data.status
|
||||
}
|
||||
},
|
||||
async getservicesTableData () {
|
||||
const response = await this.$get('agent/' + this.obj.id + '/cortex/services')
|
||||
if (response.code === 200) {
|
||||
const serObj = {}
|
||||
response.data.list.forEach(item => {
|
||||
serObj[item.name] = item.status
|
||||
})
|
||||
serObj.ready = this.readyTableData
|
||||
this.servicesTableData.push(serObj)
|
||||
this.tools.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -77,7 +77,7 @@
|
||||
<div slot="header" class="table-operation-title">{{$t('overall.option')}}</div>
|
||||
<div slot-scope="scope" class="table-operation-items">
|
||||
<!-- <button class="table-operation-item" v-has="'agent_edit'" @click="tableOperation(['edit', scope.row])"><i class="nz-icon nz-icon-edit"></i></button>-->
|
||||
<button class="table-operation-item" @click="showBottomBox('agent', scope.row)" :title="$t('overall.view')"><i class="nz-icon nz-icon-view1"></i></button>
|
||||
<button class="table-operation-item" @click="showBottomBox('scrapeEndpoint', scope.row)" :title="$t('overall.view')"><i class="nz-icon nz-icon-view1"></i></button>
|
||||
<el-dropdown size="medium" v-has="['agent_edit','agent_delete']" trigger="click" @command="tableOperation">
|
||||
<div class="table-operation-item table-operation-item--more" :title="$t('overall.moreOperations')">
|
||||
<i class="nz-icon nz-icon-more3"></i>
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
<template>
|
||||
<div id="cortexDetailTable">
|
||||
<template v-if="noData" height='100%'>
|
||||
<div class="cortex-service">
|
||||
<div class="cortex-title" style="margin-top: 0px;">{{$t('cortex.serviceStatus')}}</div>
|
||||
<el-table
|
||||
:data="servicesTableData"
|
||||
border>
|
||||
<el-table-column
|
||||
v-for="(item, index) in serviceTitle"
|
||||
:key="`col-${index}`"
|
||||
:fixed="item.fixed"
|
||||
:label="item.label"
|
||||
:min-width="`${item.minWidth}`"
|
||||
:prop="item.prop"
|
||||
:resizable="true"
|
||||
:width="`${item.width}`"
|
||||
class="data-column"
|
||||
>
|
||||
<template slot="header">
|
||||
<span class="data-column__span">{{item.label}}</span>
|
||||
<div class="col-resize-area"></div>
|
||||
</template>
|
||||
<template slot-scope="scope" :column="item">
|
||||
<span v-if="scope.row[item.prop]">{{scope.row[item.prop]}}</span>
|
||||
<template v-else>-</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-table
|
||||
:data="servicesTableData"
|
||||
border>
|
||||
<el-table-column
|
||||
v-for="(item, index) in serviceTitleSub"
|
||||
:key="`col-${index}`"
|
||||
:fixed="item.fixed"
|
||||
:label="item.label"
|
||||
:min-width="`${item.minWidth}`"
|
||||
:prop="item.prop"
|
||||
:resizable="true"
|
||||
:width="`${item.width}`"
|
||||
class="data-column"
|
||||
>
|
||||
<template slot="header">
|
||||
<span class="data-column__span">{{item.label}}</span>
|
||||
<div class="col-resize-area"></div>
|
||||
</template>
|
||||
<template slot-scope="scope" :column="item">
|
||||
<span v-if="scope.row[item.prop]">{{scope.row[item.prop]}}</span>
|
||||
<template v-else>-</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="cortex-ring">
|
||||
<div class="cortex-title">{{$t('cortex.ringStatus')}}</div>
|
||||
<div v-for="(items, indexs) in ringTableData" :key="`tab-${indexs}`">
|
||||
<div :class="`${indexs? 'cortex-store-gateway':'cortex-ingester'}`">
|
||||
<el-table
|
||||
:data="items"
|
||||
:border='true'
|
||||
:span-method="(param)=>objectSpanMethod(param,items)"
|
||||
>
|
||||
<el-table-column
|
||||
v-for="(item, index) in ingesterTitle"
|
||||
:key="`col-${index}`"
|
||||
:fixed="item.fixed"
|
||||
:label="item.label"
|
||||
:min-width="`${item.minWidth}`"
|
||||
:prop="item.prop"
|
||||
:resizable="true"
|
||||
:width="`${item.width}`"
|
||||
class="data-column"
|
||||
>
|
||||
<template slot="header">
|
||||
<span class="data-column__span">{{item.label}}</span>
|
||||
<div class="col-resize-area"></div>
|
||||
</template>
|
||||
<template slot-scope="scope" :column="item">
|
||||
<span v-if="item.prop === 'name'">{{indexs? $t('cortex.storeGateway'):$t('cortex.ingester')}}</span>
|
||||
<span v-else-if="item.prop === 'registeredAt'">{{scope.row[item.prop]?momentTz(scope.row[item.prop]):'-'}}</span>
|
||||
<span v-else-if="item.prop === 'heartbeat'">{{scope.row[item.prop]?momentTz(scope.row[item.prop]):'-'}}</span>
|
||||
<span v-else-if="scope.row[item.prop]">{{scope.row[item.prop]}}</span>
|
||||
<template v-else>-</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cortex-config">
|
||||
<div class="cortex-title">{{$t('overall.configEndpoint')}}</div>
|
||||
<div :class="`${configSwitch ? 'cortex-config-tab' : ''}`">
|
||||
<el-table
|
||||
v-if="configSwitch"
|
||||
:data="configTableData"
|
||||
class="config-tab">
|
||||
<el-table-column
|
||||
type="index"
|
||||
:resizable="true">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:resizable="true"
|
||||
class="data-column"
|
||||
>
|
||||
<template #header>
|
||||
<span style="padding-right: 7px;">{{$t('cortex.IncludeDefault')}}</span>
|
||||
<el-switch v-model="configSwitch" :active-value="1" :inactive-value="0" size="small" style="padding-right: 11px;"></el-switch>
|
||||
</template>
|
||||
<template slot-scope="scope">
|
||||
<span><pre>{{scope.row}}</pre></span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-table v-else class="config-switch-tab">
|
||||
<el-table-column
|
||||
type="index">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:resizable="true"
|
||||
class="data-column"
|
||||
>
|
||||
<template #header>
|
||||
<span style="padding-right: 15px;">{{$t('cortex.includeDefaultValues')}}</span>
|
||||
<el-switch v-model="configSwitch" :active-value="1" :inactive-value="0" size="small"></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="table-no-data">
|
||||
<div v-if="!loading">
|
||||
<svg class="icon" aria-hidden="true">
|
||||
<use xlink:href="#nz-icon-no-data-list"></use>
|
||||
</svg>
|
||||
<div class="table-no-data__title">No results found</div>
|
||||
</div>
|
||||
<div v-else> </div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import lodash from 'lodash'
|
||||
import table from '@/components/common/mixin/table'
|
||||
export default {
|
||||
name: 'cortexDetailTable',
|
||||
props: {
|
||||
loading: Boolean,
|
||||
ingesterTableData: Array,
|
||||
storeGatewayTableData: Array,
|
||||
configTableData: Array,
|
||||
servicesTableData: Array,
|
||||
ringTableData: Array
|
||||
},
|
||||
mixins: [table],
|
||||
components: {},
|
||||
watch: {
|
||||
loading: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler (n) {
|
||||
if (!n) {
|
||||
// 判断是否有值
|
||||
if (!lodash.isEmpty(this.ingesterTableData) || !lodash.isEmpty(this.storeGatewayTableData) || !lodash.isEmpty(this.configTableData) || !lodash.isEmpty(this.servicesTableData)) {
|
||||
this.noData = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
configSwitch: 1,
|
||||
noData: false,
|
||||
ingesterTitle: [
|
||||
{
|
||||
label: this.$t('alert.alertName'),
|
||||
prop: 'name',
|
||||
minWidth: 145
|
||||
},
|
||||
{
|
||||
label: 'ID',
|
||||
prop: 'instanceId',
|
||||
minWidth: 145
|
||||
},
|
||||
{
|
||||
label: this.$t('cortex.availabilityZone'),
|
||||
prop: 'zone',
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
label: this.$t('overall.state'),
|
||||
prop: 'state',
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
label: this.$t('overall.address'),
|
||||
prop: 'address',
|
||||
minWidth: 180
|
||||
}, {
|
||||
label: this.$t('cortex.registeredAt'),
|
||||
prop: 'registeredAt',
|
||||
minWidth: 210
|
||||
}, {
|
||||
label: this.$t('cortex.lastHeartbeat'),
|
||||
prop: 'heartbeat',
|
||||
minWidth: 210
|
||||
}, {
|
||||
label: this.$t('asset.talon.token'),
|
||||
prop: 'tokens',
|
||||
minWidth: 180
|
||||
}, {
|
||||
label: this.$t('cortex.ownership'),
|
||||
prop: 'ownership',
|
||||
minWidth: 180
|
||||
}],
|
||||
serviceTitle: [
|
||||
{
|
||||
label: this.$t('cortex.ready'),
|
||||
prop: 'ready',
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
label: this.$t('cortex.distributorService'),
|
||||
prop: 'distributor-service',
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
label: this.$t('cortex.ingesterService'),
|
||||
prop: 'ingester-service',
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
label: this.$t('cortex.memberlist'),
|
||||
prop: 'memberlist-kv',
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
label: this.$t('cortex.querier'),
|
||||
prop: 'querier',
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
label: this.$t('cortex.queryFrontend'),
|
||||
prop: 'query-frontend',
|
||||
minWidth: 180
|
||||
}
|
||||
],
|
||||
serviceTitleSub: [
|
||||
{
|
||||
label: this.$t('cortex.queryFrontendTripperware'),
|
||||
prop: 'query-frontend-tripperware',
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
label: this.$t('cortex.ring'),
|
||||
prop: 'ring',
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
label: this.$t('project.topology.rule'),
|
||||
prop: 'ruler',
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
label: this.$t('asset.server'),
|
||||
prop: 'server',
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
label: this.$t('cortex.storeGateway'),
|
||||
prop: 'store-gateway',
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
label: this.$t('cortex.storeQueryable'),
|
||||
prop: 'store-queryable',
|
||||
minWidth: 180
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
},
|
||||
methods: {
|
||||
// 合并单元格
|
||||
objectSpanMethod ({ row, column, rowIndex, columnIndex }, items) {
|
||||
if (columnIndex === 0) {
|
||||
if (rowIndex % items.length === 0) {
|
||||
return {
|
||||
rowspan: items.length,
|
||||
colspan: 1
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
rowspan: 0,
|
||||
colspan: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user