Replaced TB accordions with JQueryUI accordions. We now keep track of which accordions are open on each page. When the user navigates away and then comes back to a page, the accordions are restored. Accordion state is stored in the session cookie.
BIN
awx/ui/static/css/custom-theme/images/animated-overlay.gif
Normal file
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 212 B |
After Width: | Height: | Size: 208 B |
After Width: | Height: | Size: 208 B |
After Width: | Height: | Size: 206 B |
After Width: | Height: | Size: 206 B |
After Width: | Height: | Size: 326 B |
After Width: | Height: | Size: 262 B |
After Width: | Height: | Size: 332 B |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 253 B |
After Width: | Height: | Size: 292 B |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.4 KiB |
1183
awx/ui/static/css/custom-theme/jquery-ui-1.10.3.custom.css
vendored
Normal file
5
awx/ui/static/css/custom-theme/jquery-ui-1.10.3.custom.min.css
vendored
Normal file
@ -134,6 +134,15 @@ angular.module('ansible', [
|
||||
when('/teams/:team_id', { templateUrl: urlPrefix + 'partials/teams.html',
|
||||
controller: TeamsEdit }).
|
||||
|
||||
when('/teams/:team_id/permissions/add', { templateUrl: urlPrefix + 'partials/teams.html',
|
||||
controller: PermissionsAdd }).
|
||||
|
||||
when('/teams/:team_id/permissions', { templateUrl: urlPrefix + 'partials/teams.html',
|
||||
controller: PermissionsList }).
|
||||
|
||||
when('/teams/:team_id/permissions/:permission_id', { templateUrl: urlPrefix + 'partials/teams.html',
|
||||
controller: PermissionsEdit }).
|
||||
|
||||
when('/teams/:team_id/users', { templateUrl: urlPrefix + 'partials/teams.html',
|
||||
controller: UsersList }).
|
||||
|
||||
|
@ -317,13 +317,23 @@ function TeamsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams,
|
||||
// Related set: Add button
|
||||
scope.add = function(set) {
|
||||
$rootScope.flashMessage = null;
|
||||
if (set == 'permissions') {
|
||||
$location.path('/' + base + '/' + $routeParams.team_id + '/' + set + '/add');
|
||||
}
|
||||
else {
|
||||
$location.path('/' + base + '/' + $routeParams.team_id + '/' + set);
|
||||
}
|
||||
};
|
||||
|
||||
// Related set: Edit button
|
||||
scope.edit = function(set, id, name) {
|
||||
$rootScope.flashMessage = null;
|
||||
if (set == 'permissions') {
|
||||
$location.path('/' + base + '/' + $routeParams.team_id + '/' + set + '/' + id);
|
||||
}
|
||||
else {
|
||||
$location.path('/' + set + '/' + id);
|
||||
}
|
||||
};
|
||||
|
||||
// Related set: Delete button
|
||||
@ -331,6 +341,22 @@ function TeamsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams,
|
||||
$rootScope.flashMessage = null;
|
||||
|
||||
var action = function() {
|
||||
var url;
|
||||
if (set == 'permissions') {
|
||||
url = GetBasePath('base') + 'permissions/' + itm_id + '/';
|
||||
Rest.setUrl(url);
|
||||
Rest.destroy()
|
||||
.success( function(data, status, headers, config) {
|
||||
$('#prompt-modal').modal('hide');
|
||||
scope.search(form.related[set].iterator);
|
||||
})
|
||||
.error( function(data, status, headers, config) {
|
||||
$('#prompt-modal').modal('hide');
|
||||
ProcessErrors(scope, data, status, null,
|
||||
{ hdr: 'Error!', msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
|
||||
});
|
||||
}
|
||||
else {
|
||||
var url = defaultUrl + $routeParams.team_id + '/' + set + '/';
|
||||
Rest.setUrl(url);
|
||||
Rest.post({ id: itm_id, disassociate: 1 })
|
||||
@ -343,13 +369,13 @@ function TeamsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams,
|
||||
ProcessErrors(scope, data, status, null,
|
||||
{ hdr: 'Error!', msg: 'Call to ' + url + ' failed. POST returned status: ' + status });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Prompt({ hdr: 'Delete',
|
||||
body: 'Are you sure you want to remove ' + name + ' from ' + scope.name + ' ' + title + '?',
|
||||
action: action
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,6 +14,10 @@ angular.module('TeamFormDefinition', [])
|
||||
editTitle: '{{ name }}', //Legend in edit mode
|
||||
name: 'team',
|
||||
well: true,
|
||||
collapse: true,
|
||||
collapseTitle: 'Team Settings',
|
||||
collapseMode: 'edit',
|
||||
collapseOpen: true,
|
||||
|
||||
fields: {
|
||||
name: {
|
||||
@ -58,6 +62,146 @@ angular.module('TeamFormDefinition', [])
|
||||
|
||||
related: { //related colletions (and maybe items?)
|
||||
|
||||
credentials: {
|
||||
type: 'collection',
|
||||
title: 'Credentials',
|
||||
iterator: 'credential',
|
||||
open: false,
|
||||
|
||||
actions: {
|
||||
add: {
|
||||
ngClick: "add('credentials')",
|
||||
icon: 'icon-plus',
|
||||
label: 'Add',
|
||||
add: 'Add a new credential'
|
||||
}
|
||||
},
|
||||
|
||||
fields: {
|
||||
name: {
|
||||
key: true,
|
||||
label: 'Name'
|
||||
},
|
||||
description: {
|
||||
label: 'Description'
|
||||
}
|
||||
},
|
||||
|
||||
fieldActions: {
|
||||
edit: {
|
||||
label: 'Edit',
|
||||
ngClick: "edit('credentials', \{\{ credential.id \}\}, '\{\{ credential.name \}\}')",
|
||||
icon: 'icon-edit',
|
||||
"class": 'btn-success',
|
||||
awToolTip: 'Modify the credential'
|
||||
},
|
||||
"delete": {
|
||||
label: 'Delete',
|
||||
ngClick: "delete('credentials', \{\{ credential.id \}\}, '\{\{ credential.name \}\}', 'credentials')",
|
||||
icon: 'icon-remove',
|
||||
"class": 'btn-danger',
|
||||
awToolTip: 'Remove the credential'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
permissions: {
|
||||
type: 'collection',
|
||||
title: 'Permissions',
|
||||
iterator: 'permission',
|
||||
open: false,
|
||||
|
||||
actions: {
|
||||
add: {
|
||||
ngClick: "add('permissions')",
|
||||
icon: 'icon-plus',
|
||||
label: 'Add',
|
||||
awToolTip: 'Add a permission for this user'
|
||||
}
|
||||
},
|
||||
|
||||
fields: {
|
||||
name: {
|
||||
key: true,
|
||||
label: 'Name',
|
||||
ngClick: "edit('permissions', \{\{ permission.id \}\}, '\{\{ permission.name \}\}')"
|
||||
},
|
||||
project: {
|
||||
label: 'Project',
|
||||
sourceModel: 'project',
|
||||
sourceField: 'name',
|
||||
ngBind: 'permission.summary_fields.project.name',
|
||||
},
|
||||
inventory: {
|
||||
label: 'Inventory',
|
||||
sourceModel: 'inventory',
|
||||
sourceField: 'name',
|
||||
ngBind: 'permission.summary_fields.inventory.name',
|
||||
}
|
||||
},
|
||||
|
||||
fieldActions: {
|
||||
edit: {
|
||||
label: 'Edit',
|
||||
ngClick: "edit('permissions', \{\{ permission.id \}\}, '\{\{ permission.name \}\}')",
|
||||
icon: 'icon-edit',
|
||||
"class": 'btn-success',
|
||||
awToolTip: 'Edit the permission'
|
||||
},
|
||||
|
||||
"delete": {
|
||||
label: 'Delete',
|
||||
ngClick: "delete('permissions', \{\{ permission.id \}\}, '\{\{ permission.name \}\}', 'permissions')",
|
||||
icon: 'icon-remove',
|
||||
"class": 'btn-danger',
|
||||
awToolTip: 'Delete the permission'
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
projects: {
|
||||
type: 'collection',
|
||||
title: 'Projects',
|
||||
iterator: 'project',
|
||||
open: false,
|
||||
|
||||
actions: {
|
||||
add: {
|
||||
ngClick: "add('projects')",
|
||||
icon: 'icon-plus',
|
||||
label: 'Add'
|
||||
}
|
||||
},
|
||||
|
||||
fields: {
|
||||
name: {
|
||||
key: true,
|
||||
label: 'Name'
|
||||
},
|
||||
description: {
|
||||
label: 'Description'
|
||||
}
|
||||
},
|
||||
|
||||
fieldActions: {
|
||||
edit: {
|
||||
label: 'Edit',
|
||||
ngClick: "edit('projects', \{\{ project.id \}\}, '\{\{ project.name \}\}')",
|
||||
icon: 'icon-edit',
|
||||
"class": 'btn-success',
|
||||
awToolTip: 'Modify the project'
|
||||
},
|
||||
"delete": {
|
||||
label: 'Delete',
|
||||
ngClick: "delete('projects', \{\{ project.id \}\}, '\{\{ project.name \}\}', 'projects')",
|
||||
icon: 'icon-remove',
|
||||
"class": 'btn-danger',
|
||||
awToolTip: 'Remove the project'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
users: {
|
||||
type: 'collection',
|
||||
title: 'Users',
|
||||
@ -102,91 +246,6 @@ angular.module('TeamFormDefinition', [])
|
||||
awToolTip: 'Remove user'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
credentials: {
|
||||
type: 'collection',
|
||||
title: 'Credentials',
|
||||
iterator: 'credential',
|
||||
open: false,
|
||||
|
||||
actions: {
|
||||
add: {
|
||||
ngClick: "add('credentials')",
|
||||
icon: 'icon-plus',
|
||||
label: 'Add',
|
||||
add: 'Add a new credential'
|
||||
}
|
||||
},
|
||||
|
||||
fields: {
|
||||
name: {
|
||||
key: true,
|
||||
label: 'Name'
|
||||
},
|
||||
description: {
|
||||
label: 'Description'
|
||||
}
|
||||
},
|
||||
|
||||
fieldActions: {
|
||||
edit: {
|
||||
label: 'Edit',
|
||||
ngClick: "edit('credentials', \{\{ credential.id \}\}, '\{\{ credential.name \}\}')",
|
||||
icon: 'icon-edit',
|
||||
"class": 'btn-success',
|
||||
awToolTip: 'Modify the credential'
|
||||
},
|
||||
"delete": {
|
||||
label: 'Delete',
|
||||
ngClick: "delete('credentials', \{\{ credential.id \}\}, '\{\{ credential.name \}\}', 'credentials')",
|
||||
icon: 'icon-remove',
|
||||
"class": 'btn-danger',
|
||||
awToolTip: 'Remove the credential'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
projects: {
|
||||
type: 'collection',
|
||||
title: 'Projects',
|
||||
iterator: 'project',
|
||||
open: false,
|
||||
|
||||
actions: {
|
||||
add: {
|
||||
ngClick: "add('projects')",
|
||||
icon: 'icon-plus',
|
||||
label: 'Add'
|
||||
}
|
||||
},
|
||||
|
||||
fields: {
|
||||
name: {
|
||||
key: true,
|
||||
label: 'Name'
|
||||
},
|
||||
description: {
|
||||
label: 'Description'
|
||||
}
|
||||
},
|
||||
|
||||
fieldActions: {
|
||||
edit: {
|
||||
label: 'Edit',
|
||||
ngClick: "edit('projects', \{\{ project.id \}\}, '\{\{ project.name \}\}')",
|
||||
icon: 'icon-edit',
|
||||
"class": 'btn-success',
|
||||
awToolTip: 'Modify the project'
|
||||
},
|
||||
"delete": {
|
||||
label: 'Delete',
|
||||
ngClick: "delete('projects', \{\{ project.id \}\}, '\{\{ project.name \}\}', 'projects')",
|
||||
icon: 'icon-remove',
|
||||
"class": 'btn-danger',
|
||||
awToolTip: 'Remove the project'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,6 +14,10 @@ angular.module('UserFormDefinition', [])
|
||||
editTitle: '{{ username }}', //Legend in edit mode
|
||||
name: 'user', //Form name attribute
|
||||
well: true, //Wrap the form with TB well
|
||||
collapse: true,
|
||||
collapseTitle: 'User Settings',
|
||||
collapseMode: 'edit',
|
||||
collapseOpen: true,
|
||||
|
||||
fields: {
|
||||
username: {
|
||||
|
@ -8,9 +8,9 @@
|
||||
*
|
||||
*/
|
||||
|
||||
angular.module('FormGenerator', ['GeneratorHelpers'])
|
||||
.factory('GenerateForm', [ '$compile', 'SearchWidget', 'PaginateWidget', 'Attr', 'Icon', 'Column',
|
||||
function($compile, SearchWidget, PaginateWidget, Attr, Icon, Column) {
|
||||
angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
|
||||
.factory('GenerateForm', [ '$location', '$cookieStore', '$compile', 'SearchWidget', 'PaginateWidget', 'Attr', 'Icon', 'Column',
|
||||
function($location, $cookieStore, $compile, SearchWidget, PaginateWidget, Attr, Icon, Column) {
|
||||
return {
|
||||
|
||||
setForm: function(form) {
|
||||
@ -106,18 +106,66 @@ angular.module('FormGenerator', ['GeneratorHelpers'])
|
||||
},
|
||||
|
||||
addListeners: function() {
|
||||
// Listen for accordion collapse events and toggle the header icon
|
||||
$('.collapse')
|
||||
.on('show', function() {
|
||||
var element = $(this).parent().find('.accordion-heading i');
|
||||
element.removeClass('icon-angle-down');
|
||||
element.addClass('icon-angle-up');
|
||||
})
|
||||
.on('hide', function() {
|
||||
var element = $(this).parent().find('.accordion-heading i');
|
||||
element.removeClass('icon-angle-up');
|
||||
element.addClass('icon-angle-down');
|
||||
|
||||
$('.jqui-accordion').each( function(index) {
|
||||
|
||||
var active = false;
|
||||
var list = $cookieStore.get('accordions');
|
||||
var found = false;
|
||||
if (list) {
|
||||
var id = $(this).attr('id');
|
||||
var base = ($location.path().replace(/^\//,'').split('/')[0]);
|
||||
for (var i=0; i < list.length && found == false; i++) {
|
||||
if (list[i].base == base && list[i].id == id) {
|
||||
found = true;
|
||||
active = list[i].active;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found == false && $(this).attr('data-open') == 'true') {
|
||||
active = 0;
|
||||
}
|
||||
|
||||
$(this).accordion({
|
||||
collapsible: true,
|
||||
heightStyle: 'content',
|
||||
active: active,
|
||||
activate: function( event, ui ) {
|
||||
$('.jqui-accordion').each( function(index) {
|
||||
var active = $(this).accordion('option', 'active');
|
||||
var id = $(this).attr('id');
|
||||
var base = ($location.path().replace(/^\//,'').split('/')[0]);
|
||||
var list = $cookieStore.get('accordions');
|
||||
if (list == null || list == undefined) {
|
||||
list = [];
|
||||
}
|
||||
var found = false;
|
||||
for (var i=0; i < list.length && found == false; i++) {
|
||||
if ( list[i].base == base && list[i].id == id) {
|
||||
found = true;
|
||||
list[i].active = active;
|
||||
}
|
||||
}
|
||||
if (found == false) {
|
||||
list.push({ base: base, id: id, active: active });
|
||||
}
|
||||
$cookieStore.put('accordions',list);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
genID: function() {
|
||||
var id = new Date();
|
||||
return id.getTime();
|
||||
},
|
||||
|
||||
headerField: function(fld, field, options) {
|
||||
@ -556,13 +604,21 @@ angular.module('FormGenerator', ['GeneratorHelpers'])
|
||||
else {
|
||||
|
||||
if ( this.form.collapse && this.form.collapseMode == options.mode) {
|
||||
html += "<div class=\"accordion-group\">\n";
|
||||
/*html += "<div class=\"accordion-group\">\n";
|
||||
html += "<div class=\"accordion-heading\">\n";
|
||||
html += "<a class=\"accordion-toggle\" data-toggle=\"collapse\" data-parent=\"#accordion\" href=\"#collapse1\">";
|
||||
html += "<a id=\"" + this.form.name + "-collapse-0\" class=\"accordion-toggle\" data-toggle=\"collapse\" data-parent=\"#accordion\" href=\"#collapse0\">";
|
||||
html += "<i class=\"icon-angle-down icon-white\"></i>" + this.form.collapseTitle + "</a>\n";
|
||||
html += "</div>\n";
|
||||
html += "<div id=\"collapse1\" class=\"accordion-body collapse\">\n";
|
||||
html += "<div id=\"collapse0\" class=\"accordion-body collapse";
|
||||
html += (this.form.collapseOpen) ? " in" : "";
|
||||
html += "\">\n";
|
||||
html += "<div class=\"accordion-inner\">\n";
|
||||
*/
|
||||
html += "<div id=\"" + this.form.name + "-collapse-0\" ";
|
||||
html += (this.form.collapseOpen) ? "data-open=\"true\" " : "";
|
||||
html += "class=\"jqui-accordion\">\n";
|
||||
html += "<h3>" + this.form.collapseTitle + "<h3>\n";
|
||||
html += "<div>\n";
|
||||
}
|
||||
|
||||
// Start the well
|
||||
@ -655,7 +711,6 @@ angular.module('FormGenerator', ['GeneratorHelpers'])
|
||||
if ( this.form.collapse && this.form.collapseMode == options.mode ) {
|
||||
html += "</div>\n";
|
||||
html += "</div>\n";
|
||||
html += "</div>\n";
|
||||
}
|
||||
}
|
||||
|
||||
@ -708,13 +763,17 @@ angular.module('FormGenerator', ['GeneratorHelpers'])
|
||||
var idx = 1;
|
||||
var form = this.form;
|
||||
|
||||
var html = "<div class=\"accordion-group\">\n";
|
||||
/* var html = "<div class=\"accordion-group\">\n";
|
||||
html += "<div class=\"accordion-heading\">\n";
|
||||
html += "<a class=\"accordion-toggle\" data-toggle=\"collapse\" data-parent=\"#accordion\" href=\"#collapse2\">";
|
||||
html += "<a id=\"" + form.name + "-collapse-2\" class=\"accordion-toggle\" data-toggle=\"collapse\" data-parent=\"#accordion\" href=\"#collapse2\">";
|
||||
html += "<i class=\"icon-angle-up icon-white\"></i>Inventory Content</a>\n";
|
||||
html += "</div>\n";
|
||||
html += "<div id=\"collapse2\" class=\"accordion-body collapse in\">\n";
|
||||
html += "<div class=\"accordion-inner\">\n";
|
||||
*/
|
||||
html = "<div id=\"" + this.form.name + "-collapse-2\" data-open=\"true\" class=\"jqui-accordion\">\n";
|
||||
html += "<h3>Inventory Content<h3>\n";
|
||||
html += "<div>\n";
|
||||
|
||||
for (var itm in form.related) {
|
||||
if (form.related[itm].type == 'tree') {
|
||||
@ -834,7 +893,7 @@ angular.module('FormGenerator', ['GeneratorHelpers'])
|
||||
|
||||
html += "</div>\n";
|
||||
html += "</div>\n";
|
||||
html += "</div>\n";
|
||||
//html += "</div>\n";
|
||||
|
||||
return html;
|
||||
},
|
||||
@ -846,13 +905,14 @@ angular.module('FormGenerator', ['GeneratorHelpers'])
|
||||
//
|
||||
var idx = 1;
|
||||
var form = this.form;
|
||||
var html = "<div class=\"accordion\" id=\"accordion\">\n";
|
||||
html = "<div id=\"" + this.form.name + "-collapse-" + idx + "\" class=\"jqui-accordion\">\n";
|
||||
for (var itm in form.related) {
|
||||
if (form.related[itm].type == 'collection' || form.related[itm].type == 'tree') {
|
||||
if (form.related[itm].type == 'collection') {
|
||||
|
||||
// Start the accordion group
|
||||
html += "<div class=\"accordion-group\">\n";
|
||||
/* html += "<div class=\"accordion-group\">\n";
|
||||
html += "<div class=\"accordion-heading\">\n";
|
||||
html += "<a class=\"accordion-toggle\" data-toggle=\"collapse\" data-parent=\"#accordion\" href=\"#collapse" + idx + "\">";
|
||||
html += "<a id=\"" + form.name + "-collapse-" + idx + "\" class=\"accordion-toggle\" data-toggle=\"collapse\" data-parent=\"#accordion\" href=\"#collapse" + idx + "\">";
|
||||
html += "<i class=\"icon-angle-down icon-white\"></i>" + form.related[itm].title + "</a>\n";
|
||||
html += "</div>\n";
|
||||
html += "<div id=\"collapse" + idx + "\" class=\"accordion-body collapse";
|
||||
@ -861,6 +921,11 @@ angular.module('FormGenerator', ['GeneratorHelpers'])
|
||||
}
|
||||
html += "\">\n";
|
||||
html += "<div class=\"accordion-inner\">\n";
|
||||
*/
|
||||
|
||||
|
||||
html += "<h3>" + form.related[itm].title + "<h3>\n";
|
||||
html += "<div>\n";
|
||||
|
||||
if (form.related[itm].instructions) {
|
||||
html += "<div class=\"alert alert-info alert-block\">\n";
|
||||
@ -964,14 +1029,13 @@ angular.module('FormGenerator', ['GeneratorHelpers'])
|
||||
|
||||
html += PaginateWidget({ set: itm, iterator: form.related[itm].iterator, mini: true });
|
||||
|
||||
// End Accordion Group
|
||||
// End Accordion
|
||||
html += "</div>\n"; // accordion inner
|
||||
html += "</div>\n"; // accordion body
|
||||
html += "</div>\n"; // accordion group
|
||||
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
html += "</div>\n"; // accordion body
|
||||
html += "</div>\n";
|
||||
|
||||
return html;
|
||||
|
@ -4,10 +4,10 @@
|
||||
<meta charset="utf-8">
|
||||
<title>Ansible Commander</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="{{ STATIC_URL }}css/custom-theme/jquery-ui-1.10.3.custom.css" />
|
||||
<link rel="stylesheet" href="{{ STATIC_URL }}css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="{{ STATIC_URL }}css/bootstrap-responsive.min.css" />
|
||||
<link rel="stylesheet" href="{{ STATIC_URL }}css/font-awesome.min.css" />
|
||||
<link rel="stylesheet" href="{{ STATIC_URL }}css/redmond/jquery-ui-1.10.3.custom.min.css" />
|
||||
<link rel="stylesheet" href="{{ STATIC_URL }}css/ansible-ui.css" />
|
||||
<link rel="shortcut icon" href="{{ STATIC_URL }}img/favicon.ico" />
|
||||
<script src="{{ STATIC_URL }}js/config.js"></script>
|
||||
|