mirror of
https://github.com/ansible/awx.git
synced 2026-05-08 01:47:35 -02:30
fixed the columns and sizing of portal widgets
i had to create new list items for the two widgets. portaljobtemplates and portaljobs which resemeble their respective parent copies but with many things removed and column widths adjusted. i also changed the sizing functions for the job list widget to better handle browser resizing
This commit is contained in:
@@ -60,6 +60,7 @@ angular.module('Tower', [
|
|||||||
'CredentialFormDefinition',
|
'CredentialFormDefinition',
|
||||||
'LookUpHelper',
|
'LookUpHelper',
|
||||||
'JobTemplatesListDefinition',
|
'JobTemplatesListDefinition',
|
||||||
|
'PortalJobTemplatesListDefinition',
|
||||||
'JobTemplateFormDefinition',
|
'JobTemplateFormDefinition',
|
||||||
'JobTemplatesHelper',
|
'JobTemplatesHelper',
|
||||||
'JobSubmissionHelper',
|
'JobSubmissionHelper',
|
||||||
@@ -86,7 +87,7 @@ angular.module('Tower', [
|
|||||||
'HostPieChartWidget',
|
'HostPieChartWidget',
|
||||||
'HostGraphWidget',
|
'HostGraphWidget',
|
||||||
'DashboardJobsWidget',
|
'DashboardJobsWidget',
|
||||||
'PortalJobTemplateWidget',
|
'PortalJobsWidget',
|
||||||
'StreamWidget',
|
'StreamWidget',
|
||||||
'JobsHelper',
|
'JobsHelper',
|
||||||
'InventoryGroupsHelpDefinition',
|
'InventoryGroupsHelpDefinition',
|
||||||
@@ -117,7 +118,8 @@ angular.module('Tower', [
|
|||||||
'LoadConfigHelper',
|
'LoadConfigHelper',
|
||||||
'SocketHelper',
|
'SocketHelper',
|
||||||
'AboutAnsibleHelpModal',
|
'AboutAnsibleHelpModal',
|
||||||
'SurveyQuestionFormDefinition'
|
'SurveyQuestionFormDefinition',
|
||||||
|
'PortalJobsListDefinition'
|
||||||
])
|
])
|
||||||
|
|
||||||
.constant('AngularScheduler.partials', urlPrefix + 'lib/angular-scheduler/lib/')
|
.constant('AngularScheduler.partials', urlPrefix + 'lib/angular-scheduler/lib/')
|
||||||
|
|||||||
@@ -25,15 +25,15 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function PortalController($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, ClearScope, Stream, Rest, GetBasePath, ProcessErrors,
|
function PortalController($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, ClearScope, Stream, Rest, GetBasePath, ProcessErrors,
|
||||||
Button, PortalJobTemplate, GenerateList, JobTemplateList, SearchInit, PaginateInit, PlaybookRun){
|
Button, PortalJobsWidget, GenerateList, PortalJobTemplateList, SearchInit, PaginateInit, PlaybookRun){
|
||||||
|
|
||||||
ClearScope('portal');
|
ClearScope('portal');
|
||||||
|
|
||||||
var html,
|
var html,
|
||||||
e,
|
e,
|
||||||
winHeight,
|
// winHeight,
|
||||||
available_height,
|
// available_height,
|
||||||
list = JobTemplateList,
|
list = PortalJobTemplateList,
|
||||||
view= GenerateList,
|
view= GenerateList,
|
||||||
defaultUrl = GetBasePath('job_templates'),
|
defaultUrl = GetBasePath('job_templates'),
|
||||||
buttons = {
|
buttons = {
|
||||||
@@ -71,9 +71,9 @@ function PortalController($scope, $compile, $routeParams, $rootScope, $location,
|
|||||||
$scope.removeLoadPortal();
|
$scope.removeLoadPortal();
|
||||||
}
|
}
|
||||||
$scope.removeLoadPortal = $scope.$on('LoadPortal', function () {
|
$scope.removeLoadPortal = $scope.$on('LoadPortal', function () {
|
||||||
winHeight = $(window).height();
|
// winHeight = $(window).height();
|
||||||
available_height = Math.floor(winHeight - $('#main-menu-container .navbar').outerHeight() - $('#refresh-row').outerHeight() - 30);
|
// available_height = Math.floor(winHeight - $('#main-menu-container .navbar').outerHeight() - $('#refresh-row').outerHeight() - 30);
|
||||||
$('.portal-container').height(available_height);
|
// $('.portal-container').height(available_height);
|
||||||
|
|
||||||
view.inject( list, {
|
view.inject( list, {
|
||||||
id : 'portal-job-template',
|
id : 'portal-job-template',
|
||||||
@@ -106,7 +106,7 @@ function PortalController($scope, $compile, $routeParams, $rootScope, $location,
|
|||||||
|
|
||||||
$scope.search(list.iterator);
|
$scope.search(list.iterator);
|
||||||
|
|
||||||
PortalJobTemplate({
|
PortalJobsWidget({
|
||||||
scope: $scope,
|
scope: $scope,
|
||||||
target: 'portal-jobs',
|
target: 'portal-jobs',
|
||||||
// dashboard: data
|
// dashboard: data
|
||||||
@@ -136,5 +136,5 @@ function PortalController($scope, $compile, $routeParams, $rootScope, $location,
|
|||||||
}
|
}
|
||||||
|
|
||||||
PortalController.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', '$log','Wait', 'ClearScope', 'Stream', 'Rest', 'GetBasePath', 'ProcessErrors',
|
PortalController.$inject = ['$scope', '$compile', '$routeParams', '$rootScope', '$location', '$log','Wait', 'ClearScope', 'Stream', 'Rest', 'GetBasePath', 'ProcessErrors',
|
||||||
'Button', 'PortalJobTemplate', 'GenerateList' , 'JobTemplateList', 'SearchInit', 'PaginateInit', 'PlaybookRun'
|
'Button', 'PortalJobsWidget', 'GenerateList' , 'PortalJobTemplateList', 'SearchInit', 'PaginateInit', 'PlaybookRun'
|
||||||
];
|
];
|
||||||
|
|||||||
47
awx/ui/static/js/lists/PortalJobTemplates.js
Normal file
47
awx/ui/static/js/lists/PortalJobTemplates.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/*********************************************
|
||||||
|
* Copyright (c) 2014 AnsibleWorks, Inc.
|
||||||
|
*
|
||||||
|
* PoralJobTemplates.js
|
||||||
|
* List view object for Job Templates data model.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('PortalJobTemplatesListDefinition', [])
|
||||||
|
.value('PortalJobTemplateList', {
|
||||||
|
|
||||||
|
name: 'job_templates',
|
||||||
|
iterator: 'job_template',
|
||||||
|
selectTitle: 'Add Job Template',
|
||||||
|
editTitle: 'Job Templates',
|
||||||
|
selectInstructions: "Click on a row to select it, and click Finished when done. Use the <i class=\"icon-plus\"></i> " +
|
||||||
|
"button to create a new job template.",
|
||||||
|
index: false,
|
||||||
|
hover: true,
|
||||||
|
|
||||||
|
fields: {
|
||||||
|
name: {
|
||||||
|
label: 'Name',
|
||||||
|
columnClass: 'col-lg-5 col-md-5 col-sm-9 col-xs-8'
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
label: 'Description',
|
||||||
|
columnClass: 'col-lg-4 col-md-4 hidden-sm hidden-xs'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
},
|
||||||
|
|
||||||
|
fieldActions: {
|
||||||
|
submit: {
|
||||||
|
label: 'Launch',
|
||||||
|
mode: 'all',
|
||||||
|
ngClick: 'submitJob(job_template.id)',
|
||||||
|
awToolTip: 'Start a job using this template',
|
||||||
|
dataPlacement: 'top'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
81
awx/ui/static/js/lists/PortalJobs.js
Normal file
81
awx/ui/static/js/lists/PortalJobs.js
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/*********************************************
|
||||||
|
* Copyright (c) 2014 AnsibleWorks, Inc.
|
||||||
|
*
|
||||||
|
* PortalJobs.js
|
||||||
|
* List view object for portal job data model.
|
||||||
|
*
|
||||||
|
* Used on dashboard to provide a list of all jobs, regardless of
|
||||||
|
* status.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('PortalJobsListDefinition', [])
|
||||||
|
.value( 'PortalJobsList', {
|
||||||
|
|
||||||
|
name: 'jobs',
|
||||||
|
iterator: 'job',
|
||||||
|
editTitle: 'Jobs',
|
||||||
|
// 'class': 'table-condensed',
|
||||||
|
index: false,
|
||||||
|
hover: true,
|
||||||
|
well: false,
|
||||||
|
|
||||||
|
fields: {
|
||||||
|
id: {
|
||||||
|
label: 'ID',
|
||||||
|
//ngClick:"viewJobLog(job.id)",
|
||||||
|
key: true,
|
||||||
|
noLink: true, //undocumented: 'key' above will automatically made the fields a link, but 'noLink' will override this setting
|
||||||
|
desc: true,
|
||||||
|
searchType: 'int',
|
||||||
|
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2',
|
||||||
|
// awToolTip: "{{ job.status_tip }}",
|
||||||
|
// awTipPlacement: "top",
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
label: 'Status',
|
||||||
|
columnClass: 'col-lg-1 col-md-2 col-sm-2 col-xs-2',
|
||||||
|
// awToolTip: "{{ job.status_tip }}",
|
||||||
|
// awTipPlacement: "top",
|
||||||
|
dataTitle: "{{ job.status_popover_title }}",
|
||||||
|
icon: 'icon-job-{{ job.status }}',
|
||||||
|
iconOnly: true,
|
||||||
|
// ngClick:"viewJobLog(job.id)",
|
||||||
|
searchable: true,
|
||||||
|
nosort: true,
|
||||||
|
searchType: 'select',
|
||||||
|
searchOptions: [
|
||||||
|
{ name: "Success", value: "successful" },
|
||||||
|
{ name: "Error", value: "error" },
|
||||||
|
{ name: "Failed", value: "failed" },
|
||||||
|
{ name: "Canceled", value: "canceled" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
started: {
|
||||||
|
label: 'Started',
|
||||||
|
noLink: true,
|
||||||
|
searchable: false,
|
||||||
|
filter: "date:'MM/dd HH:mm:ss'",
|
||||||
|
columnClass: "col-lg-3 col-md-3 hidden-xs"
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
label: 'Name',
|
||||||
|
columnClass: 'col-md-5 col-xs-5',
|
||||||
|
//ngClick: "viewJobLog(job.id, job.nameHref)",
|
||||||
|
defaultSearchField: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: { },
|
||||||
|
|
||||||
|
fieldActions: {
|
||||||
|
job_details: {
|
||||||
|
mode: 'all',
|
||||||
|
ngClick: "viewJobLog(job.id)",
|
||||||
|
awToolTip: 'View job details',
|
||||||
|
dataPlacement: 'top'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
178
awx/ui/static/js/widgets/PortalJobs.js
Normal file
178
awx/ui/static/js/widgets/PortalJobs.js
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
/*********************************************
|
||||||
|
* Copyright (c) 2014 AnsibleWorks, Inc.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @ngdoc function
|
||||||
|
* @name widgets.function:DashboardJobs
|
||||||
|
* @description
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('PortalJobsWidget', ['RestServices', 'Utilities'])
|
||||||
|
.factory('PortalJobsWidget', ['$rootScope', '$compile', 'LoadSchedulesScope', 'LoadJobsScope', 'PortalJobsList', 'ScheduledJobsList', 'GetChoices', 'GetBasePath',
|
||||||
|
function ($rootScope, $compile, LoadSchedulesScope, LoadJobsScope, PortalJobsList, ScheduledJobsList, GetChoices, GetBasePath) {
|
||||||
|
return function (params) {
|
||||||
|
var scope = params.scope,
|
||||||
|
target = params.target,
|
||||||
|
choicesCount = 0,
|
||||||
|
listCount = 0,
|
||||||
|
jobs_scope = scope.$new(true),
|
||||||
|
scheduled_scope = scope.$new(true),
|
||||||
|
max_rows,
|
||||||
|
html, e;
|
||||||
|
|
||||||
|
html = '';
|
||||||
|
html += "<div class=\"portal-job-template-container\">\n";
|
||||||
|
// html+= "<span id='portal-job-template-header'>Job Templates </span>";
|
||||||
|
// html += "<ul id=\"job_status_tabs\" class=\"nav nav-tabs\">\n";
|
||||||
|
// html += "<li class=\"active\"><a id=\"active_jobs_link\" ng-click=\"toggleTab($event, 'active_jobs_link', 'job_status_tabs')\"\n";
|
||||||
|
// html += " href=\"#active-jobs-tab\" data-toggle=\"tab\">Jobs</a></li>\n";
|
||||||
|
// html += "<li><a id=\"scheduled_jobs_link\" ng-click=\"toggleTab($event, 'scheduled_jobs_link', 'job_status_tabs')\"\n";
|
||||||
|
// html += "href=\"#scheduled-jobs-tab\" data-toggle=\"tab\">Schedule</a></li>\n";
|
||||||
|
// html += "</ul>\n";
|
||||||
|
// html += "<div id=\"portal-job-template-tab-content\" class=\"tab-content \">\n";
|
||||||
|
html += "<div class=\"tab-pane active\" id=\"active-jobs-tab\">\n";
|
||||||
|
html += "<div class=\"row search-row\">\n";
|
||||||
|
html += "<div class=\"col-lg-6 col-md-6\" id=\"active-jobs-search-container\"></div>\n";
|
||||||
|
html += "</div>\n"; //row
|
||||||
|
html += "<div class=\"job-list\" id=\"active-jobs-container\">\n";
|
||||||
|
html += "<div id=\"active-jobs\" class=\"job-list-target\"></div>\n";
|
||||||
|
html += "</div>\n"; //list
|
||||||
|
html += "</div>\n"; //active-jobs-tab
|
||||||
|
// html += "<div class=\"tab-pane\" id=\"scheduled-jobs-tab\"></div>\n";
|
||||||
|
// html += "</div>\n"; // jobs-list-container
|
||||||
|
html += "</div>\n";
|
||||||
|
|
||||||
|
e = angular.element(document.getElementById(target));
|
||||||
|
e.html(html);
|
||||||
|
$compile(e)(scope);
|
||||||
|
|
||||||
|
if (scope.removeListLoaded) {
|
||||||
|
scope.removeListLoaded();
|
||||||
|
}
|
||||||
|
scope.removeListLoaded = scope.$on('listLoaded', function() {
|
||||||
|
listCount++;
|
||||||
|
if (listCount === 1) {
|
||||||
|
//api_complete = true;
|
||||||
|
scope.$emit('WidgetLoaded', "dashboard_jobs", jobs_scope, scheduled_scope);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// After all choices are ready, load up the lists and populate the page
|
||||||
|
if (scope.removeBuildJobsList) {
|
||||||
|
scope.removeBuildJobsList();
|
||||||
|
}
|
||||||
|
scope.removeBuildJobsList = scope.$on('buildJobsList', function() {
|
||||||
|
if (PortalJobsList.fields.type) {
|
||||||
|
PortalJobsList.fields.type.searchOptions = scope.type_choices;
|
||||||
|
}
|
||||||
|
LoadJobsScope({
|
||||||
|
parent_scope: scope,
|
||||||
|
scope: jobs_scope,
|
||||||
|
list: PortalJobsList,
|
||||||
|
id: 'active-jobs',
|
||||||
|
url: GetBasePath('unified_jobs') + '?status__in=running,completed,failed,successful,error,canceled',
|
||||||
|
pageSize: max_rows,
|
||||||
|
spinner: false
|
||||||
|
});
|
||||||
|
// LoadSchedulesScope({
|
||||||
|
// parent_scope: scope,
|
||||||
|
// scope: scheduled_scope,
|
||||||
|
// list: ScheduledJobsList,
|
||||||
|
// id: 'scheduled-jobs-tab',
|
||||||
|
// url: GetBasePath('schedules') + '?next_run__isnull=false',
|
||||||
|
// pageSize: max_rows,
|
||||||
|
// spinner: false
|
||||||
|
// });
|
||||||
|
|
||||||
|
$(window).resize(_.debounce(function() {
|
||||||
|
resizePortalJobsWidget();
|
||||||
|
}, 500));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (scope.removeChoicesReady) {
|
||||||
|
scope.removeChoicesReady();
|
||||||
|
}
|
||||||
|
scope.removeChoicesReady = scope.$on('choicesReady', function() {
|
||||||
|
choicesCount++;
|
||||||
|
if (choicesCount === 2) {
|
||||||
|
setPortalJobsHeight();
|
||||||
|
scope.$emit('buildJobsList');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
GetChoices({
|
||||||
|
scope: scope,
|
||||||
|
url: GetBasePath('unified_jobs'),
|
||||||
|
field: 'status',
|
||||||
|
variable: 'status_choices',
|
||||||
|
callback: 'choicesReady'
|
||||||
|
});
|
||||||
|
|
||||||
|
GetChoices({
|
||||||
|
scope: scope,
|
||||||
|
url: GetBasePath('unified_jobs'),
|
||||||
|
field: 'type',
|
||||||
|
variable: 'type_choices',
|
||||||
|
callback: 'choicesReady'
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Set the height of each container and calc max number of rows containers can hold
|
||||||
|
function setPortalJobsHeight() {
|
||||||
|
var docw = $(window).width(),
|
||||||
|
box_height, available_height, search_row, page_row, height, header, row_height;
|
||||||
|
|
||||||
|
available_height = Math.floor($(window).height() - $('#main-menu-container .navbar').outerHeight() - $('#refresh-row').outerHeight() - 30);
|
||||||
|
$('.portal-job-template-container').height(available_height);
|
||||||
|
$('.portal-container').height(available_height);
|
||||||
|
search_row = Math.max($('.search-row:eq(0)').outerHeight(), 50);
|
||||||
|
page_row = Math.max($('.page-row:eq(0)').outerHeight(), 33);
|
||||||
|
header = 0; //Math.max($('#completed_jobs_table thead').height(), 41);
|
||||||
|
height = Math.floor(available_height) - header - page_row - search_row ;
|
||||||
|
if (docw < 765 && docw >= 493) {
|
||||||
|
row_height = 27;
|
||||||
|
}
|
||||||
|
else if (docw < 493) {
|
||||||
|
row_height = 47;
|
||||||
|
}
|
||||||
|
else if (docw < 865) {
|
||||||
|
row_height = 87;
|
||||||
|
}
|
||||||
|
else if (docw < 925) {
|
||||||
|
row_height = 67;
|
||||||
|
}
|
||||||
|
else if (docw < 1415) {
|
||||||
|
row_height = 47;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
row_height = 35;
|
||||||
|
}
|
||||||
|
max_rows = Math.floor(height / row_height);
|
||||||
|
if (max_rows < 5){
|
||||||
|
box_height = header+page_row + search_row + 40 + (5 * row_height);
|
||||||
|
if (docw < 1140) {
|
||||||
|
box_height += 40;
|
||||||
|
}
|
||||||
|
$('.portal-job-template-container').height(box_height);
|
||||||
|
max_rows = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set container height and return the number of allowed rows
|
||||||
|
function resizePortalJobsWidget() {
|
||||||
|
setPortalJobsHeight();
|
||||||
|
jobs_scope[PortalJobsList.iterator + '_page_size'] = max_rows;
|
||||||
|
jobs_scope.changePageSize(PortalJobsList.name, PortalJobsList.iterator, false);
|
||||||
|
// scheduled_scope[ScheduledJobsList.iterator + '_page_size'] = max_rows;
|
||||||
|
// scheduled_scope.changePageSize(ScheduledJobsList.name, ScheduledJobsList.iterator, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]);
|
||||||
@@ -114,8 +114,10 @@
|
|||||||
<script src="{{ STATIC_URL }}js/lists/InventoryHosts.js"></script>
|
<script src="{{ STATIC_URL }}js/lists/InventoryHosts.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/lists/Credentials.js"></script>
|
<script src="{{ STATIC_URL }}js/lists/Credentials.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/lists/JobTemplates.js"></script>
|
<script src="{{ STATIC_URL }}js/lists/JobTemplates.js"></script>
|
||||||
|
<script src="{{ STATIC_URL }}js/lists/PortalJobTemplates.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/lists/Projects.js"></script>
|
<script src="{{ STATIC_URL }}js/lists/Projects.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/lists/Jobs.js"></script>
|
<script src="{{ STATIC_URL }}js/lists/Jobs.js"></script>
|
||||||
|
<script src="{{ STATIC_URL }}js/lists/PortalJobs.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/lists/CompletedJobs.js"></script>
|
<script src="{{ STATIC_URL }}js/lists/CompletedJobs.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/lists/RunningJobs.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/QueuedJobs.js"></script>
|
||||||
@@ -167,7 +169,7 @@
|
|||||||
<script src="{{ STATIC_URL }}js/widgets/HostGraph.js"></script>
|
<script src="{{ STATIC_URL }}js/widgets/HostGraph.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/widgets/JobStatusGraph.js"></script>
|
<script src="{{ STATIC_URL }}js/widgets/JobStatusGraph.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/widgets/DashboardJobs.js"></script>
|
<script src="{{ STATIC_URL }}js/widgets/DashboardJobs.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/widgets/PortalJobTemplate.js"></script>
|
<script src="{{ STATIC_URL }}js/widgets/PortalJobs.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/widgets/Stream.js"></script>
|
<script src="{{ STATIC_URL }}js/widgets/Stream.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/help/InventoryGroups.js"></script>
|
<script src="{{ STATIC_URL }}js/help/InventoryGroups.js"></script>
|
||||||
<script src="{{ STATIC_URL }}js/help/FirefoxSocketHelp.js"></script>
|
<script src="{{ STATIC_URL }}js/help/FirefoxSocketHelp.js"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user