mirror of
https://github.com/ansible/awx.git
synced 2026-03-10 05:59:28 -02:30
Merge pull request #1725 from mabashian/1670-clear-all
Activity stream clear-all bug fixes
This commit is contained in:
@@ -9,47 +9,69 @@
|
|||||||
* @name controllers.function:Activity Stream
|
* @name controllers.function:Activity Stream
|
||||||
* @description This controller controls the activity stream.
|
* @description This controller controls the activity stream.
|
||||||
*/
|
*/
|
||||||
export default ['$scope', '$state', 'subTitle', 'Stream', 'GetTargetTitle',
|
export default ['$scope', '$state', 'subTitle', 'GetTargetTitle',
|
||||||
'StreamList', 'Dataset',
|
'StreamList', 'Dataset', '$rootScope', 'ShowDetail', 'BuildDescription',
|
||||||
function activityStreamController($scope, $state, subTitle, Stream,
|
function activityStreamController($scope, $state, subTitle, GetTargetTitle,
|
||||||
GetTargetTitle, list, Dataset) {
|
list, Dataset, $rootScope, ShowDetail, BuildDescription) {
|
||||||
|
|
||||||
init();
|
// search init
|
||||||
initOmitSmartTags();
|
$scope.list = list;
|
||||||
|
$scope[`${list.iterator}_dataset`] = Dataset.data;
|
||||||
|
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
||||||
|
|
||||||
function init() {
|
// subTitle is passed in via a resolve on the route. If there is no subtitle
|
||||||
// search init
|
// generated in the resolve then we go get the targets generic title.
|
||||||
$scope.list = list;
|
|
||||||
$scope[`${list.iterator}_dataset`] = Dataset.data;
|
|
||||||
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
|
||||||
|
|
||||||
// subTitle is passed in via a resolve on the route. If there is no subtitle
|
// Get the streams sub-title based on the target. This scope variable is leveraged
|
||||||
// generated in the resolve then we go get the targets generic title.
|
// when we define the activity stream list. Specifically it is included in the list
|
||||||
|
// title.
|
||||||
|
$scope.streamSubTitle = subTitle ? subTitle : GetTargetTitle($state.params.target);
|
||||||
|
|
||||||
// Get the streams sub-title based on the target. This scope variable is leveraged
|
$rootScope.flashMessage = null;
|
||||||
// when we define the activity stream list. Specifically it is included in the list
|
|
||||||
// title.
|
$scope.refreshStream = function () {
|
||||||
$scope.streamSubTitle = subTitle ? subTitle : GetTargetTitle($state.params.target);
|
$state.go('.', null, {reload: true});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.showDetail = function (id) {
|
||||||
|
ShowDetail({
|
||||||
|
scope: $scope,
|
||||||
|
activity_id: id
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if($scope.activities && $scope.activities.length > 0) {
|
||||||
|
buildUserAndDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.$watch('activities', function(){
|
||||||
|
// Watch for future update to scope.activities (like page change, column sort, search, etc)
|
||||||
|
buildUserAndDescription();
|
||||||
|
});
|
||||||
|
|
||||||
|
function buildUserAndDescription(){
|
||||||
|
$scope.activities.forEach(function(activity, i) {
|
||||||
|
// build activity.user
|
||||||
|
if ($scope.activities[i].summary_fields.actor) {
|
||||||
|
$scope.activities[i].user = "<a href=\"/#/users/" + $scope.activities[i].summary_fields.actor.id + "\">" +
|
||||||
|
$scope.activities[i].summary_fields.actor.username + "</a>";
|
||||||
|
} else {
|
||||||
|
$scope.activities[i].user = 'system';
|
||||||
|
}
|
||||||
|
// build description column / action text
|
||||||
|
BuildDescription($scope.activities[i]);
|
||||||
|
|
||||||
// Open the stream
|
|
||||||
Stream({
|
|
||||||
scope: $scope
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specification of smart-tags omission from the UI is done in the route/state init.
|
const route = _.find($state.$current.path, (step) => {
|
||||||
// A limitation is that this specficiation is static and the key for which to be omitted from
|
return step.params.hasOwnProperty('activity_search');
|
||||||
// the smart-tags must be known at that time.
|
});
|
||||||
// In the case of activity stream, we won't to dynamically ommit the resource for which we are
|
let defaultParams = angular.copy(route.params.activity_search.config.value);
|
||||||
// displaying the activity stream for. i.e. 'project', 'credential', etc.
|
|
||||||
function initOmitSmartTags() {
|
defaultParams.or__object1__in = $state.params.activity_search.or__object1__in ? $state.params.activity_search.or__object1__in : defaultParams.or__object1__in;
|
||||||
let defaults, route = _.find($state.$current.path, (step) => {
|
defaultParams.or__object2__in = $state.params.activity_search.or__object2__in ? $state.params.activity_search.or__object2__in : defaultParams.or__object2__in;
|
||||||
return step.params.hasOwnProperty('activity_search');
|
|
||||||
});
|
$scope[`${list.iterator}_default_params`] = defaultParams;
|
||||||
if (route && $state.params.target !== undefined) {
|
|
||||||
defaults = route.params.activity_search.config.value;
|
|
||||||
defaults[$state.params.target] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
export default
|
|
||||||
function Stream($rootScope, $state, BuildDescription, ShowDetail) {
|
|
||||||
return function (params) {
|
|
||||||
|
|
||||||
var scope = params.scope;
|
|
||||||
|
|
||||||
$rootScope.flashMessage = null;
|
|
||||||
|
|
||||||
// descriptive title describing what AS is showing
|
|
||||||
scope.streamTitle = (params && params.title) ? params.title : null;
|
|
||||||
|
|
||||||
scope.refreshStream = function () {
|
|
||||||
$state.go('.', null, {reload: true});
|
|
||||||
};
|
|
||||||
|
|
||||||
scope.showDetail = function (id) {
|
|
||||||
ShowDetail({
|
|
||||||
scope: scope,
|
|
||||||
activity_id: id
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if(scope.activities && scope.activities.length > 0) {
|
|
||||||
buildUserAndDescription();
|
|
||||||
}
|
|
||||||
|
|
||||||
scope.$watch('activities', function(){
|
|
||||||
// Watch for future update to scope.activities (like page change, column sort, search, etc)
|
|
||||||
buildUserAndDescription();
|
|
||||||
});
|
|
||||||
|
|
||||||
function buildUserAndDescription(){
|
|
||||||
scope.activities.forEach(function(activity, i) {
|
|
||||||
// build activity.user
|
|
||||||
if (scope.activities[i].summary_fields.actor) {
|
|
||||||
scope.activities[i].user = "<a href=\"/#/users/" + scope.activities[i].summary_fields.actor.id + "\">" +
|
|
||||||
scope.activities[i].summary_fields.actor.username + "</a>";
|
|
||||||
} else {
|
|
||||||
scope.activities[i].user = 'system';
|
|
||||||
}
|
|
||||||
// build description column / action text
|
|
||||||
BuildDescription(scope.activities[i]);
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream.$inject = ['$rootScope', '$state', 'BuildDescription', 'ShowDetail'];
|
|
||||||
@@ -11,7 +11,6 @@ import streamDetailModal from './streamDetailModal/main';
|
|||||||
import BuildAnchor from './factories/build-anchor.factory';
|
import BuildAnchor from './factories/build-anchor.factory';
|
||||||
import BuildDescription from './factories/build-description.factory';
|
import BuildDescription from './factories/build-description.factory';
|
||||||
import ShowDetail from './factories/show-detail.factory';
|
import ShowDetail from './factories/show-detail.factory';
|
||||||
import Stream from './factories/stream.factory';
|
|
||||||
import GetTargetTitle from './get-target-title.factory';
|
import GetTargetTitle from './get-target-title.factory';
|
||||||
import ModelToBasePathKey from './model-to-base-path-key.factory';
|
import ModelToBasePathKey from './model-to-base-path-key.factory';
|
||||||
import ActivityDetailForm from './activity-detail.form';
|
import ActivityDetailForm from './activity-detail.form';
|
||||||
@@ -23,7 +22,6 @@ export default angular.module('activityStream', [streamDetailModal.name])
|
|||||||
.factory('BuildAnchor', BuildAnchor)
|
.factory('BuildAnchor', BuildAnchor)
|
||||||
.factory('BuildDescription', BuildDescription)
|
.factory('BuildDescription', BuildDescription)
|
||||||
.factory('ShowDetail', ShowDetail)
|
.factory('ShowDetail', ShowDetail)
|
||||||
.factory('Stream', Stream)
|
|
||||||
.factory('GetTargetTitle', GetTargetTitle)
|
.factory('GetTargetTitle', GetTargetTitle)
|
||||||
.factory('ModelToBasePathKey', ModelToBasePathKey)
|
.factory('ModelToBasePathKey', ModelToBasePathKey)
|
||||||
.factory('ActivityDetailForm', ActivityDetailForm)
|
.factory('ActivityDetailForm', ActivityDetailForm)
|
||||||
|
|||||||
@@ -34,8 +34,10 @@ function QuerysetService ($q, Rest, ProcessErrors, $rootScope, Wait, DjangoSearc
|
|||||||
return defer.promise;
|
return defer.promise;
|
||||||
},
|
},
|
||||||
replaceDefaultFlags (value) {
|
replaceDefaultFlags (value) {
|
||||||
value = value.toString().replace(/__icontains_DEFAULT/g, "__icontains");
|
if (value) {
|
||||||
value = value.toString().replace(/__search_DEFAULT/g, "__search");
|
value = value.toString().replace(/__icontains_DEFAULT/g, "__icontains");
|
||||||
|
value = value.toString().replace(/__search_DEFAULT/g, "__search");
|
||||||
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ function SmartSearchController (
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.clearAllTerms = () => {
|
$scope.clearAllTerms = () => {
|
||||||
const cleared = _.cloneDeep(defaults);
|
const cleared = _(defaults).omit(_.isNull).value();
|
||||||
|
|
||||||
delete cleared.page;
|
delete cleared.page;
|
||||||
|
|
||||||
|
|||||||
@@ -7,24 +7,25 @@ const webpackConfig = require('./webpack.spec');
|
|||||||
|
|
||||||
module.exports = config => {
|
module.exports = config => {
|
||||||
config.set({
|
config.set({
|
||||||
|
basePath: '../..',
|
||||||
autoWatch: true,
|
autoWatch: true,
|
||||||
colors: true,
|
colors: true,
|
||||||
browsers: ['Chrome', 'Firefox'],
|
browsers: ['Chrome', 'Firefox'],
|
||||||
frameworks: ['jasmine'],
|
frameworks: ['jasmine'],
|
||||||
reporters: ['progress', 'junit'],
|
reporters: ['progress', 'junit'],
|
||||||
files:[
|
files:[
|
||||||
'./polyfills.js',
|
'test/spec/polyfills.js',
|
||||||
path.join(SRC_PATH, '**/*.html'),
|
'client/src/vendor.js',
|
||||||
path.join(SRC_PATH, 'vendor.js'),
|
|
||||||
path.join(NODE_MODULES, 'angular-mocks/angular-mocks.js'),
|
path.join(NODE_MODULES, 'angular-mocks/angular-mocks.js'),
|
||||||
path.join(SRC_PATH, 'app.js'),
|
path.join(SRC_PATH, 'app.js'),
|
||||||
'**/*-test.js',
|
'client/src/**/*.html',
|
||||||
|
'test/spec/**/*-test.js',
|
||||||
],
|
],
|
||||||
preprocessors: {
|
preprocessors: {
|
||||||
[path.join(SRC_PATH, '**/*.html')]: 'html2js',
|
'client/src/vendor.js': 'webpack',
|
||||||
[path.join(SRC_PATH, 'vendor.js')]: 'webpack',
|
|
||||||
[path.join(SRC_PATH, 'app.js')]: 'webpack',
|
[path.join(SRC_PATH, 'app.js')]: 'webpack',
|
||||||
'**/*-test.js': 'webpack'
|
'client/src/**/*.html': 'html2js',
|
||||||
|
'test/spec/**/*-test.js': 'webpack'
|
||||||
},
|
},
|
||||||
webpack: webpackConfig,
|
webpack: webpackConfig,
|
||||||
webpackMiddleware: {
|
webpackMiddleware: {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
xdescribe('Directive: Smart Search', () => {
|
describe('Directive: Smart Search', () => {
|
||||||
let $scope,
|
let $scope,
|
||||||
|
$q,
|
||||||
template,
|
template,
|
||||||
element,
|
element,
|
||||||
dom,
|
dom,
|
||||||
@@ -9,119 +10,91 @@ xdescribe('Directive: Smart Search', () => {
|
|||||||
$state = {},
|
$state = {},
|
||||||
$stateParams,
|
$stateParams,
|
||||||
GetBasePath,
|
GetBasePath,
|
||||||
QuerySet;
|
QuerySet,
|
||||||
|
ConfigService = {},
|
||||||
|
i18n,
|
||||||
|
$transitions,
|
||||||
|
translateFilter;
|
||||||
|
|
||||||
beforeEach(angular.mock.module('shared'));
|
beforeEach(angular.mock.module('shared'));
|
||||||
beforeEach(angular.mock.module('SmartSearchModule', ($provide) => {
|
beforeEach(angular.mock.module('SmartSearchModule', ($provide) => {
|
||||||
QuerySet = jasmine.createSpyObj('QuerySet', ['decodeParam']);
|
QuerySet = jasmine.createSpyObj('QuerySet', [
|
||||||
|
'decodeParam',
|
||||||
|
'search',
|
||||||
|
'stripDefaultParams',
|
||||||
|
'createSearchTagsFromQueryset',
|
||||||
|
'initFieldset'
|
||||||
|
]);
|
||||||
QuerySet.decodeParam.and.callFake((key, value) => {
|
QuerySet.decodeParam.and.callFake((key, value) => {
|
||||||
return `${key.split('__').join(':')}:${value}`;
|
return `${key.split('__').join(':')}:${value}`;
|
||||||
});
|
});
|
||||||
GetBasePath = jasmine.createSpy('GetBasePath');
|
QuerySet.stripDefaultParams.and.returnValue([]);
|
||||||
|
QuerySet.createSearchTagsFromQueryset.and.returnValue([]);
|
||||||
|
|
||||||
|
$transitions = jasmine.createSpyObj('$transitions', [
|
||||||
|
'onSuccess'
|
||||||
|
]);
|
||||||
|
$transitions.onSuccess.and.returnValue({});
|
||||||
|
|
||||||
|
ConfigService = jasmine.createSpyObj('ConfigService', [
|
||||||
|
'getConfig'
|
||||||
|
]);
|
||||||
|
|
||||||
|
GetBasePath = jasmine.createSpy('GetBasePath');
|
||||||
|
translateFilter = jasmine.createSpy('translateFilter');
|
||||||
|
i18n = jasmine.createSpy('i18n');
|
||||||
|
$state = jasmine.createSpyObj('$state', ['go']);
|
||||||
|
|
||||||
|
$provide.value('ConfigService', ConfigService);
|
||||||
$provide.value('QuerySet', QuerySet);
|
$provide.value('QuerySet', QuerySet);
|
||||||
$provide.value('GetBasePath', GetBasePath);
|
$provide.value('GetBasePath', GetBasePath);
|
||||||
$provide.value('$state', $state);
|
$provide.value('$state', $state);
|
||||||
|
$provide.value('i18n', { '_': (a) => { return a; } });
|
||||||
|
$provide.value('translateFilter', translateFilter);
|
||||||
}));
|
}));
|
||||||
beforeEach(angular.mock.inject(($templateCache, _$rootScope_, _$compile_) => {
|
beforeEach(angular.mock.inject(($templateCache, _$rootScope_, _$compile_, _$q_) => {
|
||||||
|
$q = _$q_;
|
||||||
|
$compile = _$compile_;
|
||||||
|
$scope = _$rootScope_.$new();
|
||||||
|
|
||||||
|
ConfigService.getConfig.and.returnValue($q.when({}));
|
||||||
|
QuerySet.search.and.returnValue($q.when({}));
|
||||||
|
|
||||||
|
QuerySet.initFieldset.and.callFake(() => {
|
||||||
|
var deferred = $q.defer();
|
||||||
|
deferred.resolve({
|
||||||
|
models: {
|
||||||
|
mock: {
|
||||||
|
base: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
data: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
});
|
||||||
|
|
||||||
// populate $templateCache with directive.templateUrl at test runtime,
|
// populate $templateCache with directive.templateUrl at test runtime,
|
||||||
template = window.__html__['client/src/shared/smart-search/smart-search.partial.html'];
|
template = window.__html__['client/src/shared/smart-search/smart-search.partial.html'];
|
||||||
$templateCache.put('/static/partials/shared/smart-search/smart-search.partial.html', template);
|
$templateCache.put('/static/partials/shared/smart-search/smart-search.partial.html', template);
|
||||||
|
|
||||||
$compile = _$compile_;
|
|
||||||
$scope = _$rootScope_.$new();
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('initializing tags', () => {
|
describe('clear all', () => {
|
||||||
beforeEach(() => {
|
it('should revert search back to non-null defaults and remove page', () => {
|
||||||
QuerySet.initFieldset = function() {
|
|
||||||
return {
|
|
||||||
then: function() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
||||||
// some defaults like page_size and page will always be provided
|
|
||||||
// but should be squashed if initialized with default values
|
|
||||||
it('should not create tags', () => {
|
|
||||||
$state.$current = {
|
$state.$current = {
|
||||||
params: {
|
path: {
|
||||||
mock_search: {
|
mock: {
|
||||||
config: {
|
params: {
|
||||||
value: {
|
mock_search: {
|
||||||
page_size: '20',
|
config: {
|
||||||
order_by: '-finished',
|
value: {
|
||||||
page: '1'
|
page_size: '20',
|
||||||
}
|
order_by: '-finished',
|
||||||
}
|
page: '1',
|
||||||
}
|
some_null_param: null
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
$state.params = {
|
|
||||||
mock_search: {
|
|
||||||
page_size: '20',
|
|
||||||
order_by: '-finished',
|
|
||||||
page: '1'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
dom = angular.element(`<smart-search
|
|
||||||
django-model="mock"
|
|
||||||
search-size="mock"
|
|
||||||
base-path="mock"
|
|
||||||
iterator="mock"
|
|
||||||
collection="dataset"
|
|
||||||
search-tags="searchTags"
|
|
||||||
>
|
|
||||||
</smart-search>`);
|
|
||||||
element = $compile(dom)($scope);
|
|
||||||
$scope.$digest();
|
|
||||||
expect($('.SmartSearch-tagContainer', element).length).toEqual(0);
|
|
||||||
});
|
|
||||||
// set one possible default (order_by) with a custom value, but not another default (page_size)
|
|
||||||
it('should create an order_by tag, but not a page_size tag', () => {
|
|
||||||
$state.$current = {
|
|
||||||
params: {
|
|
||||||
mock_search: {
|
|
||||||
config: {
|
|
||||||
value: {
|
|
||||||
page_size: '20',
|
|
||||||
order_by: '-finished'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
$state.params = {
|
|
||||||
mock_search: {
|
|
||||||
page_size: '20',
|
|
||||||
order_by: 'name'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
dom = angular.element(`<smart-search
|
|
||||||
django-model="mock"
|
|
||||||
search-size="mock"
|
|
||||||
base-path="mock"
|
|
||||||
iterator="mock"
|
|
||||||
collection="dataset"
|
|
||||||
search-tags="searchTags"
|
|
||||||
>
|
|
||||||
</smart-search>`);
|
|
||||||
element = $compile(dom)($scope);
|
|
||||||
$scope.$digest();
|
|
||||||
expect($('.SmartSearch-tagContainer', element).length).toEqual(1);
|
|
||||||
expect($('.SmartSearch-tagContainer .SmartSearch-name', element)[0].innerText).toEqual('order_by:name');
|
|
||||||
});
|
|
||||||
// set many possible defaults and many non-defaults - page_size and page shouldn't generate tags, even when non-default values are set
|
|
||||||
it('should create an order_by tag, name tag, description tag - but not a page_size or page tag', () => {
|
|
||||||
$state.$current = {
|
|
||||||
params: {
|
|
||||||
mock_search: {
|
|
||||||
config: {
|
|
||||||
value: {
|
|
||||||
page_size: '20',
|
|
||||||
order_by: '-finished',
|
|
||||||
page: '1'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,6 +109,9 @@ xdescribe('Directive: Smart Search', () => {
|
|||||||
name_icontains: 'ansible'
|
name_icontains: 'ansible'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
$scope.list = {
|
||||||
|
iterator: 'mock'
|
||||||
|
};
|
||||||
dom = angular.element(`<smart-search
|
dom = angular.element(`<smart-search
|
||||||
django-model="mock"
|
django-model="mock"
|
||||||
search-size="mock"
|
search-size="mock"
|
||||||
@@ -143,21 +119,14 @@ xdescribe('Directive: Smart Search', () => {
|
|||||||
iterator="mock"
|
iterator="mock"
|
||||||
collection="dataset"
|
collection="dataset"
|
||||||
search-tags="searchTags"
|
search-tags="searchTags"
|
||||||
|
list="list"
|
||||||
>
|
>
|
||||||
</smart-search>`);
|
</smart-search>`);
|
||||||
element = $compile(dom)($scope);
|
element = $compile(dom)($scope);
|
||||||
$scope.$digest();
|
$scope.$digest();
|
||||||
expect($('.SmartSearch-tagContainer', element).length).toEqual(3);
|
const scope = element.isolateScope();
|
||||||
|
scope.clearAllTerms();
|
||||||
|
expect(QuerySet.search).toHaveBeenCalledWith('mock', {page_size: '20',order_by: '-finished',});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('removing tags', () => {
|
|
||||||
// assert a default value is still provided after a custom tag is removed
|
|
||||||
xit('should revert to state-defined order_by when order_by tag is removed', () => {});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('accessing model', () => {
|
|
||||||
xit('should retrieve cached model OPTIONS from localStorage', () => {});
|
|
||||||
xit('should call QuerySet service to retrieve unstored model OPTIONS', () => {});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
const path = require('path');
|
|
||||||
|
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const merge = require('webpack-merge');
|
const merge = require('webpack-merge');
|
||||||
const base = require(path.resolve(__dirname, '../..', 'build/webpack.base'));
|
const base = require('../../build/webpack.base');
|
||||||
|
|
||||||
const STATIC_URL = '/static/';
|
const STATIC_URL = '/static/';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user