mirror of
https://github.com/nasa/openmct.git
synced 2025-01-02 11:26:42 +00:00
fixed condition clone
This commit is contained in:
commit
50b331c451
@ -69,6 +69,7 @@
|
|||||||
:is-editing="isEditing"
|
:is-editing="isEditing"
|
||||||
@updateCurrentCondition="updateCurrentCondition"
|
@updateCurrentCondition="updateCurrentCondition"
|
||||||
@removeCondition="removeCondition"
|
@removeCondition="removeCondition"
|
||||||
|
@cloneCondition="cloneCondition"
|
||||||
@conditionResultUpdated="handleConditionResult"
|
@conditionResultUpdated="handleConditionResult"
|
||||||
@setMoveIndex="setMoveIndex"
|
@setMoveIndex="setMoveIndex"
|
||||||
/>
|
/>
|
||||||
@ -209,36 +210,52 @@ export default {
|
|||||||
this.telemetryObjs.splice(index, 1);
|
this.telemetryObjs.splice(index, 1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addCondition(event, isDefault) {
|
/*
|
||||||
let conditionDomainObject = this.createConditionDomainObject(!!isDefault);
|
Adds a condition to list via programatic creation of default for initial list, manual
|
||||||
|
creation via Add Condition button, or duplication via button in title bar of condition.
|
||||||
|
Params:
|
||||||
|
event: always null,
|
||||||
|
isDefault (boolean): true if conditionList is empty
|
||||||
|
isClone (boolean): true if duplicating a condition
|
||||||
|
definition (string): definition property of condition being duplicated with new name
|
||||||
|
index (number): index of condition being duplicated
|
||||||
|
*/
|
||||||
|
addCondition(event, isDefault, isClone, configuration, index) {
|
||||||
|
let conditionDomainObject = this.createConditionDomainObject(!!isDefault, isClone, configuration);
|
||||||
//persist the condition domain object so that we can do an openmct.objects.get on it and only persist the identifier in the conditionCollection of conditionSet
|
//persist the condition domain object so that we can do an openmct.objects.get on it and only persist the identifier in the conditionCollection of conditionSet
|
||||||
this.openmct.objects.mutate(conditionDomainObject, 'created', conditionDomainObject.created);
|
this.openmct.objects.mutate(conditionDomainObject, 'created', new Date());
|
||||||
this.conditionCollection.unshift(conditionDomainObject.identifier);
|
if (!isClone) {
|
||||||
|
this.conditionCollection.unshift(conditionDomainObject.identifier);
|
||||||
|
} else {
|
||||||
|
this.conditionCollection.splice(index + 1, 0, conditionDomainObject.identifier);
|
||||||
|
}
|
||||||
this.persist();
|
this.persist();
|
||||||
},
|
},
|
||||||
updateCurrentCondition(identifier) {
|
updateCurrentCondition(identifier) {
|
||||||
this.currentConditionIdentifier = identifier;
|
this.currentConditionIdentifier = identifier;
|
||||||
},
|
},
|
||||||
createConditionDomainObject(isDefault) {
|
createConditionDomainObject(isDefault, isClone, configuration) {
|
||||||
|
const configurationTemplate = {
|
||||||
|
name: isDefault ? 'Default' : (isClone ? 'Copy of ' : '') + 'Unnamed Condition',
|
||||||
|
output: 'false',
|
||||||
|
trigger: 'any',
|
||||||
|
criteria: isDefault ? [] : [{
|
||||||
|
telemetry: '',
|
||||||
|
operation: '',
|
||||||
|
input: '',
|
||||||
|
metadata: '',
|
||||||
|
key: ''
|
||||||
|
}]
|
||||||
|
};
|
||||||
let conditionObj = {
|
let conditionObj = {
|
||||||
isDefault: isDefault,
|
isDefault: isDefault,
|
||||||
type: 'condition',
|
type: 'condition',
|
||||||
name: isDefault ? 'Default' : 'Unnamed Condition',
|
name: isDefault ? 'Default' : (isClone ? 'Copy of ' : '') + 'Unnamed Condition',
|
||||||
identifier: {
|
identifier: {
|
||||||
namespace: this.domainObject.identifier.namespace,
|
namespace: this.domainObject.identifier.namespace,
|
||||||
key: uuid()
|
key: uuid()
|
||||||
},
|
},
|
||||||
configuration: {
|
configuration: isClone ? configuration: configurationTemplate,
|
||||||
name: isDefault ? 'Default' : 'Unnamed Condition',
|
|
||||||
output: 'false',
|
|
||||||
trigger: 'any',
|
|
||||||
criteria: isDefault ? [] : [{
|
|
||||||
telemetry: '',
|
|
||||||
operation: '',
|
|
||||||
input: '',
|
|
||||||
metadata: ''
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
summary: 'summary description',
|
summary: 'summary description',
|
||||||
created: new Date()
|
created: new Date()
|
||||||
};
|
};
|
||||||
@ -262,12 +279,18 @@ export default {
|
|||||||
this.updateCurrentConditionId();
|
this.updateCurrentConditionId();
|
||||||
},
|
},
|
||||||
reorder(reorderPlan) {
|
reorder(reorderPlan) {
|
||||||
let oldConditions = this.conditionCollection.slice();
|
let oldConditions = Array.from(this.conditionCollection);
|
||||||
reorderPlan.forEach((reorderEvent) => {
|
reorderPlan.forEach((reorderEvent) => {
|
||||||
this.$set(this.conditionCollection, reorderEvent.newIndex, oldConditions[reorderEvent.oldIndex]);
|
this.$set(this.conditionCollection, reorderEvent.newIndex, oldConditions[reorderEvent.oldIndex]);
|
||||||
});
|
});
|
||||||
this.persist();
|
this.persist();
|
||||||
},
|
},
|
||||||
|
cloneCondition(condition) {
|
||||||
|
this.openmct.objects.get(condition.identifier).then((obj) => {
|
||||||
|
obj.configuration.name = 'Copy of ' + obj.configuration.name;
|
||||||
|
this.addCondition(null, false, true, obj.configuration, condition.index);
|
||||||
|
});
|
||||||
|
},
|
||||||
persist() {
|
persist() {
|
||||||
this.openmct.objects.mutate(this.domainObject, 'configuration.conditionCollection', this.conditionCollection);
|
this.openmct.objects.mutate(this.domainObject, 'configuration.conditionCollection', this.conditionCollection);
|
||||||
}
|
}
|
||||||
|
389
src/plugins/condition/components/ConditionEdit.vue
Normal file
389
src/plugins/condition/components/ConditionEdit.vue
Normal file
@ -0,0 +1,389 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- TODO: current condition class should be set using openmct.objects.makeKeyString(<identifier>) -->
|
||||||
|
<div v-if="condition"
|
||||||
|
class="c-c-editui__conditions c-c-container__container c-c__drag-wrapper"
|
||||||
|
:class="['widget-condition', { 'widget-condition--current': currentConditionIdentifier && (currentConditionIdentifier.key === conditionIdentifier.key) }]"
|
||||||
|
>
|
||||||
|
<div class="title-bar">
|
||||||
|
<span class="c-c__menu-hamburger"
|
||||||
|
:class="{ 'is-enabled': !condition.isDefault }"
|
||||||
|
:draggable="!condition.isDefault"
|
||||||
|
@dragstart="dragStart"
|
||||||
|
@dragover.stop
|
||||||
|
></span>
|
||||||
|
<span
|
||||||
|
class="is-enabled flex-elem"
|
||||||
|
:class="['c-c__disclosure-triangle', { 'c-c__disclosure-triangle--expanded': expanded }]"
|
||||||
|
@click="expanded = !expanded"
|
||||||
|
></span>
|
||||||
|
<div class="condition-summary">
|
||||||
|
<span class="condition-name">{{ condition.definition.name }}</span>
|
||||||
|
<!-- TODO: description should be derived from criteria -->
|
||||||
|
<span class="condition-description">{{ condition.definition.name }}</span>
|
||||||
|
</div>
|
||||||
|
<span v-if="!condition.isDefault"
|
||||||
|
class="is-enabled c-c__duplicate"
|
||||||
|
@click="cloneCondition"
|
||||||
|
></span>
|
||||||
|
<span v-if="!condition.isDefault"
|
||||||
|
class="is-enabled c-c__trash"
|
||||||
|
@click="removeCondition"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
<div v-if="expanded"
|
||||||
|
class="condition-config-edit widget-condition-content c-sw-editui__conditions-wrapper holder widget-conditions-wrapper flex-elem expanded"
|
||||||
|
>
|
||||||
|
<div id="conditionArea"
|
||||||
|
class="c-c-editui__condition widget-conditions"
|
||||||
|
>
|
||||||
|
<div class="c-c-condition">
|
||||||
|
<div class="c-c-condition__ui l-compact-form l-widget-condition has-local-controls">
|
||||||
|
<div>
|
||||||
|
<ul class="t-widget-condition-config">
|
||||||
|
<li>
|
||||||
|
<label>Condition Name</label>
|
||||||
|
<span class="controls">
|
||||||
|
<input v-model="condition.definition.name"
|
||||||
|
class="t-condition-name-input"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label>Output</label>
|
||||||
|
<span class="controls">
|
||||||
|
<select v-model="selectedOutputKey"
|
||||||
|
@change="checkInputValue"
|
||||||
|
>
|
||||||
|
<option value="">- Select Output -</option>
|
||||||
|
<option v-for="option in outputOptions"
|
||||||
|
:key="option.key"
|
||||||
|
:value="option.key"
|
||||||
|
>
|
||||||
|
{{ option.text }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<input v-if="selectedOutputKey === outputOptions[2].key"
|
||||||
|
v-model="condition.definition.output"
|
||||||
|
class="t-condition-name-input"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div v-if="!condition.isDefault"
|
||||||
|
class="widget-condition-content expanded"
|
||||||
|
>
|
||||||
|
<ul class="t-widget-condition-config">
|
||||||
|
<li class="has-local-controls t-condition">
|
||||||
|
<label>Match when</label>
|
||||||
|
<span class="controls">
|
||||||
|
<select>
|
||||||
|
<option value="all">all criteria are met</option>
|
||||||
|
<option value="any">any criteria are met</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="t-widget-condition-config">
|
||||||
|
<li v-if="telemetry.length"
|
||||||
|
class="has-local-controls t-condition"
|
||||||
|
>
|
||||||
|
<label>when</label>
|
||||||
|
<span class="t-configuration">
|
||||||
|
<span class="controls">
|
||||||
|
<select v-model="selectedTelemetryKey"
|
||||||
|
@change="updateTelemetryMetaData"
|
||||||
|
>
|
||||||
|
<option value="">- Select Telemetry -</option>
|
||||||
|
<option v-for="telemetryOption in telemetry"
|
||||||
|
:key="telemetryOption.identifier.key"
|
||||||
|
:value="telemetryOption.identifier"
|
||||||
|
>
|
||||||
|
{{ telemetryOption.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
<span class="controls">
|
||||||
|
<select v-model="selectedMetaDataKey">
|
||||||
|
<option value="">- Select Field -</option>
|
||||||
|
<option v-for="option in telemetryMetadata"
|
||||||
|
:key="option.key"
|
||||||
|
:value="option.key"
|
||||||
|
>
|
||||||
|
{{ option.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
<span class="controls">
|
||||||
|
<select v-model="selectOperationName"
|
||||||
|
@change="setInputValueVisibility"
|
||||||
|
>
|
||||||
|
<option value="">- Select Comparison -</option>
|
||||||
|
<option v-for="option in operations"
|
||||||
|
:key="option.name"
|
||||||
|
:value="option.name"
|
||||||
|
>
|
||||||
|
{{ option.text }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<input v-if="comparisonValueField"
|
||||||
|
class="t-condition-name-input"
|
||||||
|
type="text"
|
||||||
|
v-model="operationValue"
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { OPERATIONS } from '../utils/operations';
|
||||||
|
import ConditionClass from "@/plugins/condition/Condition";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
inject: ['openmct', 'domainObject'],
|
||||||
|
props: {
|
||||||
|
conditionIdentifier: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
currentConditionIdentifier: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
telemetry: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
conditionIndex: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
condition: this.condition,
|
||||||
|
expanded: true,
|
||||||
|
telemetryObject: this.telemetryObject,
|
||||||
|
telemetryMetadata: this.telemetryMetadata,
|
||||||
|
operations: OPERATIONS,
|
||||||
|
selectedMetaDataKey: '',
|
||||||
|
selectedTelemetryKey: '',
|
||||||
|
selectOperationName: '',
|
||||||
|
selectedOutputKey: '',
|
||||||
|
stringOutputField: false,
|
||||||
|
comparisonValueField: false,
|
||||||
|
operationValue: this.operationValue,
|
||||||
|
outputOptions: [
|
||||||
|
{
|
||||||
|
key: 'false',
|
||||||
|
text: 'False'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'true',
|
||||||
|
text: 'True'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'string',
|
||||||
|
text: 'String'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
};
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
this.destroy();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.openmct.objects.get(this.conditionIdentifier).then((obj => {
|
||||||
|
this.condition = obj;
|
||||||
|
this.initialize();
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
updated() {
|
||||||
|
//validate telemetry exists, update criteria as needed
|
||||||
|
this.validate();
|
||||||
|
this.persist();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
dragStart(e) {
|
||||||
|
e.dataTransfer.effectAllowed = "copyMove";
|
||||||
|
e.dataTransfer.setDragImage(e.target.closest('.c-c-container__container'), 0, 0);
|
||||||
|
this.$emit('setMoveIndex', this.conditionIndex);
|
||||||
|
},
|
||||||
|
initialize() {
|
||||||
|
this.setOutput();
|
||||||
|
this.setOperation();
|
||||||
|
this.updateTelemetry();
|
||||||
|
this.conditionClass = new ConditionClass(this.condition, this.openmct);
|
||||||
|
this.conditionClass.on('conditionResultUpdated', this.handleConditionResult.bind(this));
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
this.conditionClass.off('conditionResultUpdated', this.handleConditionResult.bind(this));
|
||||||
|
if (this.conditionClass && typeof this.conditionClass.destroy === 'function') {
|
||||||
|
this.conditionClass.destroy();
|
||||||
|
delete this.conditionClass;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
this.selectedMetaDataKey = '';
|
||||||
|
this.selectedTelemetryKey = '';
|
||||||
|
this.selectOperationName = '';
|
||||||
|
this.operationValue = '';
|
||||||
|
},
|
||||||
|
validate() {
|
||||||
|
if (this.hasTelemetry() && !this.getTelemetryKey()) {
|
||||||
|
this.reset();
|
||||||
|
} else {
|
||||||
|
if (!this.conditionClass) {
|
||||||
|
this.initialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleConditionResult(args) {
|
||||||
|
this.$emit('conditionResultUpdated', {
|
||||||
|
id: this.conditionIdentifier,
|
||||||
|
result: args.data.result
|
||||||
|
})
|
||||||
|
},
|
||||||
|
removeCondition(ev) {
|
||||||
|
this.$emit('removeCondition', this.conditionIdentifier);
|
||||||
|
},
|
||||||
|
cloneCondition(ev) {
|
||||||
|
this.$emit('cloneCondition', {
|
||||||
|
identifier: this.conditionIdentifier,
|
||||||
|
index: Number(ev.target.closest('.widget-condition').getAttribute('data-condition-index'))
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setOutput() {
|
||||||
|
let conditionOutput = this.condition.definition.output;
|
||||||
|
if (conditionOutput !== 'false' && conditionOutput !== 'true') {
|
||||||
|
this.selectedOutputKey = this.outputOptions[2].key;
|
||||||
|
} else {
|
||||||
|
if (conditionOutput === 'true') {
|
||||||
|
this.selectedOutputKey = this.outputOptions[1].key;
|
||||||
|
} else {
|
||||||
|
this.selectedOutputKey = this.outputOptions[0].key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setOperation() {
|
||||||
|
if (this.condition.definition.criteria.length && this.condition.definition.criteria[0].operation) {
|
||||||
|
for (let i=0, ii=this.operations.length; i < ii; i++) {
|
||||||
|
if (this.condition.definition.criteria[0].operation === this.operations[i].name) {
|
||||||
|
this.selectOperationName = this.operations[i].name;
|
||||||
|
this.comparisonValueField = this.operations[i].inputCount > 0;
|
||||||
|
if (this.comparisonValueField) {
|
||||||
|
this.operationValue = this.condition.definition.criteria[0].input[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateTelemetry() {
|
||||||
|
if (this.hasTelemetry()) {
|
||||||
|
this.openmct.objects.get(this.condition.definition.criteria[0].key).then((obj) => {
|
||||||
|
this.telemetryObject = obj;
|
||||||
|
this.telemetryMetadata = this.openmct.telemetry.getMetadata(this.telemetryObject).values();
|
||||||
|
this.selectedMetaDataKey = this.getTelemetryMetadataKey();
|
||||||
|
this.selectedTelemetryKey = this.getTelemetryKey();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.telemetryObject = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getTelemetryMetadataKey() {
|
||||||
|
let index = -1;
|
||||||
|
if (this.condition.definition.criteria[0].metaDataKey) {
|
||||||
|
index = _.findIndex(this.telemetryMetadata, (metadata) => {
|
||||||
|
return metadata.key === this.condition.definition.criteria[0].metaDataKey;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this.telemetryMetadata.length && index > -1 ? this.telemetryMetadata[index].key : '';
|
||||||
|
},
|
||||||
|
getTelemetryKey() {
|
||||||
|
let index = -1;
|
||||||
|
if (this.condition.definition.criteria[0].key) {
|
||||||
|
index = _.findIndex(this.telemetry, (obj) => {
|
||||||
|
let key = this.openmct.objects.makeKeyString(obj.identifier);
|
||||||
|
let conditionKey = this.openmct.objects.makeKeyString(this.condition.definition.criteria[0].key);
|
||||||
|
return key === conditionKey;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this.telemetry.length && index > -1 ? this.telemetry[index].identifier : '';
|
||||||
|
},
|
||||||
|
hasTelemetry() {
|
||||||
|
return this.condition.definition.criteria.length && this.condition.definition.criteria[0].key;
|
||||||
|
},
|
||||||
|
updateConditionCriteria() {
|
||||||
|
if (this.condition.definition.criteria.length) {
|
||||||
|
let criterion = this.condition.definition.criteria[0];
|
||||||
|
criterion.key = this.selectedTelemetryKey;
|
||||||
|
criterion.metaDataKey = this.selectedMetaDataKey;
|
||||||
|
criterion.operation = this.selectOperationName;
|
||||||
|
criterion.input = [this.operationValue];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
persist() {
|
||||||
|
this.updateConditionCriteria();
|
||||||
|
this.openmct.objects.mutate(this.condition, 'definition', this.condition.definition);
|
||||||
|
},
|
||||||
|
checkInputValue() {
|
||||||
|
if (this.selectedOutputKey === this.outputOptions[2].key) {
|
||||||
|
this.condition.definition.output = '';
|
||||||
|
} else {
|
||||||
|
this.condition.definition.output = this.selectedOutputKey;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setInputValueVisibility(ev) {
|
||||||
|
for (let i=0, ii=this.operations.length; i < ii; i++) {
|
||||||
|
if (this.selectOperationName === this.operations[i].name) {
|
||||||
|
this.comparisonValueField = this.operations[i].inputCount > 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//find the criterion being updated and set the operation property
|
||||||
|
},
|
||||||
|
updateTelemetryMetaData() {
|
||||||
|
this.selectedMetaDataKey = '';
|
||||||
|
this.updateConditionCriteria();
|
||||||
|
this.updateTelemetry();
|
||||||
|
},
|
||||||
|
updateCurrentCondition() {
|
||||||
|
this.$emit('updateCurrentCondition', this.conditionIdentifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
Loading…
Reference in New Issue
Block a user