merge
This commit is contained in:
commit
bde655bd1e
|
@ -1,5 +1,6 @@
|
|||
import './styles/mdl-override.css';
|
||||
import './styles/mdi-override.css';
|
||||
import './styles/zoom-image.css';
|
||||
|
||||
export * from './module';
|
||||
export * from './directives/index';
|
||||
|
|
|
@ -4,3 +4,5 @@ import './dialog';
|
|||
import './validation';
|
||||
import './acl';
|
||||
import './on-error-src';
|
||||
import './zoom-image';
|
||||
import './visible-by';
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
describe('Directive zoomImage', () => {
|
||||
let idContainer = 'zoomImage';
|
||||
let compile;
|
||||
let scope;
|
||||
let srcDefault = 'http://default.img.jpg/';
|
||||
let srcZoom = 'http://zoom.img.jpg/';
|
||||
let findContainer;
|
||||
|
||||
beforeEach(() => {
|
||||
angular.mock.module('client');
|
||||
});
|
||||
|
||||
beforeEach(angular.mock.inject(($compile, $rootScope) => {
|
||||
compile = $compile;
|
||||
scope = $rootScope.$new();
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
findContainer = document.getElementById(idContainer);
|
||||
if (findContainer) {
|
||||
findContainer.parentNode.removeChild(findContainer);
|
||||
findContainer = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
function getCompiledImage(imgHtml) {
|
||||
let element = angular.element(imgHtml);
|
||||
var compiledElement = compile(element)(scope);
|
||||
scope.$digest();
|
||||
return compiledElement;
|
||||
}
|
||||
|
||||
it('should create zoom container when click into image', () => {
|
||||
let image = getCompiledImage(`<img src="${srcDefault}" zoom-image>`);
|
||||
image[0].click();
|
||||
findContainer = document.getElementById(idContainer);
|
||||
|
||||
expect(findContainer).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should detroy zoom container when click outside zoomed image', () => {
|
||||
let image = getCompiledImage(`<img src="${srcDefault}" zoom-image>`);
|
||||
image[0].click();
|
||||
findContainer = document.getElementById(idContainer);
|
||||
|
||||
let findOutsideImage = findContainer.querySelector('.zoomImage-background');
|
||||
findOutsideImage.click();
|
||||
|
||||
findContainer = document.getElementById(idContainer);
|
||||
|
||||
expect(findContainer).toBeNull();
|
||||
});
|
||||
|
||||
it('should create new image, into zoom container, with src as original image src', () => {
|
||||
let image = getCompiledImage(`<img src="${srcDefault}" zoom-image>`);
|
||||
image[0].click();
|
||||
findContainer = document.getElementById(idContainer);
|
||||
let findNewImage = findContainer.querySelector('.zoomImage-background');
|
||||
|
||||
expect(findNewImage.style.backgroundImage).toEqual(`url("${srcDefault}")`);
|
||||
});
|
||||
|
||||
it('should create new image, into zoom container, with src likes zoomImage value', () => {
|
||||
let image = getCompiledImage(`<img src="${srcDefault}" zoom-image="${srcZoom}">`);
|
||||
image[0].click();
|
||||
findContainer = document.getElementById(idContainer);
|
||||
let findNewImage = findContainer.querySelector('.zoomImage-background');
|
||||
|
||||
expect(findNewImage.style.backgroundImage).toEqual(`url("${srcZoom}")`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
import ngModule from '../module';
|
||||
|
||||
function vnVisibleBy(aclService) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
priority: -1,
|
||||
link: function($scope, $element, $attrs) {
|
||||
let acls = $attrs.vnVisibleBy.split(',');
|
||||
if (!aclService.aclPermission(acls)) {
|
||||
$element[0].style.visibility = 'hidden';
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
vnVisibleBy.$inject = ['aclService'];
|
||||
|
||||
ngModule.directive('vnVisibleBy', vnVisibleBy);
|
|
@ -0,0 +1,70 @@
|
|||
import ngModule from '../module';
|
||||
|
||||
export function directive($timeout) {
|
||||
let idContainer = 'zoomImage';
|
||||
let container;
|
||||
let background;
|
||||
|
||||
function createContainers(src) {
|
||||
if (document.getElementById(idContainer)) {
|
||||
destroyContainers();
|
||||
}
|
||||
container = document.createElement('div');
|
||||
container.id = idContainer;
|
||||
|
||||
background = document.createElement('div');
|
||||
background.className = 'zoomImage-background';
|
||||
background.style.backgroundImage = `url(${src})`;
|
||||
container.appendChild(background);
|
||||
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
function addListeners() {
|
||||
background.addEventListener('click', destroyContainers);
|
||||
document.addEventListener('keydown', e => keyDownHandler(e));
|
||||
}
|
||||
|
||||
function removeListeners() {
|
||||
if (container) {
|
||||
background.removeEventListener('click', destroyContainers);
|
||||
document.removeEventListener('keydown', e => keyDownHandler(e));
|
||||
}
|
||||
}
|
||||
|
||||
function keyDownHandler(event) {
|
||||
if (event.keyCode === 27) {
|
||||
destroyContainers();
|
||||
}
|
||||
}
|
||||
|
||||
function destroyContainers() {
|
||||
if (document.getElementById(idContainer)) {
|
||||
removeListeners();
|
||||
container.parentNode.removeChild(container);
|
||||
}
|
||||
|
||||
container = undefined;
|
||||
background = undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
priority: 9999,
|
||||
link: function($scope, $element, $attrs) {
|
||||
$element.on('click', function(event) {
|
||||
let src = $attrs.zoomImage || $attrs.src;
|
||||
if (src) {
|
||||
createContainers(src);
|
||||
addListeners();
|
||||
} else
|
||||
throw new Error('No image source detected');
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
directive.$inject = ['$timeout'];
|
||||
|
||||
ngModule.directive('zoomImage', directive);
|
|
@ -0,0 +1,19 @@
|
|||
img[zoom-image]{
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
div#zoomImage, div#zoomImage .zoomImage-background {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
div#zoomImage .zoomImage-background{
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
cursor: zoom-out;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: auto 98%;
|
||||
}
|
|
@ -86,20 +86,7 @@
|
|||
"description": "Botanical",
|
||||
"icon": "folder"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url" : "/picture",
|
||||
"state": "item.card.picture",
|
||||
"component": "vn-item-picture",
|
||||
"params": {
|
||||
"item": "$ctrl.item"
|
||||
},
|
||||
"menu": {
|
||||
"description": "Picture",
|
||||
"icon": "folder"
|
||||
}
|
||||
},
|
||||
{
|
||||
},{
|
||||
"url" : "/barcode",
|
||||
"state": "item.card.itemBarcode",
|
||||
"component": "vn-item-barcode",
|
||||
|
|
|
@ -16,7 +16,6 @@ class ItemCard {
|
|||
{relation: "producer"},
|
||||
{relation: "intrastat"},
|
||||
{relation: "expence"},
|
||||
{relation: "taxClass"},
|
||||
{relation: "itemTag", scope: {order: "priority ASC", include: {relation: "tag"}}}
|
||||
]
|
||||
};
|
||||
|
|
|
@ -21,8 +21,8 @@ describe('Item', () => {
|
|||
describe('$onInit()', () => {
|
||||
it('should request to patch the propagation of tax status', () => {
|
||||
controller.client = {id: 123, isEqualizated: false};
|
||||
$httpBackend.whenGET('/item/api/Items/undefined?filter={"include":[{"relation":"itemType"},{"relation":"origin"},{"relation":"ink"},{"relation":"producer"},{"relation":"intrastat"},{"relation":"expence"},{"relation":"taxClass"},{"relation":"itemTag","scope":{"order":"priority ASC","include":{"relation":"tag"}}}]}').respond({data: 'item'});
|
||||
$httpBackend.expectGET('/item/api/Items/undefined?filter={"include":[{"relation":"itemType"},{"relation":"origin"},{"relation":"ink"},{"relation":"producer"},{"relation":"intrastat"},{"relation":"expence"},{"relation":"taxClass"},{"relation":"itemTag","scope":{"order":"priority ASC","include":{"relation":"tag"}}}]}');
|
||||
$httpBackend.whenGET('/item/api/Items/undefined?filter={"include":[{"relation":"itemType"},{"relation":"origin"},{"relation":"ink"},{"relation":"producer"},{"relation":"intrastat"},{"relation":"expence"},{"relation":"itemTag","scope":{"order":"priority ASC","include":{"relation":"tag"}}}]}').respond({data: 'item'});
|
||||
$httpBackend.expectGET('/item/api/Items/undefined?filter={"include":[{"relation":"itemType"},{"relation":"origin"},{"relation":"ink"},{"relation":"producer"},{"relation":"intrastat"},{"relation":"expence"},{"relation":"itemTag","scope":{"order":"priority ASC","include":{"relation":"tag"}}}]}');
|
||||
controller.$onInit();
|
||||
$httpBackend.flush();
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
<vn-title>New item</vn-title>
|
||||
<vn-horizontal>
|
||||
<vn-textfield vn-one label="Name" field="$ctrl.item.name" vn-focus></vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete vn-one
|
||||
url="/item/api/ItemTypes"
|
||||
label="Type"
|
||||
|
@ -20,8 +22,6 @@
|
|||
field="$ctrl.item.typeFk"
|
||||
>
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete vn-one
|
||||
url="/item/api/Intrastats"
|
||||
label="Intrastat"
|
||||
|
@ -33,7 +33,6 @@
|
|||
>
|
||||
<tpl-item>{{$parent.$parent.item.description}}</tpl-item>
|
||||
</vn-autocomplete>
|
||||
<vn-textfield vn-one label="Relevancy" field="$ctrl.item.relevancy" type="number"></vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete vn-one
|
||||
|
|
|
@ -60,19 +60,6 @@
|
|||
initial-data="$ctrl.item.expence"
|
||||
></vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete vn-one
|
||||
url="/item/api/TaxClasses"
|
||||
label="TaxClass"
|
||||
show-field="description"
|
||||
value-field="id"
|
||||
order="description ASC"
|
||||
filter-search="{where: {description: {regexp: 'search'}} }"
|
||||
field="$ctrl.item.taxClassFk"
|
||||
initial-data="$ctrl.item.taxClass"
|
||||
><tpl-item>{{$parent.$parent.item.description}}</tpl-item></vn-autocomplete>
|
||||
<vn-one></vn-one>
|
||||
</vn-horizontal>
|
||||
</vn-vertical>
|
||||
</vn-card>
|
||||
<vn-button-bar>
|
||||
|
|
|
@ -3,8 +3,14 @@
|
|||
<vn-auto class="descriptor-header pointer" ui-sref="item.index">
|
||||
<img ng-src="/static/images/icon_item.png"/>
|
||||
</vn-auto>
|
||||
<vn-auto pad-medium text-center>
|
||||
<img ng-src="http://verdnatura.es/vn-image-data/catalog/200x200/{{$ctrl.item.image}}" on-error-src/>
|
||||
<vn-auto style="position: relative" pad-medium text-center>
|
||||
<img
|
||||
ng-src="http://verdnatura.es/vn-image-data/catalog/200x200/{{::$ctrl.item.image}}"
|
||||
zoom-image="http://verdnatura.es/vn-image-data/catalog/900x900/{{::$ctrl.item.image}}" on-error-src/>
|
||||
<a href="https://www.verdnatura.es/#!form=admin/items&filter={{::$ctrl.item.id}}" target="_blank"><vn-float-button icon="edit"
|
||||
style="position: absolute; bottom: 1em; right: 1em;"
|
||||
vn-visible-by="administrative"></vn-float-button>
|
||||
</a>
|
||||
</vn-auto>
|
||||
<vn-auto pad-medium>
|
||||
<div><span translate>Id</span>: <b>{{$ctrl.item.id}}</b></div>
|
||||
|
|
|
@ -11,6 +11,5 @@ import './tags/item-tags';
|
|||
import './history/item-history';
|
||||
import './niche/item-niche';
|
||||
import './botanical/item-botanical';
|
||||
import './picture/item-picture';
|
||||
import './barcode/item-barcode';
|
||||
|
||||
|
|
|
@ -8,8 +8,7 @@
|
|||
on-search="$ctrl.search(index)"
|
||||
advanced="true"
|
||||
popover="vn-item-filter-panel"
|
||||
ignore-keys = "['page', 'size', 'search']"
|
||||
>
|
||||
ignore-keys = "['page', 'size', 'search']">
|
||||
</vn-searchbar>
|
||||
</vn-horizontal>
|
||||
</vn-card>
|
||||
|
|
|
@ -13,7 +13,6 @@ Item history: Historial del artículo
|
|||
Item tags: Tags del artículo
|
||||
Niche: Nicho
|
||||
Picture: Foto
|
||||
Item pictures: Fotos del artículo
|
||||
Barcode: Código barras
|
||||
Item barcode: Código de barras del artículo
|
||||
Changed by: Cambiado por
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
<vn-card>
|
||||
<vn-vertical pad-large>
|
||||
<vn-title>Item pictures</vn-title>
|
||||
</vn-vertical>
|
||||
</vn-card>
|
|
@ -1,5 +0,0 @@
|
|||
import ngModule from '../module';
|
||||
|
||||
ngModule.component('vnItemPicture', {
|
||||
template: require('./item-picture.html')
|
||||
});
|
|
@ -1,8 +1,8 @@
|
|||
salix: []
|
||||
auth: []
|
||||
core: []
|
||||
client: []
|
||||
production: []
|
||||
route: []
|
||||
locator: []
|
||||
core: []
|
||||
item: []
|
||||
locator: []
|
||||
production: []
|
||||
salix: []
|
||||
route: []
|
||||
|
|
|
@ -1,14 +1,5 @@
|
|||
<div style="position: fixed; top: 0; right: 0; padding: .8em 1.5em; z-index: 10;">
|
||||
<vn-icon icon="apps" id="apps" translate-attr="{title: 'Applications'}"></vn-icon>
|
||||
<ul for="langs" class="mdl-menu mdl-js-menu mdl-menu--bottom-right" pad-small>
|
||||
<li
|
||||
ng-repeat="lang in ::$ctrl.langs"
|
||||
class="mdl-menu__item"
|
||||
ng-click="$ctrl.onChangeLangClick(lang)">
|
||||
<span>{{::lang}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<vn-icon icon="language" id="langs" translate-attr="{title: 'Change language'}"></vn-icon>
|
||||
<ul for="apps" class="mdl-menu mdl-js-menu mdl-menu--bottom-right" pad-small>
|
||||
<li ng-repeat="mod in ::$ctrl.modules" class="mdl-menu__item" ui-sref="{{::mod.route.state}}">
|
||||
<vn-icon ng-if="::mod.icon && !mod.icon.startsWith('/')" icon="{{::mod.icon}}"></vn-icon>
|
||||
|
@ -16,6 +7,16 @@
|
|||
<span translate="{{::mod.name}}"></span>
|
||||
</li>
|
||||
</ul>
|
||||
<vn-icon id="lang-button" icon="language" translate-attr="{title: 'Change language'}"></vn-icon>
|
||||
<ul id="langs" for="lang-button" class="mdl-menu mdl-js-menu mdl-menu--bottom-right" pad-small>
|
||||
<li
|
||||
ng-repeat="lang in ::$ctrl.langs"
|
||||
name="{{::lang}}"
|
||||
class="mdl-menu__item"
|
||||
ng-click="$ctrl.onChangeLangClick(lang)">
|
||||
<span>{{::lang}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<vn-icon icon="exit_to_app" translate-attr="{title: 'Logout'}" ng-click="$ctrl.onLogoutClick()"></vn-icon>
|
||||
<!--
|
||||
TODO: Keep it commented until they are functional
|
||||
|
|
|
@ -128,17 +128,17 @@ Nightmare.action('waitForTextInElement', function(selector, name, done) {
|
|||
});
|
||||
|
||||
Nightmare.action('changeLanguageToEnglish', function(done) {
|
||||
this.wait(selectors.globalItems.languageButton)
|
||||
this.wait('#lang-button')
|
||||
.evaluate(selector => {
|
||||
return document.querySelector(selector).title;
|
||||
}, selectors.globalItems.languageButton)
|
||||
.then(result => {
|
||||
if (result === 'Cambiar idioma') {
|
||||
this.click(selectors.globalItems.languageButton)
|
||||
.then(done);
|
||||
}
|
||||
if (result === 'Change language') {
|
||||
}, '#lang-button')
|
||||
.then(title => {
|
||||
if (title === 'Change language') {
|
||||
this.then(done);
|
||||
} else {
|
||||
this.click('#lang-button')
|
||||
.click('#langs > li[name="en"]')
|
||||
.then(done);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,7 +10,6 @@ export default {
|
|||
globalItems: {
|
||||
topBar: `${components.vnTopbar}`,
|
||||
logOutButton: `${components.vnIcon}[icon="exit_to_app"]`,
|
||||
languageButton: `${components.vnIcon}[icon="language"]`,
|
||||
snackbarIsActive: '.mdl-snackbar--active > .mdl-snackbar__text',
|
||||
applicationsMenuButton: `${components.vnIcon}[icon="apps"]`,
|
||||
applicationsMenuVisible: `${components.vnMainMenu} .is-visible > div`,
|
||||
|
@ -54,7 +53,7 @@ export default {
|
|||
socialNameInput: `${components.vnTextfield}[name="socialName"]`,
|
||||
fiscalIdInput: `${components.vnTextfield}[name="fi"]`,
|
||||
equalizationTaxCheckboxLabel: `${components.vnCheck}[label='Is equalizated'] > label > input`,
|
||||
acceptPropagationButton: `body > vn-app > vn-vertical > vn-vertical > vn-client-card > vn-main-block > vn-horizontal > vn-one > vn-vertical > vn-client-fiscal-data > vn-dialog > div > form > div.button-bar > tpl-buttons > button:nth-child(2)`,
|
||||
acceptPropagationButton: `vn-client-fiscal-data vn-confirm button[response="ACCEPT"]`,
|
||||
addressInput: `${components.vnTextfield}[name="street"]`,
|
||||
cityInput: `${components.vnTextfield}[name="city"]`,
|
||||
postcodeInput: `${components.vnTextfield}[name="postcode"]`,
|
||||
|
@ -77,7 +76,7 @@ export default {
|
|||
payMethodOptionOne: `${components.vnAutocomplete}[field="$ctrl.client.payMethodFk"] > vn-vertical > vn-drop-down > vn-vertical:not(.ng-hide) > vn-auto:nth-child(2) > ul > li:nth-child(2)`,
|
||||
IBANInput: `${components.vnTextfield}[name="iban"]`,
|
||||
dueDayInput: `${components.vnTextfield}[name="dueDay"]`,
|
||||
cancelNotificationButton: 'body > vn-app > vn-vertical > vn-vertical > vn-client-card > vn-main-block > vn-horizontal > vn-one > vn-vertical > vn-client-billing-data > vn-dialog > div > form > div.button-bar > tpl-buttons > button:nth-child(1)',
|
||||
cancelNotificationButton: 'vn-client-billing-data > vn-dialog tpl-buttons > button:nth-child(1)',
|
||||
receivedCoreVNHCheckbox: `${components.vnCheck}[label='Received core VNH'] > label > input`,
|
||||
receivedCoreVNLCheckbox: `${components.vnCheck}[label='Received core VNL'] > label > input`,
|
||||
receivedB2BVNLCheckbox: `${components.vnCheck}[label='Received B2B VNL'] > label > input`,
|
||||
|
|
156
gulpfile.js
156
gulpfile.js
|
@ -9,28 +9,30 @@ const log = require('fancy-log');
|
|||
|
||||
// Configuration
|
||||
|
||||
const isWindows = /^win/.test(process.platform);
|
||||
let isWindows = /^win/.test(process.platform);
|
||||
|
||||
if (argv.NODE_ENV)
|
||||
process.env.NODE_ENV = argv.NODE_ENV;
|
||||
|
||||
const env = process.env.NODE_ENV ? process.env.NODE_ENV : 'development';
|
||||
let env = process.env.NODE_ENV ? process.env.NODE_ENV : 'development';
|
||||
|
||||
const langs = ['es', 'en'];
|
||||
const srcDir = './client';
|
||||
const servicesDir = './services';
|
||||
const nginxDir = `${servicesDir}/nginx`;
|
||||
const buildDir = `${nginxDir}/static`;
|
||||
let langs = ['es', 'en'];
|
||||
let srcDir = './client';
|
||||
let servicesDir = './services';
|
||||
|
||||
let wpConfig = require('./webpack.config.yml');
|
||||
let buildDir = wpConfig.buildDir;
|
||||
let devServerPort = wpConfig.devServerPort;
|
||||
|
||||
let nginxDir = `${servicesDir}/nginx`;
|
||||
let proxyConf = require(`${nginxDir}/config.yml`);
|
||||
let proxyEnvFile = `${nginxDir}/config.${env}.yml`;
|
||||
|
||||
if (fs.existsSync(proxyEnvFile))
|
||||
Object.assign(proxyConf, require(proxyEnvFile));
|
||||
|
||||
const defaultService = proxyConf.main;
|
||||
const defaultPort = proxyConf.defaultPort;
|
||||
const devServerPort = proxyConf.devServerPort;
|
||||
let defaultService = proxyConf.main;
|
||||
let defaultPort = proxyConf.defaultPort;
|
||||
|
||||
// Development
|
||||
|
||||
|
@ -42,30 +44,51 @@ gulp.task('client', ['build-clean'], () => {
|
|||
return gulp.start('watch', 'routes', 'locales', 'webpack-dev-server');
|
||||
});
|
||||
|
||||
/**
|
||||
* Starts all backend services, including the nginx proxy and the database.
|
||||
*/
|
||||
gulp.task('services', async () => {
|
||||
await runSequenceP('docker-start', 'services-only', 'nginx');
|
||||
});
|
||||
|
||||
/**
|
||||
* Starts backend services.
|
||||
*/
|
||||
gulp.task('services-only', async () => {
|
||||
const services = await getServices();
|
||||
for (let service of services)
|
||||
require(service.index).start(service.port);
|
||||
});
|
||||
|
||||
/**
|
||||
* Runs the e2e tests, restoring the fixtures first.
|
||||
*/
|
||||
gulp.task('e2e', ['docker-rebuild'], async () => {
|
||||
await runSequenceP('e2e-only');
|
||||
});
|
||||
|
||||
/**
|
||||
* Runs the e2e tests.
|
||||
*/
|
||||
gulp.task('e2e-only', () => {
|
||||
const jasmine = require('gulp-jasmine');
|
||||
return gulp.src('./e2e_tests.js')
|
||||
.pipe(jasmine({reporter: 'none'}));
|
||||
});
|
||||
|
||||
/**
|
||||
* Cleans all generated project files.
|
||||
*/
|
||||
gulp.task('clean', ['build-clean', 'nginx-clean']);
|
||||
|
||||
/**
|
||||
* Alias for the 'install' task.
|
||||
*/
|
||||
gulp.task('i', ['install']);
|
||||
|
||||
/**
|
||||
* Installs node dependencies in all project directories.
|
||||
*/
|
||||
gulp.task('install', () => {
|
||||
const install = require('gulp-install');
|
||||
const print = require('gulp-print');
|
||||
|
@ -123,6 +146,9 @@ gulp.task('docker-compose', async () => {
|
|||
await fs.writeFile('./docker-compose.yml', ymlString);
|
||||
});
|
||||
|
||||
/**
|
||||
* Cleans all files generated by the 'build' task.
|
||||
*/
|
||||
gulp.task('build-clean', () => {
|
||||
const del = require('del');
|
||||
const files = [
|
||||
|
@ -139,10 +165,16 @@ gulp.task('build-clean', () => {
|
|||
let nginxConf = 'temp/nginx.conf';
|
||||
let nginxTemp = `${nginxDir}/temp`;
|
||||
|
||||
/**
|
||||
* Starts the nginx process, if it is started, restarts it.
|
||||
*/
|
||||
gulp.task('nginx', async () => {
|
||||
await runSequenceP('nginx-stop', 'nginx-start');
|
||||
});
|
||||
|
||||
/**
|
||||
* Starts the nginx process, generating it's configuration file first.
|
||||
*/
|
||||
gulp.task('nginx-start', ['nginx-conf'], async () => {
|
||||
let nginxBin = await nginxGetBin();
|
||||
|
||||
|
@ -153,6 +185,9 @@ gulp.task('nginx-start', ['nginx-conf'], async () => {
|
|||
await execP(`${nginxBin} -c "${nginxConf}" -p "${nginxDir}"`);
|
||||
});
|
||||
|
||||
/**
|
||||
* Stops the nginx process.
|
||||
*/
|
||||
gulp.task('nginx-stop', async () => {
|
||||
try {
|
||||
let nginxBin = await nginxGetBin();
|
||||
|
@ -161,6 +196,11 @@ gulp.task('nginx-stop', async () => {
|
|||
} catch (e) {}
|
||||
});
|
||||
|
||||
/**
|
||||
* Generates the nginx configuration file. If NODE_ENV is defined and the
|
||||
* 'nginx.[environment].mst' file exists, it is used as a template, otherwise,
|
||||
* the 'nginx.mst' template file is used.
|
||||
*/
|
||||
gulp.task('nginx-conf', ['nginx-stop'], async () => {
|
||||
const mustache = require('mustache');
|
||||
|
||||
|
@ -187,6 +227,9 @@ gulp.task('nginx-conf', ['nginx-stop'], async () => {
|
|||
await fs.writeFile(`${nginxTemp}/nginx.conf`, nginxConf);
|
||||
});
|
||||
|
||||
/**
|
||||
* Cleans all files generated by nginx.
|
||||
*/
|
||||
gulp.task('nginx-clean', ['nginx-stop'], () => {
|
||||
const del = require('del');
|
||||
return del([`${nginxTemp}/*`], {force: true});
|
||||
|
@ -280,6 +323,11 @@ gulp.task('webpack-dev-server', function() {
|
|||
|
||||
let localeFiles = `${srcDir}/**/locale/*.yml`;
|
||||
|
||||
/**
|
||||
* Mixes all locale files into one JSON file per module and language. It looks
|
||||
* recursively in all project directories for locale folders with per language
|
||||
* yaml translation files.
|
||||
*/
|
||||
gulp.task('locales', function() {
|
||||
const extend = require('gulp-extend');
|
||||
const yaml = require('gulp-yaml');
|
||||
|
@ -323,6 +371,10 @@ gulp.task('watch', function() {
|
|||
|
||||
// Docker
|
||||
|
||||
/**
|
||||
* Rebuilds the docker and it's image, if these already exist, destroys and
|
||||
* rebuilds them.
|
||||
*/
|
||||
gulp.task('docker-rebuild', async () => {
|
||||
try {
|
||||
await execP('docker rm -f dblocal');
|
||||
|
@ -334,16 +386,22 @@ gulp.task('docker-rebuild', async () => {
|
|||
await runSequenceP('docker-run');
|
||||
});
|
||||
|
||||
/**
|
||||
* Does the minium effort to start the docker, if it doesn't exists calls
|
||||
* the 'docker-run' task, if it is started does nothing. Keep in mind that when
|
||||
* you do not rebuild the docker you may be using an outdated version of it.
|
||||
* See the 'docker-rebuild' task for more info.
|
||||
*/
|
||||
gulp.task('docker-start', async () => {
|
||||
let result;
|
||||
|
||||
let state;
|
||||
try {
|
||||
result = await execP('docker container inspect -f "{{json .State}}" dblocal');
|
||||
let result = await execP('docker container inspect -f "{{json .State}}" dblocal');
|
||||
state = JSON.parse(result.stdout);
|
||||
} catch (err) {
|
||||
return await runSequenceP('docker-run');
|
||||
}
|
||||
|
||||
switch (JSON.parse(result.stdout).Status) {
|
||||
switch (state.Status) {
|
||||
case 'running':
|
||||
return;
|
||||
case 'exited':
|
||||
|
@ -353,6 +411,9 @@ gulp.task('docker-start', async () => {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Runs the docker, if the container or it's image doesn't exists builds them.
|
||||
*/
|
||||
gulp.task('docker-run', async () => {
|
||||
try {
|
||||
await execP('docker image inspect -f "{{json .Id}}" dblocal');
|
||||
|
@ -364,29 +425,58 @@ gulp.task('docker-run', async () => {
|
|||
await runSequenceP('docker-wait');
|
||||
});
|
||||
|
||||
/**
|
||||
* Waits until MySQL docker is started and ready to serve connections.
|
||||
*/
|
||||
gulp.task('docker-wait', callback => {
|
||||
let maxInterval = 30 * 60000;
|
||||
let interval = 1000;
|
||||
let timer = 0;
|
||||
const mysql = require('mysql2');
|
||||
|
||||
let interval = 1;
|
||||
let elapsedTime = 0;
|
||||
let maxInterval = 30 * 60;
|
||||
|
||||
log('Waiting for MySQL init process...');
|
||||
let waitForLocaldb = setInterval(() => {
|
||||
if (timer < maxInterval) {
|
||||
timer += interval;
|
||||
exec('docker logs --tail 1 dblocal', (err, stdout, stderr) => {
|
||||
if (stderr.includes('starting as process 1') || err) {
|
||||
clearInterval(waitForLocaldb);
|
||||
callback(err);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
clearInterval(waitForLocaldb);
|
||||
callback(new Error(`MySQL not initialized whithin ${maxInterval / 1000} secs`));
|
||||
checker();
|
||||
|
||||
async function checker() {
|
||||
elapsedTime += interval;
|
||||
let state;
|
||||
|
||||
try {
|
||||
let result = await execP('docker container inspect -f "{{json .State}}" dblocal');
|
||||
state = JSON.parse(result.stdout);
|
||||
} catch (err) {
|
||||
return callback(new Error(err.message));
|
||||
}
|
||||
}, interval);
|
||||
|
||||
if (state.Status === 'exited')
|
||||
return callback(new Error('Docker exited, please see the docker logs for more info'));
|
||||
|
||||
let conn = mysql.createConnection({
|
||||
host: 'localhost',
|
||||
user: 'root'
|
||||
});
|
||||
conn.on('error', () => {});
|
||||
conn.connect(err => {
|
||||
conn.destroy();
|
||||
if (!err) return callback();
|
||||
|
||||
if (elapsedTime >= maxInterval)
|
||||
callback(new Error(`MySQL not initialized whithin ${elapsedTime} secs`));
|
||||
else
|
||||
setTimeout(checker, interval * 1000);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
* Promisified version of exec().
|
||||
*
|
||||
* @param {String} command The exec command
|
||||
* @return {Promise} The promise
|
||||
*/
|
||||
function execP(command) {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(command, (err, stdout, stderr) => {
|
||||
|
@ -401,6 +491,12 @@ function execP(command) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Promisified version of runSequence().
|
||||
*
|
||||
* @param {String} args The list of gulp task names
|
||||
* @return {Promise} The promise
|
||||
*/
|
||||
function runSequenceP() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let args = Array.prototype.slice.call(arguments);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -25,6 +25,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "^1.6.6",
|
||||
"assets-webpack-plugin": "^3.5.1",
|
||||
"babel": "^6.23.0",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-loader": "^7.1.2",
|
||||
|
@ -63,7 +64,7 @@
|
|||
"merge-stream": "^1.0.1",
|
||||
"minimist": "^1.2.0",
|
||||
"mustache": "^2.3.0",
|
||||
"mysql": "^2.15.0",
|
||||
"mysql2": "^1.5.2",
|
||||
"nightmare": "^2.10.0",
|
||||
"node-sass": "^4.7.2",
|
||||
"nodemon": "^1.12.1",
|
||||
|
@ -74,6 +75,7 @@
|
|||
"style-loader": "^0.20.1",
|
||||
"webpack": "^3.10.0",
|
||||
"webpack-dev-server": "^2.11.1",
|
||||
"webpack-merge": "^4.1.1",
|
||||
"yaml-loader": "^0.5.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
@ -2,6 +2,7 @@ FROM node:8.9.4
|
|||
|
||||
COPY auth /app
|
||||
COPY loopback /loopback
|
||||
COPY nginx/static/webpack-assets.json /loopback/client/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
|
|
@ -6,14 +6,8 @@
|
|||
</head>
|
||||
<body ng-app="vnAuth">
|
||||
<vn-login></vn-login>
|
||||
<script type="text/javascript"
|
||||
src="/static/bundle.manifest.js">
|
||||
</script>
|
||||
<script type="text/javascript"
|
||||
src="/static/bundle.vendor.js">
|
||||
</script>
|
||||
<script type="text/javascript"
|
||||
src="/static/bundle.auth.js">
|
||||
</script>
|
||||
<% for (let jsFile of assets('auth', ['vendor'])) { %>
|
||||
<script type="text/javascript" src="<%= jsFile %>"></script>
|
||||
<% } %>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -6,7 +6,7 @@ module.exports = function(app) {
|
|||
let applications = app.get('applications');
|
||||
|
||||
app.get('/', function(req, res) {
|
||||
res.render('index.ejs');
|
||||
app.renderIndex(res);
|
||||
});
|
||||
|
||||
app.post('/login', function(req, res) {
|
||||
|
|
|
@ -17,6 +17,14 @@
|
|||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"acls": [
|
||||
{
|
||||
"accessType": "READ",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -11,4 +11,7 @@ RUN chmod -R 755 .
|
|||
|
||||
CMD ["mysqld"]
|
||||
|
||||
#HEALTHCHECK --interval=5s --timeout=10s --retries=200 \
|
||||
# CMD mysqladmin ping -h 127.0.0.1 -u root || exit 1
|
||||
|
||||
EXPOSE 3306
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
let mysql = require('mysql');
|
||||
let mysql = require('mysql2');
|
||||
|
||||
let connection = mysql.createConnection({
|
||||
multipleStatements: true,
|
||||
|
|
|
@ -75,11 +75,6 @@
|
|||
"model": "Expence",
|
||||
"foreignKey": "expenceFk"
|
||||
},
|
||||
"taxClass": {
|
||||
"type": "belongsTo",
|
||||
"model": "TaxClass",
|
||||
"foreignKey": "taxClassFk"
|
||||
},
|
||||
"itemTag": {
|
||||
"type": "hasMany",
|
||||
"model": "ItemTag",
|
||||
|
|
|
@ -4,5 +4,6 @@
|
|||
"Unable to mark the equivalence surcharge": "Unable to mark the equivalence surcharge",
|
||||
"The default consignee can not be unchecked": "The default consignee can not be unchecked",
|
||||
"Unable to default a disabled consignee": "Unable to default a disabled consignee",
|
||||
"El método de pago seleccionado requiere que se especifique el IBAN": "El método de pago seleccionado requiere que se especifique el IBAN"
|
||||
"El método de pago seleccionado requiere que se especifique el IBAN": "El método de pago seleccionado requiere que se especifique el IBAN",
|
||||
"Ya existe un usuario con ese nombre": "Ya existe un usuario con ese nombre"
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -7,6 +7,7 @@
|
|||
"dependencies": {
|
||||
"compression": "^1.0.3",
|
||||
"cors": "^2.5.2",
|
||||
"fs-extra": "^5.0.0",
|
||||
"helmet": "^1.3.0",
|
||||
"i18n": "^0.8.3",
|
||||
"loopback": "^3.14.0",
|
||||
|
@ -16,6 +17,7 @@
|
|||
"loopback-connector-remote": "^3.1.1",
|
||||
"loopback-context": "^3.3.0",
|
||||
"md5": "^2.2.1",
|
||||
"require-yaml": "0.0.1",
|
||||
"serve-favicon": "^2.0.1",
|
||||
"strong-error-handler": "^2.1.0"
|
||||
},
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
|
||||
let loopback = require('loopback');
|
||||
let boot = require('loopback-boot');
|
||||
let path = require('path');
|
||||
let fs = require('fs');
|
||||
let fs = require('fs-extra');
|
||||
let i18n = require('i18n');
|
||||
let path = require('path');
|
||||
|
||||
module.exports = {
|
||||
loopback: loopback,
|
||||
|
@ -26,26 +25,57 @@ function vnBoot(app, rootDir, rootModule) {
|
|||
|
||||
// View
|
||||
|
||||
let wpAssets;
|
||||
let viewDir = `${rootDir}/../client`;
|
||||
|
||||
if (fs.existsSync(viewDir)) {
|
||||
try {
|
||||
wpAssets = require('../client/webpack-assets.json');
|
||||
} catch (e) {}
|
||||
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', viewDir);
|
||||
app.use(loopback.static(path.resolve(rootDir, '../client')));
|
||||
}
|
||||
|
||||
app.renderIndex = async res => {
|
||||
res.render(`${viewDir}/index.ejs`, {
|
||||
assets: assets
|
||||
});
|
||||
|
||||
function assets(main, deps) {
|
||||
let jsFiles;
|
||||
|
||||
if (wpAssets) {
|
||||
jsFiles = [wpAssets.manifest.js];
|
||||
|
||||
for (let dep of deps)
|
||||
jsFiles.push(wpAssets[dep].js);
|
||||
|
||||
jsFiles.push(wpAssets[main].js);
|
||||
} else {
|
||||
let publicPath = '/static';
|
||||
jsFiles = [`${publicPath}/manifest.js`];
|
||||
|
||||
for (let dep of deps)
|
||||
jsFiles.push(`${publicPath}/${dep}.js`);
|
||||
|
||||
jsFiles.push(`${publicPath}/${main}.js`);
|
||||
}
|
||||
|
||||
return jsFiles;
|
||||
}
|
||||
};
|
||||
|
||||
// Initialization
|
||||
|
||||
app.start = function(port) {
|
||||
app.start = port => {
|
||||
function onListen() {
|
||||
app.emit('started');
|
||||
|
||||
if (require.main === rootModule) {
|
||||
let packageJson = require(`${rootDir}/../package.json`);
|
||||
let appName = packageJson.name;
|
||||
let baseUrl = app.get('url').replace(/\/$/, '');
|
||||
console.log(`Web server ${appName} listening at: %s`, baseUrl);
|
||||
}
|
||||
let packageJson = require(`${rootDir}/../package.json`);
|
||||
let appName = packageJson.name;
|
||||
let baseUrl = app.get('url').replace(/\/$/, '');
|
||||
console.log(`Web server ${appName} listening at: %s`, baseUrl);
|
||||
}
|
||||
|
||||
let args = port ? [port, onListen] : [onListen];
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
host: localhost
|
||||
port: 5000
|
||||
main: salix
|
||||
defaultPort: 3000
|
||||
devServerPort: 8081
|
||||
defaultPort: 3000
|
|
@ -2,6 +2,7 @@ FROM node:8.9.4
|
|||
|
||||
COPY salix /app
|
||||
COPY loopback /loopback
|
||||
COPY nginx/static/webpack-assets.json /loopback/client/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
|
|
@ -10,15 +10,8 @@
|
|||
<script type="text/javascript"
|
||||
src="/static/routes.js">
|
||||
</script>
|
||||
<script type="text/javascript"
|
||||
src="/static/bundle.manifest.js">
|
||||
</script>
|
||||
<script type="text/javascript"
|
||||
src="/static/bundle.vendor.js">
|
||||
</script>
|
||||
<script type="text/javascript"
|
||||
src="/static/bundle.salix.js">
|
||||
</script>
|
||||
<% for (let jsFile of assets('salix', ['vendor'])) { %>
|
||||
<script type="text/javascript" src="<%= jsFile %>"></script>
|
||||
<% } %>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ module.exports = function(app) {
|
|||
let token = req.cookies.vnToken;
|
||||
validateToken(token, function(isValid) {
|
||||
if (isValid)
|
||||
res.render('index.ejs');
|
||||
app.renderIndex(res);
|
||||
else
|
||||
redirectToAuth(res, req.get('origin'));
|
||||
});
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
require('require-yaml');
|
||||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const merge = require('webpack-merge');
|
||||
const AssetsWebpackPlugin = require('assets-webpack-plugin');
|
||||
const wpConfig = require('./webpack.config.yml');
|
||||
|
||||
var webpack = require('webpack');
|
||||
var path = require('path');
|
||||
let env = process.env.NODE_ENV ? process.env.NODE_ENV : 'development';
|
||||
let devMode = env === 'development';
|
||||
let outputPath = path.join(__dirname, wpConfig.buildDir);
|
||||
|
||||
var devMode = process.env.NODE_ENV !== 'production';
|
||||
|
||||
var config = {
|
||||
entry: {
|
||||
'bundle.salix': ['salix'],
|
||||
'bundle.auth': ['auth'],
|
||||
'bundle.vendor': ['vendor']
|
||||
},
|
||||
let baseConfig = {
|
||||
entry: wpConfig.entry,
|
||||
output: {
|
||||
path: path.join(__dirname, './services/nginx/static'),
|
||||
filename: '[name].js',
|
||||
publicPath: '/static/',
|
||||
chunkFilename: 'chunk.[name].[chunkhash].js'
|
||||
path: outputPath,
|
||||
publicPath: `${wpConfig.publicPath}/`
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
|
@ -52,20 +51,41 @@ var config = {
|
|||
},
|
||||
plugins: [
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
names: ['bundle.vendor', 'bundle.manifest']
|
||||
names: ['vendor', 'manifest']
|
||||
})
|
||||
],
|
||||
devtool: 'source-map'
|
||||
};
|
||||
|
||||
if (!devMode) {
|
||||
// FIXME: https://github.com/webpack-contrib/uglifyjs-webpack-plugin/issues/132
|
||||
// config.plugins.push(
|
||||
// new webpack.optimize.UglifyJsPlugin({
|
||||
// minimize: true,
|
||||
// compress: {warnings: false}
|
||||
// })
|
||||
// );
|
||||
}
|
||||
let prodConfig = {
|
||||
output: {
|
||||
filename: '[name].[chunkhash].js',
|
||||
chunkFilename: 'chunk.[id].[chunkhash].js'
|
||||
},
|
||||
plugins: [
|
||||
// FIXME: https://github.com/webpack-contrib/uglifyjs-webpack-plugin/issues/132
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
minimize: true,
|
||||
compress: {warnings: false}
|
||||
}),
|
||||
new AssetsWebpackPlugin({
|
||||
path: outputPath
|
||||
}),
|
||||
new webpack.HashedModuleIdsPlugin()
|
||||
],
|
||||
devtool: 'source-map'
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
let devConfig = {
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
chunkFilename: 'chunk.[id].js'
|
||||
},
|
||||
plugins: [
|
||||
new webpack.NamedModulesPlugin()
|
||||
],
|
||||
devtool: 'eval'
|
||||
};
|
||||
|
||||
let mrgConfig = devMode ? devConfig : prodConfig;
|
||||
module.exports = merge(baseConfig, mrgConfig);
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
buildDir: services/nginx/static
|
||||
devServerPort: 3500
|
||||
publicPath: /static
|
||||
entry: {
|
||||
salix: [salix],
|
||||
auth: [auth],
|
||||
vendor: [vendor]
|
||||
}
|
Loading…
Reference in New Issue