mirror of
https://github.com/ansible/awx.git
synced 2026-01-16 12:20:45 -03:30
Job detail page re-refactor
Fixed event viewer dialog to match old event viewer field groupings. Removed access to job_host_summaries and job_events.
This commit is contained in:
parent
f223113cdc
commit
eac4798c63
@ -60,14 +60,9 @@ angular.module('Tower', [
|
||||
'CompletedJobsDefinition',
|
||||
'RunningJobsDefinition',
|
||||
'JobFormDefinition',
|
||||
'JobEventsListDefinition',
|
||||
'JobEventDataDefinition',
|
||||
'JobEventsFormDefinition',
|
||||
'JobHostDefinition',
|
||||
'JobSummaryDefinition',
|
||||
'ParseHelper',
|
||||
'ChildrenHelper',
|
||||
'EventsHelper',
|
||||
'ProjectPathHelper',
|
||||
'md5Helper',
|
||||
'AccessHelper',
|
||||
@ -132,21 +127,6 @@ angular.module('Tower', [
|
||||
controller: 'JobStdoutController'
|
||||
}).
|
||||
|
||||
when('/job_events/:id', {
|
||||
templateUrl: urlPrefix + 'partials/job_events.html',
|
||||
controller: 'JobEventsList'
|
||||
}).
|
||||
|
||||
when('/job_host_summaries/:id', {
|
||||
templateUrl: urlPrefix + 'partials/job_host_summaries.html',
|
||||
controller: 'JobHostSummaryList'
|
||||
}).
|
||||
|
||||
when('/jobs/:job_id/job_events/:event_id', {
|
||||
templateUrl: urlPrefix + 'partials/jobs.html',
|
||||
controller: 'JobEventsEdit'
|
||||
}).
|
||||
|
||||
when('/job_templates', {
|
||||
templateUrl: urlPrefix + 'partials/job_templates.html',
|
||||
controller: 'JobTemplatesList'
|
||||
@ -197,11 +177,6 @@ angular.module('Tower', [
|
||||
controller: 'OrganizationsAdd'
|
||||
}).
|
||||
|
||||
when('/hosts/:id/job_host_summaries', {
|
||||
templateUrl: urlPrefix + 'partials/jobs.html',
|
||||
controller: 'JobHostSummaryList'
|
||||
}).
|
||||
|
||||
when('/inventories', {
|
||||
templateUrl: urlPrefix + 'partials/inventories.html',
|
||||
controller: 'InventoriesList'
|
||||
|
||||
73
awx/ui/static/js/forms/EventsViewer.js
Normal file
73
awx/ui/static/js/forms/EventsViewer.js
Normal file
@ -0,0 +1,73 @@
|
||||
/*********************************************
|
||||
* Copyright (c) 2014 AnsibleWorks, Inc.
|
||||
*
|
||||
* JobEventsForm.js
|
||||
*
|
||||
*/
|
||||
angular.module('EventsViewerFormDefinition', [])
|
||||
.value('EventsViewerForm', {
|
||||
|
||||
fields: {
|
||||
status: {
|
||||
label: 'Status',
|
||||
section: 'Event'
|
||||
},
|
||||
id: {
|
||||
label: 'ID',
|
||||
section: 'Event'
|
||||
},
|
||||
created: {
|
||||
label: 'Created On',
|
||||
section: 'Event'
|
||||
},
|
||||
host_name: {
|
||||
label: 'Host',
|
||||
section: 'Event'
|
||||
},
|
||||
play: {
|
||||
label: 'Play',
|
||||
type: 'text',
|
||||
section: 'Event'
|
||||
},
|
||||
task: {
|
||||
label: 'Task',
|
||||
section: 'Event'
|
||||
},
|
||||
role: {
|
||||
label: 'Role',
|
||||
section: 'Event'
|
||||
},
|
||||
rc: {
|
||||
label: 'Return Code',
|
||||
section: 'Results'
|
||||
},
|
||||
msg: {
|
||||
label: 'Message',
|
||||
section: 'Results'
|
||||
},
|
||||
results: {
|
||||
label: 'Results',
|
||||
section: 'Results'
|
||||
},
|
||||
start: {
|
||||
label: 'Start',
|
||||
section: 'Timing'
|
||||
},
|
||||
end: {
|
||||
label: 'End',
|
||||
section: 'Timing'
|
||||
},
|
||||
delta: {
|
||||
label: 'Elapsed',
|
||||
section: 'Timing'
|
||||
},
|
||||
module_name: {
|
||||
label: 'Name',
|
||||
section: 'Module'
|
||||
},
|
||||
module_args: {
|
||||
label: 'Arguments',
|
||||
section: 'Module'
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1,144 +0,0 @@
|
||||
/*********************************************
|
||||
* Copyright (c) 2014 AnsibleWorks, Inc.
|
||||
*
|
||||
* JobEventsForm.js
|
||||
*
|
||||
*/
|
||||
angular.module('JobEventsFormDefinition', [])
|
||||
.value('JobEventsForm', {
|
||||
|
||||
name: 'job_events',
|
||||
well: false,
|
||||
forceListeners: true,
|
||||
|
||||
fields: {
|
||||
status: {
|
||||
labelClass: 'job-{{ status }}',
|
||||
type: 'custom',
|
||||
section: 'Event',
|
||||
control: "<div class=\"job-event-status job-{{ status }}\"><i class=\"fa icon-job-{{ status }}\"></i> {{ status }}</div>"
|
||||
},
|
||||
id: {
|
||||
label: 'ID',
|
||||
type: 'text',
|
||||
readonly: true,
|
||||
section: 'Event',
|
||||
'class': 'span1'
|
||||
},
|
||||
created: {
|
||||
label: 'Created On',
|
||||
type: 'text',
|
||||
section: 'Event',
|
||||
readonly: true
|
||||
},
|
||||
host: {
|
||||
label: 'Host',
|
||||
type: 'text',
|
||||
readonly: true,
|
||||
section: 'Event',
|
||||
ngShow: "host !== ''"
|
||||
},
|
||||
play: {
|
||||
label: 'Play',
|
||||
type: 'text',
|
||||
readonly: true,
|
||||
section: 'Event',
|
||||
ngShow: "play !== ''"
|
||||
},
|
||||
task: {
|
||||
label: 'Task',
|
||||
type: 'text',
|
||||
readonly: true,
|
||||
section: 'Event',
|
||||
ngShow: "task !== ''"
|
||||
},
|
||||
rc: {
|
||||
label: 'Return Code',
|
||||
type: 'text',
|
||||
readonly: true,
|
||||
section: 'Results',
|
||||
'class': 'span1',
|
||||
ngShow: "rc !== ''"
|
||||
},
|
||||
msg: {
|
||||
label: 'Msg',
|
||||
type: 'textarea',
|
||||
readonly: true,
|
||||
section: 'Results',
|
||||
'class': 'nowrap',
|
||||
ngShow: "msg !== ''",
|
||||
rows: 10
|
||||
},
|
||||
stdout: {
|
||||
label: 'Standard Out',
|
||||
type: 'textarea',
|
||||
readonly: true,
|
||||
section: 'Results',
|
||||
'class': 'nowrap',
|
||||
ngShow: "stdout !== ''",
|
||||
rows: 10
|
||||
},
|
||||
stderr: {
|
||||
label: 'Standard Err',
|
||||
type: 'textarea',
|
||||
readonly: true,
|
||||
section: 'Results',
|
||||
'class': 'nowrap',
|
||||
ngShow: "stderr !== ''",
|
||||
rows: 10
|
||||
},
|
||||
results: {
|
||||
label: 'Results',
|
||||
type: 'textarea',
|
||||
section: 'Results',
|
||||
readonly: true,
|
||||
'class': 'nowrap',
|
||||
ngShow: "results !== ''",
|
||||
rows: 10
|
||||
},
|
||||
start: {
|
||||
label: 'Start',
|
||||
type: 'text',
|
||||
readonly: true,
|
||||
section: 'Timing',
|
||||
ngShow: "start !== ''"
|
||||
},
|
||||
traceback: {
|
||||
label: false,
|
||||
type: 'textarea',
|
||||
readonly: true,
|
||||
section: 'Traceback',
|
||||
'class': 'nowrap',
|
||||
ngShow: "traceback !== ''",
|
||||
rows: 10
|
||||
},
|
||||
end: {
|
||||
label: 'End',
|
||||
type: 'text',
|
||||
readonly: true,
|
||||
section: 'Timing',
|
||||
ngShow: "end !== ''"
|
||||
},
|
||||
delta: {
|
||||
label: 'Elapsed',
|
||||
type: 'text',
|
||||
readonly: true,
|
||||
section: 'Timing',
|
||||
ngShow: "delta !== ''"
|
||||
},
|
||||
module_name: {
|
||||
label: 'Name',
|
||||
type: 'text',
|
||||
readonly: true,
|
||||
section: 'Module',
|
||||
ngShow: "module_name !== ''"
|
||||
},
|
||||
module_args: {
|
||||
label: 'Args',
|
||||
type: 'text',
|
||||
readonly: true,
|
||||
section: 'Module',
|
||||
ngShow: "module_args !== ''"
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'HostEventsViewerHelper'])
|
||||
angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'EventsViewerFormDefinition'])
|
||||
|
||||
.factory('EventViewer', ['$compile', 'CreateDialog', 'GetEvent', 'Wait', 'EventAddTable', 'GetBasePath', 'LookUpName', 'Empty', 'EventAddPreFormattedText',
|
||||
function($compile, CreateDialog, GetEvent, Wait, EventAddTable, GetBasePath, LookUpName, Empty, EventAddPreFormattedText) {
|
||||
@ -32,17 +32,29 @@ angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'HostEventsView
|
||||
var elem;
|
||||
|
||||
$('#status-form-container').empty();
|
||||
$('#results-form-container').empty();
|
||||
$('#timing-form-container').empty();
|
||||
$('#stdout-form-container').empty();
|
||||
$('#stderr-form-container').empty();
|
||||
$('#traceback-form-container').empty();
|
||||
$('#eventview-tabs li:eq(1)').hide();
|
||||
$('#eventview-tabs li:eq(2)').hide();
|
||||
$('#eventview-tabs li:eq(3)').hide();
|
||||
$('#eventview-tabs li:eq(4)').hide();
|
||||
$('#eventview-tabs li:eq(5)').hide();
|
||||
|
||||
EventAddTable({ scope: scope, id: 'status-form-container', event: data });
|
||||
EventAddTable({ scope: scope, id: 'status-form-container', event: data, section: 'Event' });
|
||||
|
||||
if (EventAddTable({ scope: scope, id: 'results-form-container', event: data, section: 'Results'})) {
|
||||
$('#eventview-tabs li:eq(1)').show();
|
||||
}
|
||||
|
||||
if (EventAddTable({ scope: scope, id: 'timing-form-container', event: data, section: 'Timing' })) {
|
||||
$('#eventview-tabs li:eq(2)').show();
|
||||
}
|
||||
|
||||
if (data.stdout) {
|
||||
$('#eventview-tabs li:eq(1)').show();
|
||||
$('#eventview-tabs li:eq(3)').show();
|
||||
EventAddPreFormattedText({
|
||||
id: 'stdout-form-container',
|
||||
val: data.stdout
|
||||
@ -50,7 +62,7 @@ angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'HostEventsView
|
||||
}
|
||||
|
||||
if (data.stderr) {
|
||||
$('#eventview-tabs li:eq(2)').show();
|
||||
$('#eventview-tabs li:eq(4)').show();
|
||||
EventAddPreFormattedText({
|
||||
id: 'stderr-form-container',
|
||||
val: data.stderr
|
||||
@ -58,7 +70,7 @@ angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'HostEventsView
|
||||
}
|
||||
|
||||
if (data.traceback) {
|
||||
$('#eventview-tabs li:eq(3)').show();
|
||||
$('#eventview-tabs li:eq(5)').show();
|
||||
EventAddPreFormattedText({
|
||||
id: 'traceback-form-container',
|
||||
val: data.traceback
|
||||
@ -120,6 +132,7 @@ angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'HostEventsView
|
||||
}
|
||||
if (data.results[0].event_data.res.ansible_facts) {
|
||||
// don't show fact gathering results
|
||||
data.results[0].event_data.res.task = "Gathering Facts";
|
||||
delete data.results[0].event_data.res.ansible_facts;
|
||||
}
|
||||
data.results[0].event_data.res.status = getStatus(data);
|
||||
@ -143,13 +156,17 @@ angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'HostEventsView
|
||||
}
|
||||
delete event_data.invocation;
|
||||
}
|
||||
event_data.parent = data.results[0].parent;
|
||||
event_data.play = data.results[0].play;
|
||||
event_data.task = data.results[0].task;
|
||||
if (data.results[0].task) {
|
||||
event_data.task = data.results[0].task;
|
||||
}
|
||||
event_data.created = data.results[0].created;
|
||||
event_data.role = data.results[0].role;
|
||||
event_data.host_id = data.results[0].host;
|
||||
event_data.host_name = data.results[0].host_name;
|
||||
if (event_data.host) {
|
||||
delete event_data.host;
|
||||
}
|
||||
event_data.id = data.results[0].id;
|
||||
event_data.parent = data.results[0].parent;
|
||||
event_data.event = (data.results[0].event_display) ? data.results[0].event_display : data.results[0].event;
|
||||
@ -162,61 +179,47 @@ angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'HostEventsView
|
||||
};
|
||||
}])
|
||||
|
||||
.factory('EventAddTable', ['$compile', '$filter', 'Empty', function($compile, $filter, Empty) {
|
||||
.factory('EventAddTable', ['$compile', '$filter', 'Empty', 'EventsViewerForm', function($compile, $filter, Empty, EventsViewerForm) {
|
||||
return function(params) {
|
||||
var scope = params.scope,
|
||||
id = params.id,
|
||||
event = params.event,
|
||||
section = params.section,
|
||||
html = '', e;
|
||||
|
||||
function keyToLabel(key) {
|
||||
var label = '';
|
||||
switch(key) {
|
||||
case "id":
|
||||
label = "Event ID";
|
||||
break;
|
||||
case "parent":
|
||||
label = "Parent Event ID";
|
||||
break;
|
||||
case "rc":
|
||||
label = "Return Code";
|
||||
break;
|
||||
default:
|
||||
label = key.charAt(0).toUpperCase() + key.slice(1);
|
||||
label = label.replace(/(\_.)/g, function(match) {
|
||||
var res;
|
||||
res = match.replace(/\_/,'');
|
||||
res = ' ' + res.toUpperCase();
|
||||
return res;
|
||||
});
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
function parseJSON(obj) {
|
||||
var html = '', keys;
|
||||
var html = '', keys, found = false;
|
||||
if (typeof obj === "object") {
|
||||
html += "<table class=\"table eventviewer-status\">\n";
|
||||
html += "<tbody>\n";
|
||||
keys = Object.keys(obj).sort();
|
||||
keys.forEach(function(key) {
|
||||
var label;
|
||||
if (key !== "stdout" && key !== "stderr" && key !== "traceback" && key !== "host_id" && key !== "host") {
|
||||
label = keyToLabel(key);
|
||||
if (EventsViewerForm.fields[key] && EventsViewerForm.fields[key].section === section) {
|
||||
label = EventsViewerForm.fields[key].label;
|
||||
if (Empty(obj[key])) {
|
||||
// exclude empty items
|
||||
}
|
||||
else if (typeof obj[key] === "boolean" || typeof obj[key] === "number" || typeof obj[key] === "string") {
|
||||
found = true;
|
||||
html += "<tr><td class=\"key\">" + label + ":</td><td class=\"value\">";
|
||||
if (key === "status") {
|
||||
html += "<i class=\"fa icon-job-" + obj[key] + "\"></i> " + obj[key];
|
||||
}
|
||||
else if (key === "start" || key === "end" || key === "created") {
|
||||
html += $filter('date')(obj[key], 'MM/dd/yy HH:mm:ss');
|
||||
if (!/Z$/.test(obj[key])) {
|
||||
//sec = parseInt(obj[key].substr(obj[key].length - 6, 6),10) / 1000);
|
||||
//obj[key] = obj[key].replace(/\d{6}$/,sec) + 'Z';
|
||||
obj[key] = obj[key].replace(/\ /,'T') + 'Z';
|
||||
html += $filter('date')(obj[key], 'MM/dd/yy HH:mm:ss.sss');
|
||||
}
|
||||
else {
|
||||
html += $filter('date')(obj[key], 'MM/dd/yy HH:mm:ss');
|
||||
}
|
||||
}
|
||||
else if (key === "host_name") {
|
||||
html += "<a href=\"#/home/hosts/?id=" + obj.host_id + "\" target=\"_blank\" " +
|
||||
"aw-tool-tip=\"Click to view host.<br />Opens in new tab or window.\" data-placement=\"right\" " +
|
||||
"aw-tool-tip=\"Click to edit host.\" data-placement=\"right\" " +
|
||||
"ng-click=\"modalOK()\">" + obj[key] + "</a>";
|
||||
}
|
||||
else {
|
||||
@ -226,6 +229,7 @@ angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'HostEventsView
|
||||
html += "</td></tr>\n";
|
||||
}
|
||||
else if (typeof obj[key] === "object" && Array.isArray(obj[key])) {
|
||||
found = true;
|
||||
html += "<tr><td class=\"key\">" + label + ":</td><td class=\"value\">";
|
||||
obj[key].forEach(function(row) {
|
||||
html += "[" + row + "],";
|
||||
@ -234,6 +238,7 @@ angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'HostEventsView
|
||||
html += "</td></tr>\n";
|
||||
}
|
||||
else if (typeof obj[key] === "object") {
|
||||
found = true;
|
||||
html += "<tr><td class=\"key\">" + label + ":</td><td class=\"nested-table\">\n" + parseJSON(obj[key]) + "</td></tr>\n";
|
||||
}
|
||||
}
|
||||
@ -241,12 +246,16 @@ angular.module('EventViewerHelper', ['ModalDialog', 'Utilities', 'HostEventsView
|
||||
html += "</tbody>\n";
|
||||
html += "</table>\n";
|
||||
}
|
||||
return html;
|
||||
return (found) ? html : '';
|
||||
}
|
||||
html = parseJSON(event);
|
||||
e = angular.element(document.getElementById(id));
|
||||
e.empty().html(html);
|
||||
$compile(e)(scope);
|
||||
e.empty();
|
||||
if (html) {
|
||||
e.html(html);
|
||||
$compile(e)(scope);
|
||||
}
|
||||
return (html) ? true : false;
|
||||
};
|
||||
}])
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ table.eventviewer-status {
|
||||
margin-top: 20px;
|
||||
|
||||
.key {
|
||||
width: 20%;
|
||||
font-weight: bold;
|
||||
}
|
||||
.value i {
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
<div id="eventviewer-modal-dialog" title="Log View" style="display: none;">
|
||||
<ul id="eventview-tabs" class="nav nav-tabs">
|
||||
<li class="active"><a href="#status" id="status-link" data-toggle="tab" ng-click="toggleTab($event, 'status-link', 'eventview-tabs')">Status</a></li>
|
||||
<li class="active"><a href="#status" id="status-link" data-toggle="tab" ng-click="toggleTab($event, 'status-link', 'eventview-tabs')">Event</a></li>
|
||||
<li><a href="#results" id="results-link" data-toggle="tab" ng-click="toggleTab($event, 'results-link', 'eventview-tabs')">Results</a></li>
|
||||
<li><a href="#timing" id="timing-link" data-toggle="tab" ng-click="toggleTab($event, 'timing-link', 'eventview-tabs')">Timing</a></li>
|
||||
<li><a href="#stdout" id="stdout-link" data-toggle="tab" ng-click="toggleTab($event, 'stdout-link', 'eventview-tabs')">Standard Out</a></li>
|
||||
<li><a href="#stderr" id="stderr-link" data-toggle="tab" ng-click="toggleTab($event, 'stderr-link', 'eventview-tabs')">Standard Error</a></li>
|
||||
<li><a href="#traceback" id="traceback-link" data-toggle="tab" ng-click="toggleTab($event, 'traceback-link', 'eventview-tabs')">Traceback</a></li>
|
||||
@ -9,6 +11,12 @@
|
||||
<div class="tab-pane active" id="status">
|
||||
<div id="status-form-container"></div>
|
||||
</div>
|
||||
<div class="tab-pane" id="results">
|
||||
<div id="results-form-container"></div>
|
||||
</div>
|
||||
<div class="tab-pane" id="timing">
|
||||
<div id="timing-form-container"></div>
|
||||
</div>
|
||||
<div class="tab-pane" id="stdout">
|
||||
<div id="stdout-form-container"></div>
|
||||
</div>
|
||||
|
||||
@ -78,8 +78,6 @@
|
||||
<script src="{{ STATIC_URL }}js/controllers/Projects.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/controllers/Jobs.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/controllers/JobDetail.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/controllers/JobEvents.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/controllers/JobHosts.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/controllers/JobTemplates.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/controllers/JobStdout.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/controllers/Permissions.js"></script>
|
||||
@ -98,8 +96,7 @@
|
||||
<script src="{{ STATIC_URL }}js/forms/Projects.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/forms/ProjectStatus.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/forms/Permissions.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/forms/JobEventData.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/forms/JobEvents.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/forms/EventsViewer.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/forms/HostGroups.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/forms/ActivityDetail.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/forms/JobSummary.js"></script>
|
||||
@ -122,8 +119,6 @@
|
||||
<script src="{{ STATIC_URL }}js/lists/CompletedJobs.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/lists/RunningJobs.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/lists/QueuedJobs.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/lists/JobEvents.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/lists/JobHosts.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/lists/Permissions.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/lists/Streams.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/lists/HomeGroups.js"></script>
|
||||
@ -142,7 +137,6 @@
|
||||
<script src="{{ STATIC_URL }}js/helpers/JobSubmission.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/helpers/Lookup.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/helpers/Parse.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/helpers/Events.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/helpers/Children.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/helpers/ProjectPath.js"></script>
|
||||
<script src="{{ STATIC_URL }}js/helpers/md5.js"></script>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user