Merge branch 'master' into PHRAS-3278-patch-for-4-1-3

This commit is contained in:
Nicolas Maillat
2021-01-15 15:19:11 +01:00
committed by GitHub
66 changed files with 3298 additions and 2612 deletions

51
.env
View File

@@ -1,4 +1,5 @@
PHRASEANET_PROJECT_NAME=Phraseanet
PHRASEANET_SERVER_NAME=localhost
# Registry from where you pull Docker images
PHRASEANET_DOCKER_REGISTRY=local
# Tag of the Docker images
@@ -37,6 +38,11 @@ SESSION_CACHE_LIMITER=off
# PHP LOG LEVEL : Possible Values: alert, error, warning, notice, debug
PHP_LOG_LEVEL=warning
# --------------- MYSQL CONFIGURATION --------------------
# Mysql max allowed packet
MYSQL_MAX_ALLOWED_PACKET=16M
# --------------- PHRASEANET CONFIGURATION --------------------
# These variables are used in the configuration.yml .
@@ -54,11 +60,6 @@ PHRASEANET_DB_PASSWORD=root
INSTALL_DB_TEMPLATE=DublinCore
INSTALL_APPBOX=ab_master
INSTALL_DATABOX=db_databox1
PHRASEANET_SERVER_NAME=localhost
# Mysql max allowed packet
MYSQL_MAX_ALLOWED_PACKET=16M
# binaries execution timeouts
PHRASEANET_FFMPEG_TIMEOUT=7200
PHRASEANET_FFPROBE_TIMEOUT=120
@@ -79,15 +80,32 @@ PHRASEANET_API_AUTH_TOKEN_HEADER_ONLY=false
# Phraseanet mail configuration
PHRASEANET_EMITTER_EMAIL=phraseanet@example.com
PHRASEANET_MAIL_OBJECT_PREFIX=
PHRASEANET_SMTP_ENABLED=false
PHRASEANET_SMTP_HOST=
PHRASEANET_SMTP_PORT=
PHRASEANET_MAIL_OBJECT_PREFIX="phraseanet"
PHRASEANET_SMTP_ENABLED=true
PHRASEANET_SMTP_HOST=mailhog
PHRASEANET_SMTP_PORT=1025
PHRASEANET_SMTP_AUTH_ENABLED=false
PHRASEANET_SMTP_SECURE_MODE=tls
PHRASEANET_SMTP_SECURE_MODE=null
PHRASEANET_SMTP_USER=
PHRASEANET_SMTP_PASSWORD=
# Phraseanet Workers setting
PHRASEANET_EXPLODE_WORKER=1
PHRASEANET_WORKER_assetsIngest=1
PHRASEANET_WORKER_createRecord=2
PHRASEANET_WORKER_deleteRecord=2
PHRASEANET_WORKER_exportMail=2
PHRASEANET_WORKER_exposeUpload=2
PHRASEANET_WORKER_ftp=1
PHRASEANET_WORKER_mainQueue=3
PHRASEANET_WORKER_populateIndex=1
PHRASEANET_WORKER_pullAssets=1
PHRASEANET_WORKER_recordEdit=2
PHRASEANET_WORKER_subdefCreation=1
PHRASEANET_WORKER_subtitle=1
PHRASEANET_WORKER_validationReminder=1
PHRASEANET_WORKER_webhook=1
PHRASEANET_WORKER_writeMetadatas=1
# Locale setting
@@ -100,6 +118,18 @@ LC_CTYPE=C.UTF-8
LC_TIME=C.UTF-8
LC_NAME=C.UTF-8
# --- EXTERNAL BINARIES SETTING ----
# ImageMagick default policy override
IMAGEMAGICK_POLICY_VERSION=6
IMAGEMAGICK_POLICY_WIDTH=16KP
IMAGEMAGICK_POLICY_HEIGHT=16KP
IMAGEMAGICK_POLICY_MAP=512MiB
IMAGEMAGICK_POLICY_MEMORY=256MiB
IMAGEMAGICK_POLICY_AREA=128MB
IMAGEMAGICK_POLICY_DISK=1GiB
IMAGEMAGICK_POLICY_TEMPORARY_PATH=/tmp
# --- DEV purpose ---
# PhpMyAdmin port
@@ -140,3 +170,4 @@ SSH_AUTH_SOCK=/dev/null
# Plugin support
PHRASEANET_PLUGINS=
PHRASEANET_SSH_PRIVATE_KEY=

View File

@@ -215,7 +215,7 @@ CMD ["php-fpm", "-F"]
FROM phraseanet-fpm as phraseanet-worker
ENTRYPOINT ["docker/phraseanet/worker/entrypoint.sh"]
CMD ["bin/console", "worker:execute"]
CMD ["/bin/bash", "bin/run-worker.sh"]
#########################################################################
# phraseanet-nginx

View File

@@ -10230,15 +10230,15 @@ var workzone = function workzone(services) {
selection: new _selectable2.default(services, (0, _jquery2.default)('#baskets'), { selector: '.CHIM' }),
refresh: refreshBaskets,
addElementToBasket: function addElementToBasket(options) {
var sbas_id = options.sbas_id,
record_id = options.record_id,
var dbId = options.dbId,
recordId = options.recordId,
event = options.event,
singleSelection = options.singleSelection;
singleSelection = !!singleSelection || false;
if ((0, _jquery2.default)('#baskets .SSTT.active').length === 1) {
return dropOnBask(event, (0, _jquery2.default)('#IMGT_' + sbas_id + '_' + record_id), (0, _jquery2.default)('#baskets .SSTT.active'), singleSelection);
return dropOnBask(event, (0, _jquery2.default)('#IMGT_' + dbId + '_' + recordId), (0, _jquery2.default)('#baskets .SSTT.active'), singleSelection);
} else {
humane.info(localeService.t('noActiveBasket'));
}
@@ -10397,9 +10397,9 @@ var workzone = function workzone(services) {
});
function WorkZoneElementRemover(el, confirm) {
var context = el.data('context');
var context = (0, _jquery2.default)(el).data('context');
if (confirm !== true && (0, _jquery2.default)(el).hasClass('groupings') && warnOnRemove) {
if (confirm !== true && ((0, _jquery2.default)(el).hasClass('groupings') || (0, _jquery2.default)(el).closest('.chim-wrapper').hasClass('chim-feedback-item')) && warnOnRemove) {
var buttons = {};
buttons[localeService.t('valider')] = function () {
@@ -10411,9 +10411,18 @@ var workzone = function workzone(services) {
(0, _jquery2.default)('#DIALOG-baskets').dialog('close').remove();
};
var texte = '<p>' + localeService.t('confirmRemoveReg') + '</p><div><input type="checkbox" onchange="prodApp.appEvents.emit(\'workzone.doRemoveWarning\', this);"/>' + localeService.t('hideMessage') + '</div>';
var texte = '';
var title = '';
if ((0, _jquery2.default)(el).hasClass('groupings')) {
texte = '<p>' + localeService.t('confirmRemoveReg') + '</p><div><input type="checkbox" onchange="prodApp.appEvents.emit(\'workzone.doRemoveWarning\', this);"/>' + localeService.t('hideMessage') + '</div>';
title = localeService.t('removeTitle');
} else {
texte = '<p>' + localeService.t('confirmRemoveFeedBack') + '</p>';
title = localeService.t('removeRecordFeedbackTitle');
}
(0, _jquery2.default)('body').append('<div id="DIALOG-baskets"></div>');
(0, _jquery2.default)('#DIALOG-baskets').attr('title', localeService.t('removeTitle')).empty().append(texte).dialog({
(0, _jquery2.default)('#DIALOG-baskets').attr('title', title).empty().append(texte).dialog({
autoOpen: false,
closeOnEscape: true,
resizable: false,
@@ -10519,7 +10528,9 @@ var workzone = function workzone(services) {
uiactive.addClass('ui-state-focus active');
// reset selection when opening a basket type
workzoneOptions.selection.empty();
appEvents.emit('broadcast.workzoneResultSelection', { asArray: [], serialized: "" });
getContent(uiactive);
},
@@ -10884,6 +10895,10 @@ var workzone = function workzone(services) {
left: -20
},
start: function start(event, ui) {
if (!(0, _jquery2.default)(this).hasClass('selected')) {
return false;
}
var baskets = (0, _jquery2.default)('#baskets');
baskets.append('<div class="top-scroller"></div>' + '<div class="bottom-scroller"></div>');
(0, _jquery2.default)('.bottom-scroller', baskets).bind('mousemove', function () {
@@ -10898,9 +10913,9 @@ var workzone = function workzone(services) {
},
drag: function drag(event, ui) {
if (appCommons.utilsModule.is_ctrl_key(event) || (0, _jquery2.default)(this).closest('.content').hasClass('grouping')) {
(0, _jquery2.default)('#dragDropCursor div').empty().append('+ ' + workzoneOptions.selection.length());
(0, _jquery2.default)('#dragDropCursor div').empty().append(workzoneOptions.selection.length() + ', ' + localeService.t('movedRecord'));
} else {
(0, _jquery2.default)('#dragDropCursor div').empty().append(workzoneOptions.selection.length());
(0, _jquery2.default)('#dragDropCursor div').empty().append('+ ' + workzoneOptions.selection.length());
}
}
});
@@ -11042,7 +11057,7 @@ var workzone = function workzone(services) {
switch (action) {
case 'CHU2CHU':
if (!appCommons.utilsModule.is_ctrl_key(event)) act = 'MOV';
if (appCommons.utilsModule.is_ctrl_key(event)) act = 'MOV';
break;
case 'IMGT2REG':
case 'CHU2REG':
@@ -11125,6 +11140,8 @@ var workzone = function workzone(services) {
var publicationId = destKey.attr('data-publication-id');
var exposeName = (0, _jquery2.default)('#expose_list').val();
var assetsContainer = destKey.find('.expose_item_deployed');
if (publicationId !== undefined) {
assetsContainer.empty().addClass('loading');
_jquery2.default.ajax({
@@ -11146,6 +11163,7 @@ var workzone = function workzone(services) {
});
}
}
}
function fix() {
_jquery2.default.ajax({
@@ -63419,7 +63437,7 @@ var addToBasket = function addToBasket(services) {
var dbId = $el.data('db-id');
var recordId = $el.data('record-id');
appEvents.emit('workzone.doAddToBasket', {
dbId: dbId, recordId: recordId, event: event.currentTarget
dbId: dbId, recordId: recordId, event: event.currentTarget, singleSelection: true
});
});
};
@@ -65231,13 +65249,12 @@ var previewRecordService = function previewRecordService(services) {
event.preventDefault();
closePreview();
}).on('dblclick', '.open-preview-action', function (event) {
var $el = (0, _jquery2.default)(event.currentTarget);
// env, pos, contId, reload
var reload = $el.data('reload') === true ? true : false;
_openPreview(event.currentTarget, $el.data('kind'), $el.data('position'), $el.data('id'), $el.data('kind'));
var $element = (0, _jquery2.default)(event.currentTarget);
openPreview($element);
}).on('click', '.to-open-preview-action', function (event) {
event.preventDefault();
(0, _jquery2.default)('.open-preview-action').trigger("dblclick");
var $element = (0, _jquery2.default)(event.currentTarget);
openPreview($element);
});
$previewContainer.on('click', '.preview-navigate-action', function (event) {
event.preventDefault();
@@ -65481,7 +65498,18 @@ var previewRecordService = function previewRecordService(services) {
(0, _jquery2.default)('#PREVIEWBOX img.record.zoomable').draggable();
}
(0, _jquery2.default)('#SPANTITLE').empty().append(data.title);
var basketIcon = '';
if (data.containerType !== null) {
if (data.containerType === 'feedback') {
basketIcon = "<img src='/assets/common/images/icons/basket_validation.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
} else if (data.containerType === 'push') {
basketIcon = "<img src='/assets/common/images/icons/basket_push.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
} else {
basketIcon = "<img src='/assets/common/images/icons/basket.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
}
}
(0, _jquery2.default)('#SPANTITLE').empty().append(basketIcon + data.title);
(0, _jquery2.default)('#PREVIEWTITLE_COLLLOGO').empty().append(data.collection_logo);
(0, _jquery2.default)('#PREVIEWTITLE_COLLNAME').empty().append(data.databox_name + ' / ' + data.collection_name);
@@ -65544,6 +65572,12 @@ var previewRecordService = function previewRecordService(services) {
(0, _jquery2.default)("iframe", $sel).css('width', NW).css('height', NH);
}
function openPreview($element) {
var reload = $element.data('reload') === true ? true : false;
// env, pos, contId, reload
_openPreview(event.currentTarget, $element.data('kind'), $element.data('position'), $element.data('id'), reload);
}
function closePreview() {
options.open = false;
if (activeThumbnailFrame !== false) {

View File

@@ -10230,15 +10230,15 @@ var workzone = function workzone(services) {
selection: new _selectable2.default(services, (0, _jquery2.default)('#baskets'), { selector: '.CHIM' }),
refresh: refreshBaskets,
addElementToBasket: function addElementToBasket(options) {
var sbas_id = options.sbas_id,
record_id = options.record_id,
var dbId = options.dbId,
recordId = options.recordId,
event = options.event,
singleSelection = options.singleSelection;
singleSelection = !!singleSelection || false;
if ((0, _jquery2.default)('#baskets .SSTT.active').length === 1) {
return dropOnBask(event, (0, _jquery2.default)('#IMGT_' + sbas_id + '_' + record_id), (0, _jquery2.default)('#baskets .SSTT.active'), singleSelection);
return dropOnBask(event, (0, _jquery2.default)('#IMGT_' + dbId + '_' + recordId), (0, _jquery2.default)('#baskets .SSTT.active'), singleSelection);
} else {
humane.info(localeService.t('noActiveBasket'));
}
@@ -10397,9 +10397,9 @@ var workzone = function workzone(services) {
});
function WorkZoneElementRemover(el, confirm) {
var context = el.data('context');
var context = (0, _jquery2.default)(el).data('context');
if (confirm !== true && (0, _jquery2.default)(el).hasClass('groupings') && warnOnRemove) {
if (confirm !== true && ((0, _jquery2.default)(el).hasClass('groupings') || (0, _jquery2.default)(el).closest('.chim-wrapper').hasClass('chim-feedback-item')) && warnOnRemove) {
var buttons = {};
buttons[localeService.t('valider')] = function () {
@@ -10411,9 +10411,18 @@ var workzone = function workzone(services) {
(0, _jquery2.default)('#DIALOG-baskets').dialog('close').remove();
};
var texte = '<p>' + localeService.t('confirmRemoveReg') + '</p><div><input type="checkbox" onchange="prodApp.appEvents.emit(\'workzone.doRemoveWarning\', this);"/>' + localeService.t('hideMessage') + '</div>';
var texte = '';
var title = '';
if ((0, _jquery2.default)(el).hasClass('groupings')) {
texte = '<p>' + localeService.t('confirmRemoveReg') + '</p><div><input type="checkbox" onchange="prodApp.appEvents.emit(\'workzone.doRemoveWarning\', this);"/>' + localeService.t('hideMessage') + '</div>';
title = localeService.t('removeTitle');
} else {
texte = '<p>' + localeService.t('confirmRemoveFeedBack') + '</p>';
title = localeService.t('removeRecordFeedbackTitle');
}
(0, _jquery2.default)('body').append('<div id="DIALOG-baskets"></div>');
(0, _jquery2.default)('#DIALOG-baskets').attr('title', localeService.t('removeTitle')).empty().append(texte).dialog({
(0, _jquery2.default)('#DIALOG-baskets').attr('title', title).empty().append(texte).dialog({
autoOpen: false,
closeOnEscape: true,
resizable: false,
@@ -10519,7 +10528,9 @@ var workzone = function workzone(services) {
uiactive.addClass('ui-state-focus active');
// reset selection when opening a basket type
workzoneOptions.selection.empty();
appEvents.emit('broadcast.workzoneResultSelection', { asArray: [], serialized: "" });
getContent(uiactive);
},
@@ -10884,6 +10895,10 @@ var workzone = function workzone(services) {
left: -20
},
start: function start(event, ui) {
if (!(0, _jquery2.default)(this).hasClass('selected')) {
return false;
}
var baskets = (0, _jquery2.default)('#baskets');
baskets.append('<div class="top-scroller"></div>' + '<div class="bottom-scroller"></div>');
(0, _jquery2.default)('.bottom-scroller', baskets).bind('mousemove', function () {
@@ -10898,9 +10913,9 @@ var workzone = function workzone(services) {
},
drag: function drag(event, ui) {
if (appCommons.utilsModule.is_ctrl_key(event) || (0, _jquery2.default)(this).closest('.content').hasClass('grouping')) {
(0, _jquery2.default)('#dragDropCursor div').empty().append('+ ' + workzoneOptions.selection.length());
(0, _jquery2.default)('#dragDropCursor div').empty().append(workzoneOptions.selection.length() + ', ' + localeService.t('movedRecord'));
} else {
(0, _jquery2.default)('#dragDropCursor div').empty().append(workzoneOptions.selection.length());
(0, _jquery2.default)('#dragDropCursor div').empty().append('+ ' + workzoneOptions.selection.length());
}
}
});
@@ -11042,7 +11057,7 @@ var workzone = function workzone(services) {
switch (action) {
case 'CHU2CHU':
if (!appCommons.utilsModule.is_ctrl_key(event)) act = 'MOV';
if (appCommons.utilsModule.is_ctrl_key(event)) act = 'MOV';
break;
case 'IMGT2REG':
case 'CHU2REG':
@@ -11125,6 +11140,8 @@ var workzone = function workzone(services) {
var publicationId = destKey.attr('data-publication-id');
var exposeName = (0, _jquery2.default)('#expose_list').val();
var assetsContainer = destKey.find('.expose_item_deployed');
if (publicationId !== undefined) {
assetsContainer.empty().addClass('loading');
_jquery2.default.ajax({
@@ -11146,6 +11163,7 @@ var workzone = function workzone(services) {
});
}
}
}
function fix() {
_jquery2.default.ajax({
@@ -63419,7 +63437,7 @@ var addToBasket = function addToBasket(services) {
var dbId = $el.data('db-id');
var recordId = $el.data('record-id');
appEvents.emit('workzone.doAddToBasket', {
dbId: dbId, recordId: recordId, event: event.currentTarget
dbId: dbId, recordId: recordId, event: event.currentTarget, singleSelection: true
});
});
};
@@ -65231,13 +65249,12 @@ var previewRecordService = function previewRecordService(services) {
event.preventDefault();
closePreview();
}).on('dblclick', '.open-preview-action', function (event) {
var $el = (0, _jquery2.default)(event.currentTarget);
// env, pos, contId, reload
var reload = $el.data('reload') === true ? true : false;
_openPreview(event.currentTarget, $el.data('kind'), $el.data('position'), $el.data('id'), $el.data('kind'));
var $element = (0, _jquery2.default)(event.currentTarget);
openPreview($element);
}).on('click', '.to-open-preview-action', function (event) {
event.preventDefault();
(0, _jquery2.default)('.open-preview-action').trigger("dblclick");
var $element = (0, _jquery2.default)(event.currentTarget);
openPreview($element);
});
$previewContainer.on('click', '.preview-navigate-action', function (event) {
event.preventDefault();
@@ -65481,7 +65498,18 @@ var previewRecordService = function previewRecordService(services) {
(0, _jquery2.default)('#PREVIEWBOX img.record.zoomable').draggable();
}
(0, _jquery2.default)('#SPANTITLE').empty().append(data.title);
var basketIcon = '';
if (data.containerType !== null) {
if (data.containerType === 'feedback') {
basketIcon = "<img src='/assets/common/images/icons/basket_validation.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
} else if (data.containerType === 'push') {
basketIcon = "<img src='/assets/common/images/icons/basket_push.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
} else {
basketIcon = "<img src='/assets/common/images/icons/basket.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
}
}
(0, _jquery2.default)('#SPANTITLE').empty().append(basketIcon + data.title);
(0, _jquery2.default)('#PREVIEWTITLE_COLLLOGO').empty().append(data.collection_logo);
(0, _jquery2.default)('#PREVIEWTITLE_COLLNAME').empty().append(data.databox_name + ' / ' + data.collection_name);
@@ -65544,6 +65572,12 @@ var previewRecordService = function previewRecordService(services) {
(0, _jquery2.default)("iframe", $sel).css('width', NW).css('height', NH);
}
function openPreview($element) {
var reload = $element.data('reload') === true ? true : false;
// env, pos, contId, reload
_openPreview(event.currentTarget, $element.data('kind'), $element.data('position'), $element.data('id'), reload);
}
function closePreview() {
options.open = false;
if (activeThumbnailFrame !== false) {

View File

@@ -11,7 +11,7 @@ const addToBasket = (services) => {
let dbId = $el.data('db-id');
let recordId = $el.data('record-id');
appEvents.emit('workzone.doAddToBasket', {
dbId, recordId, event: event.currentTarget
dbId, recordId, event: event.currentTarget, singleSelection: true
});
});
};

View File

@@ -81,20 +81,14 @@ const previewRecordService = services => {
closePreview();
})
.on('dblclick', '.open-preview-action', event => {
let $el = $(event.currentTarget);
// env, pos, contId, reload
let reload = $el.data('reload') === true ? true : false;
_openPreview(
event.currentTarget,
$el.data('kind'),
$el.data('position'),
$el.data('id'),
$el.data('kind')
);
let $element = $(event.currentTarget);
openPreview($element);
})
.on('click', '.to-open-preview-action', event => {
event.preventDefault();
$( '.open-preview-action' ).trigger( "dblclick" );
let $element = $(event.currentTarget);
openPreview($element);
})
;
$previewContainer
@@ -387,7 +381,18 @@ const previewRecordService = services => {
$('#PREVIEWBOX img.record.zoomable').draggable();
}
$('#SPANTITLE').empty().append(data.title);
let basketIcon = '';
if (data.containerType !== null ) {
if (data.containerType === 'feedback') {
basketIcon = "<img src='/assets/common/images/icons/basket_validation.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
} else if (data.containerType === 'push') {
basketIcon = "<img src='/assets/common/images/icons/basket_push.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
} else {
basketIcon = "<img src='/assets/common/images/icons/basket.png' title='' width='24' class='btn-image' style='width:24px;height: 24px;'/>";
}
}
$('#SPANTITLE').empty().append(basketIcon + data.title);
$('#PREVIEWTITLE_COLLLOGO')
.empty()
.append(data.collection_logo);
@@ -466,6 +471,18 @@ const previewRecordService = services => {
}
function openPreview($element) {
let reload = $element.data('reload') === true ? true : false;
// env, pos, contId, reload
_openPreview(
event.currentTarget,
$element.data('kind'),
$element.data('position'),
$element.data('id'),
reload
);
}
function closePreview() {
options.open = false;
if (activeThumbnailFrame !== false) {

View File

@@ -214,11 +214,11 @@ const workzone = (services) => {
selection: new Selectable(services, $('#baskets'), {selector: '.CHIM'}),
refresh: refreshBaskets,
addElementToBasket: function (options) {
let {sbas_id, record_id, event, singleSelection} = options;
let {dbId, recordId, event, singleSelection} = options;
singleSelection = !!singleSelection || false;
if ($('#baskets .SSTT.active').length === 1) {
return dropOnBask(event, $('#IMGT_' + sbas_id + '_' + record_id), $('#baskets .SSTT.active'), singleSelection);
return dropOnBask(event, $('#IMGT_' + dbId + '_' + recordId), $('#baskets .SSTT.active'), singleSelection);
} else {
humane.info(localeService.t('noActiveBasket'));
}
@@ -373,9 +373,9 @@ const workzone = (services) => {
});
function WorkZoneElementRemover(el, confirm) {
var context = el.data('context');
var context = $(el).data('context');
if (confirm !== true && $(el).hasClass('groupings') && warnOnRemove) {
if (confirm !== true && ($(el).hasClass('groupings') || $(el).closest('.chim-wrapper').hasClass('chim-feedback-item')) && warnOnRemove) {
var buttons = {};
buttons[localeService.t('valider')] = function () {
@@ -387,9 +387,18 @@ const workzone = (services) => {
$('#DIALOG-baskets').dialog('close').remove();
};
var texte = '<p>' + localeService.t('confirmRemoveReg') + '</p><div><input type="checkbox" onchange="prodApp.appEvents.emit(\'workzone.doRemoveWarning\', this);"/>' + localeService.t('hideMessage') + '</div>';
var texte = '';
var title = '';
if ($(el).hasClass('groupings')) {
texte = '<p>' + localeService.t('confirmRemoveReg') + '</p><div><input type="checkbox" onchange="prodApp.appEvents.emit(\'workzone.doRemoveWarning\', this);"/>' + localeService.t('hideMessage') + '</div>';
title = localeService.t('removeTitle');
} else {
texte = '<p>' + localeService.t('confirmRemoveFeedBack') + '</p>';
title = localeService.t('removeRecordFeedbackTitle');
}
$('body').append('<div id="DIALOG-baskets"></div>');
$('#DIALOG-baskets').attr('title', localeService.t('removeTitle'))
$('#DIALOG-baskets').attr('title', title)
.empty()
.append(texte)
.dialog({
@@ -500,7 +509,9 @@ const workzone = (services) => {
uiactive.addClass('ui-state-focus active');
// reset selection when opening a basket type
workzoneOptions.selection.empty();
appEvents.emit('broadcast.workzoneResultSelection', {asArray:[], serialized:""});
getContent(uiactive);
@@ -874,6 +885,10 @@ const workzone = (services) => {
left: -20
},
start: function (event, ui) {
if (!$(this).hasClass('selected')) {
return false;
}
var baskets = $('#baskets');
baskets.append('<div class="top-scroller"></div>' +
'<div class="bottom-scroller"></div>');
@@ -891,11 +906,10 @@ const workzone = (services) => {
},
drag: function (event, ui) {
if (appCommons.utilsModule.is_ctrl_key(event) || $(this).closest('.content').hasClass('grouping')) {
$('#dragDropCursor div').empty().append('+ ' + workzoneOptions.selection.length());
$('#dragDropCursor div').empty().append(workzoneOptions.selection.length() + ', ' + localeService.t('movedRecord'));
} else {
$('#dragDropCursor div').empty().append(workzoneOptions.selection.length());
$('#dragDropCursor div').empty().append('+ ' + workzoneOptions.selection.length());
}
}
});
window.workzoneOptions = workzoneOptions;
@@ -1043,7 +1057,7 @@ const workzone = (services) => {
switch (action) {
case 'CHU2CHU' :
if (!appCommons.utilsModule.is_ctrl_key(event)) act = 'MOV';
if (appCommons.utilsModule.is_ctrl_key(event)) act = 'MOV';
break;
case 'IMGT2REG':
case 'CHU2REG' :
@@ -1129,6 +1143,8 @@ const workzone = (services) => {
let publicationId = destKey.attr('data-publication-id');
let exposeName = $('#expose_list').val();
let assetsContainer = destKey.find('.expose_item_deployed');
if (publicationId !== undefined) {
assetsContainer.empty().addClass('loading');
$.ajax({
@@ -1151,6 +1167,7 @@ const workzone = (services) => {
});
}
}
}
function fix() {
$.ajax({

View File

@@ -331,6 +331,15 @@ workers:
user: guest
password: guest
vhost: /
queues:
writeMetadatas: # this Q is "delayable" in case of record is locked
ttl_retry: 1500 # overwrite 1000 ms default delay
ttl_delayed: 10000 # overwrite 5000 ms default delay
subdefCreation: # this Q is "delayable" in case of record is locked
ttl_delayed: 10000 # overwrite 5000 ms default delay
pullAssets:
ttl_retry: 5000
max_retry : 5
externalservice:
ginger:

View File

@@ -119,6 +119,7 @@ services:
- redis
- rabbitmq
- elasticsearch
- phraseanet
environment:
- PHRASEANET_PROJECT_NAME
- PHRASEANET_TRUSTED_PROXIES
@@ -135,7 +136,30 @@ services:
- LC_CTYPE=C.UTF-8
- LC_TIME=C.UTF-8
- LC_NAME=C.UTF-8
- PHRASEANET_EXPLODE_WORKER
- PHRASEANET_WORKER_assetsIngest
- PHRASEANET_WORKER_createRecord
- PHRASEANET_WORKER_deleteRecord
- PHRASEANET_WORKER_exportMail
- PHRASEANET_WORKER_exposeUpload
- PHRASEANET_WORKER_ftp
- PHRASEANET_WORKER_mainQueue
- PHRASEANET_WORKER_populateIndex
- PHRASEANET_WORKER_pullAssets
- PHRASEANET_WORKER_recordEdit
- PHRASEANET_WORKER_subdefCreation
- PHRASEANET_WORKER_subtitle
- PHRASEANET_WORKER_validationReminder
- PHRASEANET_WORKER_webhook
- PHRASEANET_WORKER_writeMetadatas
- IMAGEMAGICK_POLICY_VERSION
- IMAGEMAGICK_POLICY_WIDTH
- IMAGEMAGICK_POLICY_HEIGHT
- IMAGEMAGICK_POLICY_MAP
- IMAGEMAGICK_POLICY_MEMORY
- IMAGEMAGICK_POLICY_AREA
- IMAGEMAGICK_POLICY_DISK
- IMAGEMAGICK_POLICY_TEMPORARY_PATH
volumes:
- ${PHRASEANET_CONFIG_DIR}:/var/alchemy/Phraseanet/config:rw
- ${PHRASEANET_LOGS_DIR}:/var/alchemy/Phraseanet/logs:rw

View File

@@ -38,7 +38,7 @@ if [ -f "$FILE" ]; then
if [[ $PHRASEANET_SMTP_ENABLED && $PHRASEANET_SMTP_ENABLED = true ]]; then
bin/setup system:config set registry.email.smtp-enabled $PHRASEANET_SMTP_ENABLED
bin/setup system:config set registry.email.smtp-auth-enabled $PHRASEANET_SMTP_AUTH_ENABLED
bin/setup system:config set registry.email.smtp-auth-secure-mode $PHRASEANET_SMTP_SECURE_MODE
bin/setup system:config set registry.email.smtp-secure-mode $PHRASEANET_SMTP_SECURE_MODE
bin/setup system:config set registry.email.smtp-host $PHRASEANET_SMTP_HOST
bin/setup system:config set registry.email.smtp-port $PHRASEANET_SMTP_PORT
bin/setup system:config set registry.email.smtp-user $PHRASEANET_SMTP_USER
@@ -49,7 +49,7 @@ if [ -f "$FILE" ]; then
if [[ -n ${PHRASEANET_ADMIN_ACCOUNT_ID} && $PHRASEANET_ADMIN_ACCOUNT_ID =~ ^[0-9]+$ ]]; then
bin/console user:password --user_id=$PHRASEANET_ADMIN_ACCOUNT_ID --password $PHRASEANET_ADMIN_ACCOUNT_PASSWORD -y
fi
echo `date +"%Y-%m-%d %H:%M:%S"` " - config/configuration.yml update by Phraseanet entrypoint.sh Finished !"
else
echo "$FILE doesn't exist, entering setup..."
@@ -62,6 +62,7 @@ else
datas
runuser app -c docker/phraseanet/auto-install.sh
echo `date +"%Y-%m-%d %H:%M:%S"` " - End of Phraseanet Installation"
fi
if [ ${XDEBUG_ENABLED} == "1" ]; then
@@ -70,7 +71,8 @@ if [ ${XDEBUG_ENABLED} == "1" ]; then
fi
./docker/phraseanet/plugins/console init
#rm -Rf cache/
rm -Rf cache/*
chmod 600 config/configuration.yml
chown -R app:app \
cache \
@@ -84,6 +86,7 @@ if [ -d "plugins/" ];then
chown -R app:app plugins;
fi
chown -R app:app datas &
chown -R app:app datas && echo `date +"%Y-%m-%d %H:%M:%S"` " - Finished chown on datas by entreypoint" &
echo `date +"%Y-%m-%d %H:%M:%S"` " - Finished runnning Phraseanet entrypoint.sh"
bash -e docker-php-entrypoint $@

View File

@@ -15,4 +15,47 @@ if [ ${XDEBUG_ENABLED} == "1" ]; then
docker-php-ext-enable xdebug
fi
if [ -f /etc/ImageMagick-$IMAGEMAGICK_POLICY_VERSION/policy.xml ]; then
if [ ! -d $IMAGEMAGICK_POLICY_TEMPORARY_PATH ]; then
echo "$IMAGEMAGICK_POLICY_TEMPORARY_PATH does not exist lets create it"
mkdir -p $IMAGEMAGICK_POLICY_TEMPORARY_PATH
fi
sed -i "s/domain=\"resource\" name=\"memory\" value=\".*\"/domain=\"resource\" name=\"memory\" value=\"$IMAGEMAGICK_POLICY_MEMORY\"/g" /etc/ImageMagick-$IMAGEMAGICK_POLICY_VERSION/policy.xml
sed -i "s/domain=\"resource\" name=\"map\" value=\".*\"/domain=\"resource\" name=\"map\" value=\"$IMAGEMAGICK_POLICY_MAP\"/g" /etc/ImageMagick-$IMAGEMAGICK_POLICY_VERSION/policy.xml
sed -i "s/domain=\"resource\" name=\"width\" value=\".*\"/domain=\"resource\" name=\"width\" value=\"$IMAGEMAGICK_POLICY_WIDTH\"/g" /etc/ImageMagick-$IMAGEMAGICK_POLICY_VERSION/policy.xml
sed -i "s/domain=\"resource\" name=\"height\" value=\".*\"/domain=\"resource\" name=\"height\" value=\"$IMAGEMAGICK_POLICY_HEIGHT\"/g" /etc/ImageMagick-$IMAGEMAGICK_POLICY_VERSION/policy.xml
sed -i "s/domain=\"resource\" name=\"disk\" value=\".*\"/domain=\"resource\" name=\"disk\" value=\"$IMAGEMAGICK_POLICY_DISK\"/g" /etc/ImageMagick-$IMAGEMAGICK_POLICY_VERSION/policy.xml
sed -i "s/domain=\"resource\" name=\"area\" value=\".*\"/domain=\"resource\" name=\"area\" value=\"$IMAGEMAGICK_POLICY_AREA\"/g" /etc/ImageMagick-$IMAGEMAGICK_POLICY_VERSION/policy.xml
sed -i "s/.*domain=\"resource\" name=\"temporary-path\" value=\".*/<domain=\"resource\" name=\"temporary-path\" value=\"\\$IMAGEMAGICK_POLICY_TEMPORARY_PATH\" \/\>/g" /etc/ImageMagick-$IMAGEMAGICK_POLICY_VERSION/policy.xml
fi
rm -rf bin/run-worker.sh
if [ ${PHRASEANET_EXPLODE_WORKER} == "1" ]; then
for i in `env | grep PHRASEANET_WORKER_ | cut -d'=' -f1`
do
queue_name="$(echo $i | cut -d'_' -f3)"
m=$i
command="bin/console worker:execute --queue-name=$queue_name -m ${!m} &"
echo $command >> bin/run-worker.sh
done
echo 'WORKER_NB_QUEUES=`env | grep PHRASEANET_WORKER_ | wc -l`
WORKER_LOOP_VALUE=20s
while true;
do
sleep $WORKER_LOOP_VALUE
nb_process=`ps faux | grep "worker:execute" | grep php | wc -l`
date_time_process=`date +"%Y-%m-%d %H:%M:%S"`
echo $date_time_process "-" $nb_process "running workers"
if [ $nb_process -lt $WORKER_NB_QUEUES ]
then
exit 1
break
fi
done ' >> bin/run-worker.sh
else
command="bin/console worker:execute"
echo $command >> bin/run-worker.sh
fi
runuser -u app -- $@

View File

@@ -13,6 +13,7 @@ use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\Controller\RecordsRequest;
use Alchemy\Phrasea\Model\Entities\Basket;
use Alchemy\Phrasea\Model\Entities\BasketElement;
use Alchemy\Phrasea\Model\Entities\ValidationData;
use Alchemy\Phrasea\Model\Manipulator\BasketManipulator;
use Alchemy\Phrasea\Model\Repositories\BasketElementRepository;
use Doctrine\ORM\EntityManagerInterface;
@@ -251,8 +252,25 @@ class BasketController extends Controller
continue;
}
$basket_element->getBasket()->removeElement($basket_element);
$oldBasket = $basket_element->getBasket();
$oldBasket->removeElement($basket_element);
$basket->addElement($basket_element);
// configure participant when moving from other type of basket to basket type feedback
if ($oldBasket->getValidation() == null && ($validationSession = $basket->getValidation()) !== null) {
$participants = $validationSession->getParticipants();
foreach ($participants as $participant) {
$validationData = new ValidationData();
$validationData->setParticipant($participant);
$validationData->setBasketElement($basket_element);
$this->getEntityManager()->persist($validationData);
}
}
$n++;
}

View File

@@ -59,9 +59,12 @@ class LanguageController
'feed_require_fields' => $translator->trans('Vous n\'avez pas rempli tous les champ requis'),
'feed_require_feed' => $translator->trans('Vous n\'avez pas selectionne de fil de publication'),
'removeTitle' => $translator->trans('panier::Supression d\'un element d\'un reportage'),
'removeRecordFeedbackTitle' => $translator->trans('basket:feedback Delete item'),
'removeExposePublication' => $translator->trans('expose::Your are about to delete a publication from expose, please confirm your action !'),
'removeAssetPublication' => $translator->trans('expose::Your are about to delete an asset from a publication, please confirm your action !'),
'confirmRemoveReg' => $translator->trans('panier::Attention, vous etes sur le point de supprimer un element du reportage. Merci de confirmer votre action.'),
'confirmRemoveFeedBack' => $translator->trans('basket:feedback Warning!You are about to delete one record from a feedback, please confirm your action'),
'movedRecord' => $translator->trans('basket:: Items are being to moved !'),
'advsearch_title' => $translator->trans('phraseanet::recherche avancee'),
'bask_rename' => $translator->trans('panier:: renommer le panier'),
'reg_wrong_sbas' => $translator->trans('panier:: Un reportage ne peux recevoir que des elements provenants de la base ou il est enregistre'),

View File

@@ -146,7 +146,7 @@ class PushController extends Controller
$this->getDataboxLogger($element->getDatabox())->log(
$element,
Session_Logger::EVENT_VALIDATE,
Session_Logger::EVENT_PUSH,
$user_receiver->getId(),
''
);
@@ -386,7 +386,7 @@ class PushController extends Controller
$this->getDataboxLogger($basketElement->getRecord($this->app)->getDatabox())->log(
$basketElement->getRecord($this->app),
Session_Logger::EVENT_PUSH,
Session_Logger::EVENT_VALIDATE,
$participantUser->getId(),
''
);

View File

@@ -103,6 +103,18 @@ class RecordController extends Controller
$recordTitle = htmlspecialchars($record->get_title());
}
$containerType = null;
if ($env === 'BASK') {
if ($record->get_container()->getValidation()) {
$containerType = 'feedback';
} elseif ($record->get_container()->getPusher()) {
$containerType = 'push';
} else {
$containerType = 'basket';
}
}
return $this->app->json([
"desc" => $this->render('prod/preview/caption.html.twig', [
'record' => $record,
@@ -131,6 +143,7 @@ class RecordController extends Controller
]),
"pos" => $record->getNumber(),
"title" => $recordTitle,
"containerType" => $containerType,
"databox_name" => $record->getDatabox()->get_dbname(),
"collection_name" => $record->getCollection()->get_name(),
"collection_logo" => $record->getCollection()->getLogo($record->getBaseId(), $this->app),

View File

@@ -320,6 +320,7 @@ class LegacyRecordRepository implements RecordRepository
. " WHERE g.rid_parent IN ( :storyIds )\n"
. " ORDER BY g.rid_parent, g.ord ASC\n"
. ") r \n"
. "ORDER BY r.rid_parent, r.ord ASC\n"
;
}

View File

@@ -6,6 +6,7 @@ use Alchemy\Phrasea\Command\Command;
use Alchemy\Phrasea\WorkerManager\Queue\AMQPConnection;
use Alchemy\Phrasea\WorkerManager\Queue\MessageHandler;
use Alchemy\Phrasea\WorkerManager\Worker\WorkerInvoker;
use Doctrine\DBAL\Connection;
use PhpAmqpLib\Channel\AMQPChannel;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -45,7 +46,7 @@ class WorkerExecuteCommand extends Command
if ($channel == null) {
$output->writeln("Can't connect to rabbit, check configuration!");
return;
return 1;
}
$serverConnection->declareExchange();
@@ -56,7 +57,7 @@ class WorkerExecuteCommand extends Command
if ($input->getOption('max-processes') != null && $maxProcesses == 0) {
$output->writeln('<error>Invalid max-processes option.Need an integer</error>');
return;
return 1;
} elseif($maxProcesses) {
$workerInvoker->setMaxProcessPoolValue($maxProcesses);
}
@@ -69,8 +70,26 @@ class WorkerExecuteCommand extends Command
$messageHandler = $this->container['alchemy_worker.message.handler'];
$messageHandler->consume($serverConnection, $workerInvoker, $argQueueName, $maxProcesses);
/** @var Connection $dbConnection */
$dbConnection = $this->container['orm.em']->getConnection();
while (count($channel->callbacks)) {
$output->writeln("[*] Waiting for messages. To exit press CTRL+C");
// check connection for DB before given message to consumer
if($dbConnection->ping() === false){
$output->writeln("MySQL server is not available : retry to close and connect ....");
try {
$dbConnection->close();
$dbConnection->connect();
} catch (\Exception $e) {
// Mysql server can't be reconnected, so stop the worker
$serverConnection->connectionClose();
return 1;
}
}
$channel->wait();
}

View File

@@ -13,19 +13,22 @@ use Alchemy\Phrasea\WorkerManager\Form\WorkerConfigurationType;
use Alchemy\Phrasea\WorkerManager\Form\WorkerFtpType;
use Alchemy\Phrasea\WorkerManager\Form\WorkerPullAssetsType;
use Alchemy\Phrasea\WorkerManager\Form\WorkerSearchengineType;
use Alchemy\Phrasea\WorkerManager\Form\WorkerValidationReminderType;
use Alchemy\Phrasea\WorkerManager\Queue\AMQPConnection;
use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher;
use Doctrine\ORM\OptimisticLockException;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class AdminConfigurationController extends Controller
{
public function indexAction(PhraseaApplication $app)
public function indexAction(PhraseaApplication $app, Request $request)
{
/** @var AMQPConnection $serverConnection */
$serverConnection = $this->app['alchemy_worker.amqp.connection'];
/** @var WorkerRunningJobRepository $repoWorker */
$repoWorker = $app['repo.worker-running-job'];
@@ -39,9 +42,10 @@ class AdminConfigurationController extends Controller
$workerRunningJob = $repoWorker->findByStatus($filterStatus);
return $this->render('admin/worker-manager/index.html.twig', [
'isConnected' => ($serverConnection->getChannel() != null) ? true : false,
'isConnected' => $this->getAMQPConnection()->getChannel() != null,
'workerRunningJob' => $workerRunningJob,
'reload' => false
'reload' => false,
'_fragment' => $request->get('_fragment') ?? 'worker-configuration',
]);
}
@@ -52,26 +56,54 @@ class AdminConfigurationController extends Controller
*/
public function configurationAction(PhraseaApplication $app, Request $request)
{
$retryQueueConfig = $this->getRetryQueueConfiguration();
$AMQPConnection = $this->getAMQPConnection();
$form = $app->form(new WorkerConfigurationType(), $retryQueueConfig);
$conf = ['queues' => $this->getConf()->get(['workers', 'queues'], [])];
// ttl's are saved in conf in ms, display in form as sec.
foreach($conf['queues'] as $qname => $settings) {
foreach ($settings as $k=>$v) {
if(in_array($k, [AMQPConnection::TTL_RETRY, AMQPConnection::TTL_DELAYED])) {
$conf['queues'][$qname][$k] /= 1000.0;
}
}
}
$form = $app->form(new WorkerConfigurationType($AMQPConnection), $conf);
$form->handleRequest($request);
if ($form->isValid()) {
// save config in file
$app['conf']->set(['workers', 'retry_queue'], $form->getData());
// save config
// too bad we must remove null entries from data to not save in conf
$_data = $form->getData();
$data = $conf['queues']; // we will save a patched conf (not only data) so custom settings will be preserved
foreach($_data['queues'] as $qname => $settings) {
$data[$qname] = [];
foreach ($settings as $k=>$v) {
if(!is_null($v)) { // ignore null values from form
if(in_array($k, [AMQPConnection::TTL_RETRY, AMQPConnection::TTL_DELAYED])) {
$v = (int)(1000 * (float)$v);
}
$data[$qname][$k] = $v;
}
}
}
ksort($data);
$app['conf']->set(['workers', 'queues'], $data);
/*
* todo : reinitialize q can't depend on form content :
* e.g. if a ttl_retry is blank in form, the value should go back to default, so the q should be reinit.
*
$queues = array_intersect_key(AMQPConnection::$defaultQueues, $retryQueueConfig);
$retryQueuesToReset = array_intersect_key(AMQPConnection::$defaultRetryQueues, array_flip($queues));
/** @var AMQPConnection $serverConnection */
$serverConnection = $this->app['alchemy_worker.amqp.connection'];
// change the queue TTL
$serverConnection->reinitializeQueue($retryQueuesToReset);
$serverConnection->reinitializeQueue(AMQPConnection::$defaultDelayedQueues);
$AMQPConnection->reinitializeQueue($retryQueuesToReset);
$AMQPConnection->reinitializeQueue(AMQPConnection::$defaultDelayedQueues);
*/
return $app->redirectPath('worker_admin');
// too bad : _fragment does not work with our old url generator... it will be passed as plain url parameter
return $app->redirectPath('worker_admin', ['_fragment'=>'worker-configuration']);
}
return $this->render('admin/worker-manager/worker_configuration.html.twig', [
@@ -84,7 +116,7 @@ class AdminConfigurationController extends Controller
/** @var WorkerRunningJobRepository $repoWorker */
$repoWorker = $app['repo.worker-running-job'];
$reload = ($request->query->get('reload')) == 1 ? true : false ;
$reload = ($request->query->get('reload') == 1);
$workerRunningJob = [];
$filterStatus = [];
@@ -114,8 +146,8 @@ class AdminConfigurationController extends Controller
/**
* @param Request $request
* @param $workerId
* @return \Symfony\Component\HttpFoundation\JsonResponse
* @throws \Doctrine\ORM\OptimisticLockException
* @return JsonResponse
* @throws OptimisticLockException
*/
public function changeStatusAction(Request $request, $workerId)
{
@@ -140,13 +172,11 @@ class AdminConfigurationController extends Controller
public function queueMonitorAction(PhraseaApplication $app, Request $request)
{
$reload = ($request->query->get('reload')) == 1 ? true : false ;
$reload = ($request->query->get('reload') == 1);
/** @var AMQPConnection $serverConnection */
$serverConnection = $app['alchemy_worker.amqp.connection'];
$serverConnection->getChannel();
$serverConnection->declareExchange();
$queuesStatus = $serverConnection->getQueuesStatus();
$this->getAMQPConnection()->getChannel();
$this->getAMQPConnection()->declareExchange();
$queuesStatus = $this->getAMQPConnection()->getQueuesStatus();
return $this->render('admin/worker-manager/worker_queue_monitor.html.twig', [
'queuesStatus' => $queuesStatus,
@@ -162,10 +192,20 @@ class AdminConfigurationController extends Controller
return $this->app->json(['success' => false]);
}
/** @var AMQPConnection $serverConnection */
$serverConnection = $this->app['alchemy_worker.amqp.connection'];
$this->getAMQPConnection()->reinitializeQueue([$queueName]);
$serverConnection->reinitializeQueue([$queueName]);
return $this->app->json(['success' => true]);
}
public function deleteQueueAction(PhraseaApplication $app, Request $request)
{
$queueName = $request->request->get('queueName');
if (empty($queueName)) {
return $this->app->json(['success' => false]);
}
$this->getAMQPConnection()->deleteQueue($queueName);
return $this->app->json(['success' => true]);
}
@@ -176,7 +216,8 @@ class AdminConfigurationController extends Controller
$repoWorker = $app['repo.worker-running-job'];
$repoWorker->truncateWorkerTable();
return $app->redirectPath('worker_admin');
// too bad : _fragment does not work with our old url generator... it will be passed as plain url parameter
return $app->redirectPath('worker_admin', ['_fragment'=>'worker-info']);
}
public function deleteFinishedAction(PhraseaApplication $app)
@@ -185,7 +226,8 @@ class AdminConfigurationController extends Controller
$repoWorker = $app['repo.worker-running-job'];
$repoWorker->deleteFinishedWorks();
return $app->redirectPath('worker_admin');
// too bad : _fragment does not work with our old url generator... it will be passed as plain url parameter
return $app->redirectPath('worker_admin', ['_fragment'=>'worker-info']);
}
public function searchengineAction(PhraseaApplication $app, Request $request)
@@ -201,7 +243,8 @@ class AdminConfigurationController extends Controller
$this->getDispatcher()->dispatch(WorkerEvents::POPULATE_INDEX, new PopulateIndexEvent($populateInfo));
return $app->redirectPath('worker_admin');
// too bad : _fragment does not work with our old url generator... it will be passed as plain url parameter
return $app->redirectPath('worker_admin', ['_fragment'=>'worker-searchengine']);
}
return $this->render('admin/worker-manager/worker_searchengine.html.twig', [
@@ -211,14 +254,12 @@ class AdminConfigurationController extends Controller
public function subviewAction()
{
return $this->render('admin/worker-manager/worker_subview.html.twig', [
]);
return $this->render('admin/worker-manager/worker_subview.html.twig', [ ]);
}
public function metadataAction()
{
return $this->render('admin/worker-manager/worker_metadata.html.twig', [
]);
return $this->render('admin/worker-manager/worker_metadata.html.twig', [ ]);
}
public function ftpAction(PhraseaApplication $app, Request $request)
@@ -231,7 +272,8 @@ class AdminConfigurationController extends Controller
// save new ftp config
$app['conf']->set(['workers', 'ftp'], array_merge($ftpConfig, $form->getData()));
return $app->redirectPath('worker_admin');
// too bad : _fragment does not work with our old url generator... it will be passed as plain url parameter
return $app->redirectPath('worker_admin', ['_fragment'=>'worker-ftp']);
}
return $this->render('admin/worker-manager/worker_ftp.html.twig', [
@@ -241,26 +283,60 @@ class AdminConfigurationController extends Controller
public function validationReminderAction(PhraseaApplication $app, Request $request)
{
$interval = $app['conf']->get(['workers', 'validationReminder', 'interval'], 7200);
// nb : the "interval" for a loop-q is the ttl.
// so the setting is stored into the "queues" settings in conf.
// here only the "ttl_retry" can be set/changed in conf
$config = $this->getConf()->get(['workers', 'queues', MessagePublisher::VALIDATION_REMINDER_TYPE], []);
if(isset($config['ttl_retry'])) {
// all settings are in msec, but into the form we want large numbers in sec.
$config['ttl_retry'] /= 1000;
}
/** @var Form $form */
$form = $app->form(new WorkerValidationReminderType($this->getAMQPConnection()), $config);
if ($request->getMethod() == 'POST') {
$reminderInterval = (int)$request->request->get('worker_reminder_interval');
/** @var AMQPConnection $serverConnection */
$serverConnection = $this->app['alchemy_worker.amqp.connection'];
$serverConnection->setQueue(MessagePublisher::VALIDATION_REMINDER_QUEUE);
// save the period interval in second
$app['conf']->set(['workers', 'validationReminder', 'interval'], $reminderInterval);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
switch($data['act']) {
case 'save' : // save the form content (settings)
unset($data['act']); // don't save this
// the interval was displayed in sec. in form, convert back to msec
if(isset($data['ttl_retry'])) {
$data['ttl_retry'] *= 1000;
}
$data = array_merge($config, $data);
$app['conf']->set(['workers', 'queues', MessagePublisher::VALIDATION_REMINDER_TYPE], $data);
$this->getAMQPConnection()->reinitializeQueue([MessagePublisher::VALIDATION_REMINDER_TYPE]);
break;
case 'start':
// reinitialize the validation reminder queues
$serverConnection->reinitializeQueue([MessagePublisher::VALIDATION_REMINDER_QUEUE]);
$this->app['alchemy_worker.message.publisher']->initializeLoopQueue(MessagePublisher::VALIDATION_REMINDER_TYPE);
return $app->redirectPath('worker_admin');
$this->getAMQPConnection()->setQueue(MessagePublisher::VALIDATION_REMINDER_TYPE);
$this->getAMQPConnection()->reinitializeQueue([MessagePublisher::VALIDATION_REMINDER_TYPE]);
$this->getMessagePublisher()->initializeLoopQueue(MessagePublisher::VALIDATION_REMINDER_TYPE);
break;
case 'stop':
$this->getAMQPConnection()->reinitializeQueue([MessagePublisher::VALIDATION_REMINDER_TYPE]);
break;
}
// too bad : _fragment does not work with our old url generator... it will be passed as plain url parameter
return $app->redirectPath('worker_admin', ['_fragment'=>'worker-reminder']);
}
// guess if the q is "running" = check if there are pending message on Q or loop-Q
$running = false;
$qStatuses = $this->getAMQPConnection()->getQueuesStatus();
foreach([
MessagePublisher::VALIDATION_REMINDER_TYPE,
$this->getAMQPConnection()->getLoopQueueName(MessagePublisher::VALIDATION_REMINDER_TYPE)
] as $qName) {
if(isset($qStatuses[$qName]) && $qStatuses[$qName]['messageCount'] > 0) {
$running = true;
}
}
return $this->render('admin/worker-manager/worker_validation_reminder.html.twig', [
'interval' => $interval
'form' => $form->createView(),
'running' => $running
]);
}
@@ -276,30 +352,74 @@ class AdminConfigurationController extends Controller
public function pullAssetsAction(PhraseaApplication $app, Request $request)
{
$pullAssetsConfig = $this->getPullAssetsConfiguration();
$form = $app->form(new WorkerPullAssetsType(), $pullAssetsConfig);
$config = $this->getConf()->get(['workers', 'pull_assets'], []);
// the "pullInterval" comes from the ttl_retry
$ttl_retry = $this->getConf()->get(['workers','queues', MessagePublisher::PULL_ASSETS_TYPE, 'ttl_retry'], null);
if(!is_null($ttl_retry)) {
$ttl_retry /= 1000; // form is in sec
}
$config['pullInterval'] = $ttl_retry;
$form = $app->form(new WorkerPullAssetsType(), $config);
$form->handleRequest($request);
if ($form->isValid()) {
/** @var AMQPConnection $serverConnection */
$serverConnection = $this->app['alchemy_worker.amqp.connection'];
$serverConnection->setQueue(MessagePublisher::PULL_QUEUE);
if ($form->isSubmitted() && $form->isValid()) {
// save new pull config
$app['conf']->set(['workers', 'pull_assets'], array_merge($pullAssetsConfig, $form->getData()));
// reinitialize the pull queues
$serverConnection->reinitializeQueue([MessagePublisher::PULL_QUEUE]);
$this->app['alchemy_worker.message.publisher']->initializeLoopQueue(MessagePublisher::PULL_ASSETS_TYPE);
return $app->redirectPath('worker_admin');
$data = $form->getData();
switch($data['act']) {
case 'save' : // save the form content (settings) in 2 places
$ttl_retry = $data['pullInterval'];
unset($data['act'], $data['pullInterval'], $config['pullInterval']);
// save most data under workers/pull_assets
$app['conf']->set(['workers', 'pull_assets'], array_merge($config, $data));
// save ttl in the q settings
if(!is_null($ttl_retry)) {
$this->getConf()->set(['workers','queues', MessagePublisher::PULL_ASSETS_TYPE, 'ttl_retry'], 1000 * (int)$ttl_retry);
}
$this->getAMQPConnection()->reinitializeQueue([MessagePublisher::PULL_ASSETS_TYPE]);
break;
case 'start':
// reinitialize the validation reminder queues
$this->getAMQPConnection()->setQueue(MessagePublisher::PULL_ASSETS_TYPE);
$this->getAMQPConnection()->reinitializeQueue([MessagePublisher::PULL_ASSETS_TYPE]);
$this->getMessagePublisher()->initializeLoopQueue(MessagePublisher::PULL_ASSETS_TYPE);
break;
case 'stop':
$this->getAMQPConnection()->reinitializeQueue([MessagePublisher::PULL_ASSETS_TYPE]);
break;
}
// too bad : _fragment does not work with our old url generator... it will be passed as plain url parameter
return $app->redirectPath('worker_admin', ['_fragment'=>'worker-pull-assets']);
}
// guess if the q is "running" = check if there are pending message on Q or loop-Q
$running = false;
$qStatuses = $this->getAMQPConnection()->getQueuesStatus();
foreach([
MessagePublisher::PULL_ASSETS_TYPE,
$this->getAMQPConnection()->getLoopQueueName(MessagePublisher::PULL_ASSETS_TYPE)
] as $qName) {
if(isset($qStatuses[$qName]) && $qStatuses[$qName]['messageCount'] > 0) {
$running = true;
}
}
return $this->render('admin/worker-manager/worker_pull_assets.html.twig', [
'form' => $form->createView()
'form' => $form->createView(),
'running' => $running
]);
}
/**
* @return MessagePublisher
*/
private function getMessagePublisher()
{
return $this->app['alchemy_worker.message.publisher'];
}
/**
* @return EventDispatcherInterface
*/
@@ -333,18 +453,25 @@ class AdminConfigurationController extends Controller
return $data;
}
private function getPullAssetsConfiguration()
{
return $this->app['conf']->get(['workers', 'pull_assets'], []);
}
private function getFtpConfiguration()
{
return $this->app['conf']->get(['workers', 'ftp'], []);
return $this->getConf()->get(['workers', 'ftp'], []);
}
private function getRetryQueueConfiguration()
/**
* @return AMQPConnection
*/
private function getAMQPConnection()
{
return $this->app['conf']->get(['workers', 'retry_queue'], []);
return $this->app['alchemy_worker.amqp.connection'];
}
/**
* @return UrlGeneratorInterface
*/
private function getUrlGenerator()
{
return $this->app['url_generator'];
}
}

View File

@@ -10,7 +10,7 @@ class AssetsCreationFailureEvent extends SfEvent
private $workerMessage;
private $count;
public function __construct($payload, $workerMessage, $count = 2)
public function __construct($payload, $workerMessage, $count)
{
$this->payload = $payload;
$this->workerMessage = $workerMessage;

View File

@@ -12,7 +12,7 @@ class AssetsCreationRecordFailureEvent extends SfEvent
private $count;
private $workerJobId;
public function __construct($payload, $workerMessage = '', $count = 2, $workerJobId = 0)
public function __construct($payload, $workerMessage, $count, $workerJobId )
{
$this->payload = $payload;
$this->workerMessage = $workerMessage;

View File

@@ -13,7 +13,7 @@ class ExportMailFailureEvent extends SfEvent
private $workerMessage;
private $count;
public function __construct($emitterUserId, $tokenValue, $destinationMails, $params, $workerMessage = '', $count = 2)
public function __construct($emitterUserId, $tokenValue, $destinationMails, $params, $workerMessage, $count)
{
$this->emitterUserId = $emitterUserId;
$this->tokenValue = $tokenValue;

View File

@@ -14,7 +14,7 @@ class PopulateIndexFailureEvent extends SfEvent
private $count;
private $workerJobId;
public function __construct($host, $port, $indexName, $databoxId, $workerMessage = '', $count = 2, $workerJobId = 0)
public function __construct($host, $port, $indexName, $databoxId, $workerMessage, $count, $workerJobId)
{
$this->host = $host;
$this->port = $port;

View File

@@ -12,7 +12,7 @@ class SubdefinitionCreationFailureEvent extends RecordEvent
private $count;
private $workerJobId;
public function __construct(RecordInterface $record, $subdefName, $workerMessage = '', $count = 2, $workerJobId = 0)
public function __construct(RecordInterface $record, $subdefName, $workerMessage, $count, $workerJobId)
{
parent::__construct($record);

View File

@@ -11,7 +11,7 @@ class WebhookDeliverFailureEvent extends SfEvent
private $count;
private $deleveryId;
public function __construct($webhookEventId, $workerMessage, $count = 2, $deleveryId = null)
public function __construct($webhookEventId, $workerMessage, $count, $deleveryId = null)
{
$this->webhookEventId = $webhookEventId;
$this->workerMessage = $workerMessage;

View File

@@ -0,0 +1,68 @@
<?php
namespace Alchemy\Phrasea\WorkerManager\Form;
use Alchemy\Phrasea\WorkerManager\Queue\AMQPConnection;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
class QueueSettingsType extends AbstractType
{
private $AMQPConnection;
private $baseQueueName;
public function __construct(AMQPConnection $AMQPConnection, string $baseQueueName)
{
$this->AMQPConnection = $AMQPConnection;
$this->baseQueueName = $baseQueueName;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder->add('n_workers', HiddenType::class, [
'label' => 'admin::workermanager:tab:workerconfig:n_workers',
'required' => false,
'attr' => [
'placeholder' => 1
]
]);
if($this->AMQPConnection->hasRetryQueue($this->baseQueueName) || $this->AMQPConnection->hasLoopQueue($this->baseQueueName)) {
$builder
->add('max_retry', IntegerType::class, [
'label' => 'admin::workermanager:tab:workerconfig:max retry',
'required' => false,
'attr' => [
'placeholder' => $this->AMQPConnection->getDefaultSetting($this->baseQueueName, AMQPConnection::MAX_RETRY),
//'class'=>'col'
]
])
->add('ttl_retry', IntegerType::class, [
'label' => 'admin::workermanager:tab:workerconfig:retry delay in seconds',
'required' => false,
'attr' => [
'placeholder' => $this->AMQPConnection->getDefaultSetting($this->baseQueueName, AMQPConnection::TTL_RETRY) / 1000.0,
//'class'=>'col'
]
]);
}
if($this->AMQPConnection->hasDelayedQueue($this->baseQueueName)) {
$builder->add('ttl_delayed', IntegerType::class, [
'label' => 'admin::workermanager:tab:workerconfig:delayed delay in seconds',
'required' => false,
'attr' => [
'placeholder' => $this->AMQPConnection->getDefaultSetting($this->baseQueueName, AMQPConnection::TTL_DELAYED) / 1000.0,
//'class'=>'col'
]
]);
}
}
public function getName()
{
return 'queue_settings';
}
}

View File

@@ -2,48 +2,43 @@
namespace Alchemy\Phrasea\WorkerManager\Form;
use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher;
use Alchemy\Phrasea\WorkerManager\Queue\AMQPConnection;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
class WorkerConfigurationType extends AbstractType
{
private $AMQPConnection;
public function __construct(AMQPConnection $AMQPConnection)
{
$this->AMQPConnection = $AMQPConnection;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder
->add(MessagePublisher::ASSETS_INGEST_TYPE, 'text', [
'label' => 'admin::workermanager:tab:workerconfig: Ingest retry delay in ms'
])
->add(MessagePublisher::CREATE_RECORD_TYPE, 'text', [
'label' => 'admin::workermanager:tab:workerconfig: Create record retry delay in ms'
])
->add(MessagePublisher::SUBDEF_CREATION_TYPE, 'text', [
'label' => 'admin::workermanager:tab:workerconfig: Subdefinition retry delay in ms'
])
->add(MessagePublisher::WRITE_METADATAS_TYPE, 'text', [
'label' => 'admin::workermanager:tab:workerconfig: Metadatas retry delay in ms'
])
->add(MessagePublisher::WEBHOOK_TYPE, 'text', [
'label' => 'admin::workermanager:tab:workerconfig: Webhook retry delay in ms'
])
->add(MessagePublisher::EXPORT_MAIL_TYPE, 'text', [
'label' => 'admin::workermanager:tab:workerconfig: Export mail retry delay in ms'
])
->add(MessagePublisher::POPULATE_INDEX_TYPE, 'text', [
'label' => 'admin::workermanager:tab:workerconfig: Populate Index retry delay in ms'
])
->add(MessagePublisher::FTP_TYPE, 'text', [
'label' => 'admin::workermanager:tab:workerconfig: Ftp retry delay in ms (default 3 min)'
])
->add('delayedSubdef', 'text', [
'label' => 'admin::workermanager:tab:workerconfig: Subdef delay in ms'
])
->add('delayedWriteMeta', 'text', [
'label' => 'admin::workermanager:tab:workerconfig: Write meta delay in ms'
])
;
$g = $builder->create("queues", FormType::class, ['attr'=>['class'=>'form-row']]);
foreach($this->AMQPConnection->getBaseQueueNames() as $baseQueueName) {
if($this->AMQPConnection->hasRetryQueue($baseQueueName)
|| $this->AMQPConnection->hasLoopQueue($baseQueueName)
|| $this->AMQPConnection->hasDelayedQueue($baseQueueName)
) {
$f = new QueueSettingsType($this->AMQPConnection, $baseQueueName);
$g->add($baseQueueName, $f, ['attr' => ['class' => 'norow'], 'block_name' => 'queue']);
}
}
$builder->add($g);
$builder->add("boutton::appliquer", SubmitType::class,
[
'label' => "boutton::appliquer"
]);
}
public function getName()

View File

@@ -3,6 +3,7 @@
namespace Alchemy\Phrasea\WorkerManager\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class WorkerFtpType extends AbstractType
@@ -12,19 +13,19 @@ class WorkerFtpType extends AbstractType
parent::buildForm($builder, $options);
$builder
->add('proxy', 'text', [
->add('proxy', TextType::class, [
'label' => 'admin::workermanager:tab:ftp: Proxy',
'required' => false
])
->add('proxyPort', 'text', [
->add('proxyPort', TextType::class, [
'label' => 'admin::workermanager:tab:ftp: Proxy port',
'required' => false
])
->add('proxyUser', 'text', [
->add('proxyUser', TextType::class, [
'label' => 'admin::workermanager:tab:ftp: Proxy user',
'required' => false
])
->add('proxyPassword', 'text', [
->add('proxyPassword', TextType::class, [
'label' => 'admin::workermanager:tab:ftp: Proxy password',
'required' => false
])

View File

@@ -3,6 +3,9 @@
namespace Alchemy\Phrasea\WorkerManager\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class WorkerPullAssetsType extends AbstractType
@@ -11,20 +14,36 @@ class WorkerPullAssetsType extends AbstractType
{
parent::buildForm($builder, $options);
// because this form will have 3 submit buttons - to use the same route -, this "act" field
// will reflect the value of the clicked button (js)
// !!! tried: using symfony "getClickedButton()" does to NOT work (submit button values seems not sent in request ?)
$builder
->add('UploaderApiBaseUri', 'text', [
->add('act', HiddenType::class, [
'attr' => [
'class' => 'act'
]
]);
$builder
->add('UploaderApiBaseUri', TextType::class, [
'label' => 'admin::workermanager:tab:pullassets: Uploader api base uri'
])
->add('clientSecret', 'text', [
->add('clientSecret', TextType::class, [
'label' => 'admin::workermanager:tab:pullassets: Client secret'
])
->add('clientId', 'text', [
->add('clientId', TextType::class, [
'label' => 'admin::workermanager:tab:pullassets: Client ID'
])
->add('pullInterval', 'text', [
->add('pullInterval', TextType::class, [
'label' => 'admin::workermanager:tab:pullassets: Fetching interval in second'
])
;
]);
$builder
->add("boutton::appliquer", SubmitType::class, [
'label' => "boutton::appliquer",
'attr' => ['value' => 'save']
]);
}
public function getName()

View File

@@ -3,6 +3,8 @@
namespace Alchemy\Phrasea\WorkerManager\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Validator\Constraints\NotBlank;
@@ -15,18 +17,18 @@ class WorkerSearchengineType extends AbstractType
parent::buildForm($builder, $options);
$builder
->add('host', 'text', [
->add('host', TextType::class, [
'label' => 'admin::workermanager:tab:searchengine: Elasticsearch server host',
'constraints' => new NotBlank(),
])
->add('port', 'integer', [
->add('port', IntegerType::class, [
'label' => 'admin::workermanager:tab:searchengine: Elasticsearch service port',
'constraints' => [
new Range(['min' => 1, 'max' => 65535]),
new NotBlank()
]
])
->add('indexName', 'text', [
->add('indexName', TextType::class, [
'label' => 'admin::workermanager:tab:searchengine: Elasticsearch index name',
'constraints' => new NotBlank(),
'attr' =>['data-class'=>'inline']

View File

@@ -0,0 +1,68 @@
<?php
namespace Alchemy\Phrasea\WorkerManager\Form;
use Alchemy\Phrasea\WorkerManager\Queue\AMQPConnection;
use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class WorkerValidationReminderType extends AbstractType
{
private $AMQPConnection;
public function __construct(AMQPConnection $AMQPConnection)
{
$this->AMQPConnection = $AMQPConnection;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
// because this form will have 3 submit buttons - to use the same route -, this "act" field
// will reflect the value of the clicked button (js)
// !!! tried: using symfony "getClickedButton()" does to NOT work (submit button values seems not sent in request ?)
$builder
->add('act', HiddenType::class, [
'attr' => [
'class' => 'act'
]
]);
// every ttl is in msec, we display this large one (loop q) in sec in form.
$defaultInterval = $this->AMQPConnection->getDefaultSetting(MessagePublisher::VALIDATION_REMINDER_TYPE, AMQPConnection::TTL_RETRY) / 1000;
$builder
->add('ttl_retry', TextType::class, [
'label' => 'admin::workermanager:tab:Reminder: Interval in second',
'attr' => [
'placeholder' => $defaultInterval
]
]);
$builder
->add("boutton::appliquer", SubmitType::class, [
'label' => "boutton::appliquer",
'attr' => ['value' => 'save']
]);
/*
$builder
->add("submit", ButtonType::class, [
'label' => "start",
// 'data' => 'truc',
// 'empty_data' => 'machin',
'attr'=>[
'value' => 'start',
'name' => 'zobi'
]
]);
*/
}
// public function getName()
// {
// return 'worker_pullAssets';
// }
}

View File

@@ -45,61 +45,81 @@ class ControllerServiceProvider implements ControllerProviderInterface, ServiceP
$firewall->requireRight(\ACL::TASKMANAGER);
});
/** @uses AdminConfigurationController::indexAction */
$controllers->match('/', 'controller.worker.admin.configuration:indexAction')
->method('GET')
->bind('worker_admin');
/** @uses AdminConfigurationController::configurationAction */
$controllers->match('/configuration', 'controller.worker.admin.configuration:configurationAction')
->method('GET|POST')
->bind('worker_admin_configuration');
/** @uses AdminConfigurationController::infoAction */
$controllers->match('/info', 'controller.worker.admin.configuration:infoAction')
->method('GET')
->bind('worker_admin_info');
/** @uses AdminConfigurationController::truncateTableAction */
$controllers->match('/truncate', 'controller.worker.admin.configuration:truncateTableAction')
->method('POST')
->bind('worker_admin_truncate');
/** @uses AdminConfigurationController::deleteFinishedAction */
$controllers->match('/delete-finished', 'controller.worker.admin.configuration:deleteFinishedAction')
->method('POST')
->bind('worker_admin_delete_finished');
/** @uses AdminConfigurationController::searchengineAction */
$controllers->match('/searchengine', 'controller.worker.admin.configuration:searchengineAction')
->method('GET|POST')
->bind('worker_admin_searchengine');
/** @uses AdminConfigurationController::subviewAction */
$controllers->match('/subview', 'controller.worker.admin.configuration:subviewAction')
->method('GET|POST')
->bind('worker_admin_subview');
/** @uses AdminConfigurationController::metadataAction */
$controllers->match('/metadata', 'controller.worker.admin.configuration:metadataAction')
->method('GET|POST')
->bind('worker_admin_metadata');
/** @uses AdminConfigurationController::ftpAction */
$controllers->match('/ftp', 'controller.worker.admin.configuration:ftpAction')
->method('GET|POST')
->bind('worker_admin_ftp');
/** @uses AdminConfigurationController::populateStatusAction */
$controllers->get('/populate-status', 'controller.worker.admin.configuration:populateStatusAction')
->bind('worker_admin_populate_status');
/** @uses AdminConfigurationController::pullAssetsAction */
$controllers->match('/pull-assets', 'controller.worker.admin.configuration:pullAssetsAction')
->method('GET|POST')
->bind('worker_admin_pullAssets');
/** @uses AdminConfigurationController::validationReminderAction */
$controllers->match('/validation-reminder', 'controller.worker.admin.configuration:validationReminderAction')
->method('GET|POST')
->bind('worker_admin_validationReminder');
/** @uses AdminConfigurationController::queueMonitorAction */
$controllers->match('/queue-monitor', 'controller.worker.admin.configuration:queueMonitorAction')
->method('GET')
->bind('worker_admin_queue_monitor');
/** @uses AdminConfigurationController::purgeQueueAction */
$controllers->match('/purge-queue', 'controller.worker.admin.configuration:purgeQueueAction')
->method('POST')
->bind('worker_admin_purge_queue');
/** @uses AdminConfigurationController::deleteQueueAction */
$controllers->match('/delete-queue', 'controller.worker.admin.configuration:deleteQueueAction')
->method('POST')
->bind('worker_admin_delete_queue');
/** @uses AdminConfigurationController::changeStatusAction */
$controllers->match('/{workerId}/change-status', 'controller.worker.admin.configuration:changeStatusAction')
->method('POST')
->assert('workerId', '\d+')

View File

@@ -3,6 +3,7 @@
namespace Alchemy\Phrasea\WorkerManager\Queue;
use Alchemy\Phrasea\Core\Configuration\PropertyAccess;
use Exception;
use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Wire\AMQPTable;
@@ -20,67 +21,109 @@ class AMQPConnection
private $hostConfig;
private $conf;
public static $defaultQueues = [
MessagePublisher::WRITE_METADATAS_TYPE => MessagePublisher::METADATAS_QUEUE,
MessagePublisher::SUBDEF_CREATION_TYPE => MessagePublisher::SUBDEF_QUEUE,
MessagePublisher::EXPORT_MAIL_TYPE => MessagePublisher::EXPORT_QUEUE,
MessagePublisher::WEBHOOK_TYPE => MessagePublisher::WEBHOOK_QUEUE,
MessagePublisher::ASSETS_INGEST_TYPE => MessagePublisher::ASSETS_INGEST_QUEUE,
MessagePublisher::CREATE_RECORD_TYPE => MessagePublisher::CREATE_RECORD_QUEUE,
MessagePublisher::PULL_ASSETS_TYPE => MessagePublisher::PULL_QUEUE,
MessagePublisher::POPULATE_INDEX_TYPE => MessagePublisher::POPULATE_INDEX_QUEUE,
MessagePublisher::DELETE_RECORD_TYPE => MessagePublisher::DELETE_RECORD_QUEUE,
MessagePublisher::MAIN_QUEUE_TYPE => MessagePublisher::MAIN_QUEUE,
MessagePublisher::SUBTITLE_TYPE => MessagePublisher::SUBTITLE_QUEUE,
MessagePublisher::FTP_TYPE => MessagePublisher::FTP_QUEUE,
MessagePublisher::VALIDATION_REMINDER_TYPE => MessagePublisher::VALIDATION_REMINDER_QUEUE,
MessagePublisher::EXPOSE_UPLOAD_TYPE => MessagePublisher::EXPOSE_UPLOAD_QUEUE,
MessagePublisher::RECORD_EDIT_TYPE => MessagePublisher::RECORD_EDIT_QUEUE
];
// the corresponding worker queues and retry queues, loop queue
public static $defaultRetryQueues = [
MessagePublisher::METADATAS_QUEUE => MessagePublisher::RETRY_METADATAS_QUEUE,
MessagePublisher::SUBDEF_QUEUE => MessagePublisher::RETRY_SUBDEF_QUEUE,
MessagePublisher::EXPORT_QUEUE => MessagePublisher::RETRY_EXPORT_QUEUE,
MessagePublisher::WEBHOOK_QUEUE => MessagePublisher::RETRY_WEBHOOK_QUEUE,
MessagePublisher::ASSETS_INGEST_QUEUE => MessagePublisher::RETRY_ASSETS_INGEST_QUEUE,
MessagePublisher::CREATE_RECORD_QUEUE => MessagePublisher::RETRY_CREATE_RECORD_QUEUE,
MessagePublisher::POPULATE_INDEX_QUEUE => MessagePublisher::RETRY_POPULATE_INDEX_QUEUE,
MessagePublisher::PULL_QUEUE => MessagePublisher::LOOP_PULL_QUEUE,
MessagePublisher::FTP_QUEUE => MessagePublisher::RETRY_FTP_QUEUE,
MessagePublisher::VALIDATION_REMINDER_QUEUE => MessagePublisher::LOOP_VALIDATION_REMINDER_QUEUE
];
public static $defaultFailedQueues = [
MessagePublisher::WRITE_METADATAS_TYPE => MessagePublisher::FAILED_METADATAS_QUEUE,
MessagePublisher::SUBDEF_CREATION_TYPE => MessagePublisher::FAILED_SUBDEF_QUEUE,
MessagePublisher::EXPORT_MAIL_TYPE => MessagePublisher::FAILED_EXPORT_QUEUE,
MessagePublisher::WEBHOOK_TYPE => MessagePublisher::FAILED_WEBHOOK_QUEUE,
MessagePublisher::ASSETS_INGEST_TYPE => MessagePublisher::FAILED_ASSETS_INGEST_QUEUE,
MessagePublisher::CREATE_RECORD_TYPE => MessagePublisher::FAILED_CREATE_RECORD_QUEUE,
MessagePublisher::POPULATE_INDEX_TYPE => MessagePublisher::FAILED_POPULATE_INDEX_QUEUE,
MessagePublisher::FTP_TYPE => MessagePublisher::FAILED_FTP_QUEUE
];
public static $defaultDelayedQueues = [
MessagePublisher::METADATAS_QUEUE => MessagePublisher::DELAYED_METADATAS_QUEUE,
MessagePublisher::SUBDEF_QUEUE => MessagePublisher::DELAYED_SUBDEF_QUEUE
];
public static $defaultLoopTypes = [
MessagePublisher::PULL_ASSETS_TYPE,
MessagePublisher::VALIDATION_REMINDER_TYPE
];
// default message TTL in retry queue in millisecond
const RETRY_DELAY = 10000;
// default message TTL for some retry queue , 3 minute
const RETRY_LARGE_DELAY = 180000;
const DEFAULT_RETRY_DELAY_VALUE = 10000;
// default message TTL in delayed queue in millisecond
const DELAY = 5000;
const DEFAULT_DELAYED_DELAY_VALUE = 5000;
// max number of retry before a message goes in failed
const DEFAULT_MAX_RETRY_VALUE = 3;
const WITH_NOTHING = 0;
const WITH_RETRY = 1;
const WITH_DELAYED = 2;
const WITH_LOOP = 4;
const BASE_QUEUE = 'base_q';
const BASE_QUEUE_WITH_RETRY = 'base_q_with_retry';
const BASE_QUEUE_WITH_LOOP = 'base_q_with_loop';
const RETRY_QUEUE = 'retry_q';
const LOOP_QUEUE = 'loop_q';
const FAILED_QUEUE = 'failed_q';
const DELAYED_QUEUE = 'delayed_q';
// settings names
const MAX_RETRY = 'max_retry';
const TTL_RETRY = 'ttl_retry';
const TTL_DELAYED = 'ttl_delayed';
const MESSAGES = [
MessagePublisher::ASSETS_INGEST_TYPE => [
'with' => self::WITH_RETRY,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => self::DEFAULT_RETRY_DELAY_VALUE,
],
MessagePublisher::CREATE_RECORD_TYPE => [
'with' => self::WITH_RETRY,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => self::DEFAULT_RETRY_DELAY_VALUE,
],
MessagePublisher::DELETE_RECORD_TYPE => [
'with' => self::WITH_NOTHING,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
],
MessagePublisher::EXPORT_MAIL_TYPE => [
'with' => self::WITH_RETRY,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => self::DEFAULT_RETRY_DELAY_VALUE,
],
MessagePublisher::EXPOSE_UPLOAD_TYPE => [
'with' => self::WITH_NOTHING,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
],
MessagePublisher::FTP_TYPE => [
'with' => self::WITH_RETRY,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => 180 * 1000,
],
MessagePublisher::MAIN_QUEUE_TYPE => [
'with' => self::WITH_NOTHING,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
],
MessagePublisher::POPULATE_INDEX_TYPE => [
'with' => self::WITH_RETRY,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => self::DEFAULT_RETRY_DELAY_VALUE,
],
MessagePublisher::PULL_ASSETS_TYPE => [
'with' => self::WITH_LOOP,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => self::DEFAULT_RETRY_DELAY_VALUE,
],
MessagePublisher::RECORD_EDIT_TYPE => [
'with' => self::WITH_NOTHING,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
],
MessagePublisher::SUBDEF_CREATION_TYPE => [
'with' => self::WITH_RETRY | self::WITH_DELAYED,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => self::DEFAULT_RETRY_DELAY_VALUE,
self::TTL_DELAYED => self::DEFAULT_DELAYED_DELAY_VALUE
],
MessagePublisher::SUBTITLE_TYPE => [
'with' => self::WITH_NOTHING,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
],
MessagePublisher::VALIDATION_REMINDER_TYPE => [
'with' => self::WITH_LOOP,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => 7200 * 1000,
],
MessagePublisher::WEBHOOK_TYPE => [
'with' => self::WITH_RETRY,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => self::DEFAULT_RETRY_DELAY_VALUE,
],
MessagePublisher::WRITE_METADATAS_TYPE => [
'with' => self::WITH_RETRY | self::WITH_DELAYED,
self::MAX_RETRY => self::DEFAULT_MAX_RETRY_VALUE,
self::TTL_RETRY => self::DEFAULT_RETRY_DELAY_VALUE,
self::TTL_DELAYED => self::DEFAULT_DELAYED_DELAY_VALUE
],
];
private $queues = []; // filled during construct, from msg list, default values and conf
public function __construct(PropertyAccess $conf)
{
@@ -94,6 +137,165 @@ class AMQPConnection
$this->hostConfig = $conf->get(['workers', 'queue', 'worker-queue'], $defaultConfiguration);
$this->conf = $conf;
// fill list of type attributes
foreach (self::MESSAGES as $name => $attr) {
$settings = $attr;
unset($settings['with']);
$this->queues[$name] = [
'Name' => $name,
'QType' => self::BASE_QUEUE,
'default_settings' => $settings, // to be displayed as placeholder
'settings' => $settings, // settings belongs only to base_q
'Exchange' => self::ALCHEMY_EXCHANGE,
];
// a q with retry can fail (after n retry) or loop
if($attr['with'] & self::WITH_RETRY) {
$this->queues[$name]['QType'] = self::BASE_QUEUE_WITH_RETRY; // todo : avoid changing qtype ?
// declare the retry q, cross-link with base q
$retry_name = $name . '_retry';
$this->queues[$name]['RetryQ'] = $retry_name; // link baseq to retryq
$this->queues[$retry_name] = [
'Name' => $retry_name,
'QType' => self::RETRY_QUEUE,
'Exchange' => self::RETRY_ALCHEMY_EXCHANGE,
'BaseQ' => $name, // link retryq back to baseq
];
// declare the failed q, cross-link with base q
$failed_name = $name . '_failed';
$this->queues[$name]['FailedQ'] = $failed_name; // link baseq to failedq
$this->queues[$failed_name] = [
'Name' => $failed_name,
'QType' => self::FAILED_QUEUE,
'Exchange' => self::RETRY_ALCHEMY_EXCHANGE,
'BaseQ' => $name, // link failedq back to baseq
];
}
// a q can be "delayed" to solve "work in progress" lock on records
if($attr['with'] & self::WITH_DELAYED) {
// declare the delayed q, cross-link with base q
$delayed_name = $name . '_delayed';
$this->queues[$name]['DelayedQ'] = $delayed_name; // link baseq to delayedq
$this->queues[$delayed_name] = [
'Name' => $delayed_name,
'QType' => self::DELAYED_QUEUE,
'Exchange' => self::RETRY_ALCHEMY_EXCHANGE,
'BaseQ' => $name, // link delayedq back to baseq
];
}
if($attr['with'] & self::WITH_LOOP) {
$this->queues[$name]['QType'] = self::BASE_QUEUE_WITH_LOOP; // todo : avoid changing qtype ?
// declare the loop q, cross-link with base q
$loop_name = $name . '_loop';
$this->queues[$name]['LoopQ'] = $loop_name; // link baseq to loopq
$this->queues[$loop_name] = [
'Name' => $loop_name,
'QType' => self::LOOP_QUEUE,
'Exchange' => self::RETRY_ALCHEMY_EXCHANGE,
'BaseQ' => $name, // link loopq back to baseq
];
}
}
// inject conf values
foreach($conf->get(['workers', 'queues'], []) as $name => $settings) {
if(!isset($this->queues[$name])) {
throw new Exception(sprintf('undefined queue "%s" in conf', $name));
}
$this->queues[$name]['settings'] = array_merge($this->queues[$name]['settings'], $settings);
}
}
public function getBaseQueueNames()
{
$keys = array_keys(self::MESSAGES);
asort($keys);
return $keys;
}
public function isBaseQueue(string $queueName)
{
return array_key_exists($queueName, self::MESSAGES);
}
public function getBaseQueueName(string $baseQueueName)
{
$q = $this->getQueue($baseQueueName);
return $q['Name'];
}
public function hasRetryQueue(string $baseQueueName)
{
$q = $this->getQueue($baseQueueName);
return array_key_exists('RetryQ', $q);
}
public function getRetryQueueName(string $baseQueueName)
{
$q = $this->getQueue($baseQueueName, 'RetryQ');
return $q['Name'];
}
public function getSetting(string $baseQueueName, string $settingName)
{
$q = $this->getQueue($baseQueueName);
return $q['settings'][$settingName];
}
public function getDefaultSetting(string $baseQueueName, string $settingName)
{
$q = $this->getQueue($baseQueueName);
return $q['default_settings'][$settingName];
}
public function hasDelayedQueue(string $baseQueueName)
{
$q = $this->getQueue($baseQueueName);
return array_key_exists('DelayedQ', $q);
}
public function getDelayedQueueName(string $baseQueueName)
{
$q = $this->getQueue($baseQueueName, 'DelayedQ');
return $q['Name'];
}
public function getFailedQueueName(string $baseQueueName)
{
$q = $this->getQueue($baseQueueName, 'FailedQ');
return $q['Name'];
}
public function hasLoopQueue(string $baseQueueName)
{
$q = $this->getQueue($baseQueueName);
return array_key_exists('LoopQ', $q);
}
public function getLoopQueueName(string $baseQueueName)
{
$q = $this->getQueue($baseQueueName, 'LoopQ');
return $q['Name'];
}
public function getExchange(string $queueName)
{
$q = $this->getQueue($queueName);
return $q['Exchange'];
}
private function getQueue(string $queueName, string $subQueueKey = null)
{
if(!array_key_exists($queueName, $this->queues)) {
throw new Exception(sprintf('undefined queue "%s"', $queueName));
}
if($subQueueKey && !array_key_exists($subQueueKey, $this->queues[$queueName])) {
throw new Exception(sprintf('base queue "%s" has no "%s"', $queueName, $subQueueKey));
}
return $subQueueKey ? $this->queues[$this->queues[$queueName][$subQueueKey]] : $this->queues[$queueName];
}
public function getConnection()
@@ -108,8 +310,9 @@ class AMQPConnection
$this->hostConfig['vhost']
);
} catch (\Exception $e) {
}
catch (Exception $e) {
// no-op
}
}
@@ -127,7 +330,8 @@ class AMQPConnection
}
return null;
} else {
}
else {
return $this->channel;
}
}
@@ -156,106 +360,191 @@ class AMQPConnection
$this->declareExchange();
}
if (isset(self::$defaultRetryQueues[$queueName])) {
$this->channel->queue_declare($queueName, false, true, false, false, false, new AMQPTable([
$queue = $this->queues[$queueName];
switch($queue['QType']) {
case self::BASE_QUEUE_WITH_RETRY:
$this->queue_declare_and_bind($queueName, self::ALCHEMY_EXCHANGE, [
'x-dead-letter-exchange' => self::RETRY_ALCHEMY_EXCHANGE, // the exchange to which republish a 'dead' message
'x-dead-letter-routing-key' => self::$defaultRetryQueues[$queueName] // the routing key to apply to this 'dead' message
]));
$this->channel->queue_bind($queueName, self::ALCHEMY_EXCHANGE, $queueName);
// declare also the corresponding retry queue
// use this to delay the delivery of a message to the alchemy-exchange
$this->channel->queue_declare(self::$defaultRetryQueues[$queueName], false, true, false, false, false, new AMQPTable([
'x-dead-letter-exchange' => AMQPConnection::ALCHEMY_EXCHANGE,
'x-dead-letter-routing-key' => $queueName,
'x-message-ttl' => $this->getTtlRetryPerRouting($queueName)
]));
$this->channel->queue_bind(self::$defaultRetryQueues[$queueName], AMQPConnection::RETRY_ALCHEMY_EXCHANGE, self::$defaultRetryQueues[$queueName]);
} elseif (in_array($queueName, self::$defaultRetryQueues)) {
// if it's a retry queue
$routing = array_search($queueName, AMQPConnection::$defaultRetryQueues);
$this->channel->queue_declare($queueName, false, true, false, false, false, new AMQPTable([
'x-dead-letter-exchange' => AMQPConnection::ALCHEMY_EXCHANGE,
'x-dead-letter-routing-key' => $routing,
'x-message-ttl' => $this->getTtlRetryPerRouting($routing)
]));
$this->channel->queue_bind($queueName, AMQPConnection::RETRY_ALCHEMY_EXCHANGE, $queueName);
} elseif (in_array($queueName, self::$defaultFailedQueues)) {
// if it's a failed queue
$this->channel->queue_declare($queueName, false, true, false, false, false);
$this->channel->queue_bind($queueName, AMQPConnection::RETRY_ALCHEMY_EXCHANGE, $queueName);
} elseif (in_array($queueName, self::$defaultDelayedQueues)) {
// if it's a delayed queue
$routing = array_search($queueName, AMQPConnection::$defaultDelayedQueues);
$this->channel->queue_declare($queueName, false, true, false, false, false, new AMQPTable([
'x-dead-letter-exchange' => AMQPConnection::ALCHEMY_EXCHANGE,
'x-dead-letter-routing-key' => $routing,
'x-message-ttl' => $this->getTtlDelayedPerRouting($routing)
]));
$this->channel->queue_bind($queueName, AMQPConnection::RETRY_ALCHEMY_EXCHANGE, $queueName);
} else {
$this->channel->queue_declare($queueName, false, true, false, false, false);
$this->channel->queue_bind($queueName, AMQPConnection::ALCHEMY_EXCHANGE, $queueName);
'x-dead-letter-routing-key' => $queue['RetryQ'] // the routing key to apply to this 'dead' message
]);
$this->setQueue($queue['RetryQ']);
break;
case self::BASE_QUEUE_WITH_LOOP:
$this->queue_declare_and_bind($queueName, self::ALCHEMY_EXCHANGE, [
'x-dead-letter-exchange' => self::RETRY_ALCHEMY_EXCHANGE, // the exchange to which republish a 'dead' message
'x-dead-letter-routing-key' => $queue['LoopQ'] // the routing key to apply to this 'dead' message
]);
$this->setQueue($queue['LoopQ']);
break;
case self::LOOP_QUEUE:
case self::RETRY_QUEUE:
$this->queue_declare_and_bind($queueName, self::RETRY_ALCHEMY_EXCHANGE, [
'x-dead-letter-exchange' => self::ALCHEMY_EXCHANGE,
'x-dead-letter-routing-key' => $queue['BaseQ'],
'x-message-ttl' => (int)$this->queues[$queue['BaseQ']]['settings'][self::TTL_RETRY]
]);
break;
case self::DELAYED_QUEUE:
$this->queue_declare_and_bind($queueName, self::RETRY_ALCHEMY_EXCHANGE, [
'x-dead-letter-exchange' => self::ALCHEMY_EXCHANGE,
'x-dead-letter-routing-key' => $queue['BaseQ'],
'x-message-ttl' => (int)$this->queues[$queue['BaseQ']]['settings'][self::TTL_DELAYED]
]);
break;
case self::FAILED_QUEUE:
$this->queue_declare_and_bind($queueName, self::RETRY_ALCHEMY_EXCHANGE);
break;
case self::BASE_QUEUE:
$this->queue_declare_and_bind($queueName, self::ALCHEMY_EXCHANGE);
break;
default:
throw new Exception(sprintf('undefined q type "%s', $queueName));
break;
}
return $this->channel;
}
public function reinitializeQueue(array $queuNames)
private function queue_declare_and_bind(string $name, string $exchange, array $arguments = null)
{
try {
$this->channel->queue_declare(
$name,
false, true, false, false, false,
$arguments ? new AMQPTable($arguments) : null
);
$this->channel->queue_bind($name, $exchange, $name);
}
catch (Exception $e) {
// the q exists and arguments don't match, fallback.
// Happens when we try to get the number of messages (getQueueStatus)
// after the settings (e.g. ttl) was changed and the q was not yet re-created
$this->getConnection();
if (isset($this->connection)) {
$this->channel = $this->connection->channel();
}
$this->channel->queue_declare($name,true);
}
}
/**
* purge some queues, delete related retry-q
* nb: called by admin/purgeQueuAction, so a q may be __any kind__ - not only base-types !
*
* @param array $queueNames
* @throws Exception
*/
public function reinitializeQueue(array $queueNames)
{
if (!isset($this->channel)) {
$this->getChannel();
$this->declareExchange();
}
foreach ($queuNames as $queuName) {
if (in_array($queuName, self::$defaultQueues)) {
$this->channel->queue_purge($queuName);
} else {
$this->channel->queue_delete($queuName);
foreach ($queueNames as $queueName) {
// re-inject conf values (some may have changed)
$settings = $this->conf->get(['workers', 'queues', $queueName], []);
if(array_key_exists($queueName, $this->queues)) {
$this->queues[$queueName]['settings'] = array_merge($this->queues[$queueName]['settings'], $settings);
}
if (isset(self::$defaultRetryQueues[$queuName])) {
$this->channel->queue_delete(self::$defaultRetryQueues[$queuName]);
if(array_key_exists($queueName, self::MESSAGES)) {
// base-q
$this->purgeQueue($queueName);
if($this->hasRetryQueue($queueName)) {
$this->deleteQueue($this->getRetryQueueName($queueName));
}
if($this->hasLoopQueue($queueName)) {
$this->deleteQueue($this->getLoopQueueName($queueName));
}
}
else {
// retry, delayed, loop, ... q
$this->deleteQueue($queueName);
}
$this->setQueue($queuName);
$this->setQueue($queueName);
}
}
/**
* delete a queue, fails silently if the q does not exists
*
* @param $queueName
*/
public function deleteQueue($queueName)
{
if (!isset($this->channel)) {
$this->getChannel();
$this->declareExchange();
}
try {
$this->channel->queue_delete($queueName);
}
catch(Exception $e) {
// no-op
}
}
/**
* purge a queue, fails silently if the q does not exists
*
* @param $queueName
*/
public function purgeQueue($queueName)
{
if (!isset($this->channel)) {
$this->getChannel();
$this->declareExchange();
}
try {
$this->channel->queue_purge($queueName);
}
catch(Exception $e) {
// no-op
}
}
/**
* Get queueName, messageCount, consumerCount of queues
* @return array
* @throws Exception
*/
public function getQueuesStatus()
{
$queuesList = array_merge(
array_values(self::$defaultQueues),
array_values(self::$defaultDelayedQueues),
array_values(self::$defaultRetryQueues),
array_values(self::$defaultFailedQueues)
);
$this->getChannel();
$queuesStatus = [];
foreach ($queuesList as $queue) {
$this->setQueue($queue);
list($queueName, $messageCount, $consumerCount) = $this->channel->queue_declare($queue, true);
foreach($this->queues as $name => $queue) {
$status['queueName'] = $queueName;
$status['messageCount'] = $messageCount;
$status['consumerCount'] = $consumerCount;
$this->setQueue($name); // todo : BASE_QUEUE_WITH_RETRY will set both BASE and RETRY Q, so we should skip one of 2
$queuesStatus[] = $status;
unset($status);
$this->getConnection();
if (isset($this->connection)) {
$this->channel = $this->connection->channel();
}
try {
list($queueName, $messageCount, $consumerCount) = $this->channel->queue_declare($name, true);
$queuesStatus[$queueName] = [
'queueName' => $queueName,
'exists' => true,
'messageCount' => $messageCount,
'consumerCount' => $consumerCount
];
}
catch (Exception $e) {
// should not happen since "setQueue()" was called
$queuesStatus[$name] = [
'queueName' => $name,
'exists' => false,
'messageCount' => -1,
'consumerCount' => -1
];
}
}
ksort($queuesStatus);
return $queuesStatus;
}
@@ -265,58 +554,4 @@ class AMQPConnection
$this->channel->close();
$this->connection->close();
}
/**
* @param $routing
* @return int
*/
private function getTtlRetryPerRouting($routing)
{
$config = $this->conf->get(['workers']);
if ($routing == MessagePublisher::PULL_QUEUE &&
isset($config['pull_assets']) &&
isset($config['pull_assets']['pullInterval']) ) {
// convert in milli second
return (int)($config['pull_assets']['pullInterval']) * 1000;
} elseif ($routing == MessagePublisher::VALIDATION_REMINDER_QUEUE) {
if (isset($config['validationReminder']) &&
isset($config['validationReminder']['interval'])) {
// convert in milli second
return (int)($config['validationReminder']['interval']) * 1000;
}
// default value to 2 hour if not set
return (int) 7200 * 1000;
} elseif (isset($config['retry_queue']) &&
isset($config['retry_queue'][array_search($routing, AMQPConnection::$defaultQueues)])) {
return (int)($config['retry_queue'][array_search($routing, AMQPConnection::$defaultQueues)]);
}
if ($routing == MessagePublisher::FTP_QUEUE) {
return self::RETRY_LARGE_DELAY;
} else {
return self::RETRY_DELAY;
}
}
private function getTtlDelayedPerRouting($routing)
{
$delayed = [
MessagePublisher::METADATAS_QUEUE => 'delayedWriteMeta',
MessagePublisher::SUBDEF_QUEUE => 'delayedSubdef'
];
$config = $this->conf->get(['workers']);
if (isset($config['retry_queue']) && isset($config['retry_queue'][$delayed[$routing]])) {
return (int)$config['retry_queue'][$delayed[$routing]];
}
return self::DELAY;
}
}

View File

@@ -4,6 +4,7 @@ namespace Alchemy\Phrasea\WorkerManager\Queue;
use Alchemy\Phrasea\WorkerManager\Worker\ProcessPool;
use Alchemy\Phrasea\WorkerManager\Worker\WorkerInvoker;
use Exception;
use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Wire\AMQPTable;
@@ -11,8 +12,6 @@ use Ramsey\Uuid\Uuid;
class MessageHandler
{
const MAX_OF_TRY = 3;
private $messagePublisher;
public function __construct(MessagePublisher $messagePublisher)
@@ -20,27 +19,37 @@ class MessageHandler
$this->messagePublisher = $messagePublisher;
}
public function consume(AMQPConnection $serverConnection, WorkerInvoker $workerInvoker, $argQueueName, $maxProcesses)
/**
* called by WorkerExecuteCommand cli
*
* @param AMQPConnection $AMQPConnection
* @param WorkerInvoker $workerInvoker
* @param array|null $argQueueNames
* @param $maxProcesses
*/
public function consume(AMQPConnection $AMQPConnection, WorkerInvoker $workerInvoker, $argQueueNames, $maxProcesses)
{
$publisher = $this->messagePublisher;
$channel = $serverConnection->getChannel();
$channel = $AMQPConnection->getChannel();
if ($channel == null) {
// todo : if there is no channel, can we push ?
$this->messagePublisher->pushLog("Can't connect to rabbit, check configuration!", "error");
return ;
}
$serverConnection->declareExchange();
$AMQPConnection->declareExchange();
// define consume callbacks
$callback = function (AMQPMessage $message) use ($channel, $workerInvoker, $publisher) {
$publisher = $this->messagePublisher;
$callback = function (AMQPMessage $message) use ($AMQPConnection, $channel, $workerInvoker, $publisher) {
$data = json_decode($message->getBody(), true);
$count = 0;
$headers = null;
if ($message->has('application_headers')) {
/** @var AMQPTable $headers */
$headers = $message->get('application_headers');
@@ -49,9 +58,10 @@ class MessageHandler
if (isset($headerData['x-death'])) {
$xDeathHeader = $headerData['x-death'];
// todo : if there are more than 1 xdeath ? what is $count ?
foreach ($xDeathHeader as $xdeath) {
$queue = $xdeath['queue'];
if (!in_array($queue, AMQPConnection::$defaultQueues)) {
if (!$AMQPConnection->isBaseQueue($queue)) {
continue;
}
@@ -61,51 +71,45 @@ class MessageHandler
}
}
// if message is yet executed 3 times, save the unprocessed message in the corresponding failed queues
if ($count > self::MAX_OF_TRY && !in_array($data['message_type'], AMQPConnection::$defaultLoopTypes)) {
$this->messagePublisher->publishFailedMessage($data['payload'], $headers, AMQPConnection::$defaultFailedQueues[$data['message_type']]);
$msgType = $data['message_type'];
$logMessage = sprintf("Rabbit message executed 3 times, it's to be saved in %s , payload >>> %s",
AMQPConnection::$defaultFailedQueues[$data['message_type']],
if($count > $AMQPConnection->getSetting($msgType, AMQPConnection::MAX_RETRY) && !$AMQPConnection->hasLoopQueue($msgType)) {
$publisher->publishFailedMessage($data['payload'], $headers, $data['message_type']);
$logMessage = sprintf("Rabbit message executed %s times, it's to be saved in %s , payload >>> %s",
$count,
$msgType,
json_encode($data['payload'])
);
$this->messagePublisher->pushLog($logMessage);
$publisher->pushLog($logMessage);
$channel->basic_ack($message->delivery_info['delivery_tag']);
} else {
}
else {
try {
$workerInvoker->invokeWorker($data['message_type'], json_encode($data['payload']));
$workerInvoker->invokeWorker($msgType, json_encode($data['payload']));
if (in_array($data['message_type'], AMQPConnection::$defaultLoopTypes)) {
if ($AMQPConnection->hasLoopQueue($msgType)) {
// make a loop for the loop type
$channel->basic_nack($message->delivery_info['delivery_tag']);
} else {
$channel->basic_ack($message->delivery_info['delivery_tag']);
}
$oldPayload = $data['payload'];
$message = $data['message_type'].' to be consumed! >> Payload ::'. json_encode($oldPayload);
$publisher->pushLog($message);
} catch (\Exception $e) {
$publisher->pushLog(
sprintf('"%s" to be consumed! >> Payload :: %s', $msgType, json_encode($data['payload']))
);
}
catch (Exception $e) {
$channel->basic_nack($message->delivery_info['delivery_tag']);
}
}
};
$prefetchCount = ProcessPool::MAX_PROCESSES;
if ($maxProcesses) {
$prefetchCount = $maxProcesses;
}
foreach (AMQPConnection::$defaultQueues as $queueName) {
if ($argQueueName ) {
if (in_array($queueName, $argQueueName)) {
$this->runConsumer($queueName, $serverConnection, $channel, $prefetchCount, $callback);
}
} else {
$this->runConsumer($queueName, $serverConnection, $channel, $prefetchCount, $callback);
$prefetchCount = $maxProcesses ? $maxProcesses : ProcessPool::MAX_PROCESSES;
foreach($AMQPConnection->getBaseQueueNames() as $queueName) {
if (!$argQueueNames || in_array($queueName, $argQueueNames)) {
$this->runConsumer($queueName, $AMQPConnection, $channel, $prefetchCount, $callback);
}
}
}
@@ -114,9 +118,10 @@ class MessageHandler
{
$serverConnection->setQueue($queueName);
// todo : remove this if !!! move code to a generic place
// initialize validation reminder when starting consumer
if ($queueName == MessagePublisher::VALIDATION_REMINDER_QUEUE) {
$serverConnection->reinitializeQueue([MessagePublisher::VALIDATION_REMINDER_QUEUE]);
if ($queueName == MessagePublisher::VALIDATION_REMINDER_TYPE) {
$serverConnection->reinitializeQueue([MessagePublisher::VALIDATION_REMINDER_TYPE]);
$this->messagePublisher->initializeLoopQueue(MessagePublisher::VALIDATION_REMINDER_TYPE);
}

View File

@@ -2,6 +2,8 @@
namespace Alchemy\Phrasea\WorkerManager\Queue;
use DateTime;
use DateTimeZone;
use Monolog\Logger;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Wire\AMQPTable;
@@ -28,107 +30,96 @@ class MessagePublisher
const MAIN_QUEUE_TYPE = 'mainQueue';
const MAIN_QUEUE = 'main-queue';
const SUBTITLE_QUEUE = 'subtitle-queue';
// ** ** \\
// worker queue to be consumed, when no ack , it is requeued to the retry queue
const ASSETS_INGEST_QUEUE = 'ingest-queue';
const CREATE_RECORD_QUEUE = 'createrecord-queue';
const DELETE_RECORD_QUEUE = 'deleterecord-queue';
const EXPORT_QUEUE = 'export-queue';
const EXPOSE_UPLOAD_QUEUE = 'exposeupload-queue';
const FTP_QUEUE = 'ftp-queue';
const METADATAS_QUEUE = 'metadatas-queue';
const POPULATE_INDEX_QUEUE = 'populateindex-queue';
const PULL_QUEUE = 'pull-queue';
const RECORD_EDIT_QUEUE = 'recordedit-queue';
const SUBDEF_QUEUE = 'subdef-queue';
const VALIDATION_REMINDER_QUEUE = 'validationReminder-queue';
const WEBHOOK_QUEUE = 'webhook-queue';
// retry queue
// we can use these retry queue with TTL, so when message expires it is requeued to the corresponding worker queue
const RETRY_ASSETS_INGEST_QUEUE = 'retry-ingest-queue';
const RETRY_CREATE_RECORD_QUEUE = 'retry-createrecord-queue';
const RETRY_EXPORT_QUEUE = 'retry-export-queue';
const RETRY_FTP_QUEUE = 'retry-ftp-queue';
const RETRY_METADATAS_QUEUE = 'retry-metadatas-queue';
const RETRY_POPULATE_INDEX_QUEUE = 'retry-populateindex-queue';
const RETRY_SUBDEF_QUEUE = 'retry-subdef-queue';
const RETRY_WEBHOOK_QUEUE = 'retry-webhook-queue';
// use those queue to make a loop on a consumer
const LOOP_PULL_QUEUE = 'loop-pull-queue';
const LOOP_VALIDATION_REMINDER_QUEUE = 'loop-validationReminder-queue';
// all failed queue, if message is treated over 3 times it goes to the failed queue
const FAILED_ASSETS_INGEST_QUEUE = 'failed-ingest-queue';
const FAILED_CREATE_RECORD_QUEUE = 'failed-createrecord-queue';
const FAILED_EXPORT_QUEUE = 'failed-export-queue';
const FAILED_FTP_QUEUE = 'failed-ftp-queue';
const FAILED_METADATAS_QUEUE = 'failed-metadatas-queue';
const FAILED_POPULATE_INDEX_QUEUE = 'failed-populateindex-queue';
const FAILED_SUBDEF_QUEUE = 'failed-subdef-queue';
const FAILED_WEBHOOK_QUEUE = 'failed-webhook-queue';
// delayed queue when record is locked
const DELAYED_SUBDEF_QUEUE = 'delayed-subdef-queue';
const DELAYED_METADATAS_QUEUE = 'delayed-metadatas-queue';
const NEW_RECORD_MESSAGE = 'newrecord';
/** @var AMQPConnection $serverConnection */
private $serverConnection;
/** @var AMQPConnection $AMQPConnection */
private $AMQPConnection;
/** @var Logger */
private $logger;
public function __construct(AMQPConnection $serverConnection, LoggerInterface $logger)
public function __construct(AMQPConnection $AMQPConnection, LoggerInterface $logger)
{
$this->serverConnection = $serverConnection;
$this->AMQPConnection = $AMQPConnection;
$this->logger = $logger;
}
public function publishMessage(array $payload, $queueName, $retryCount = null, $workerMessage = '')
public function publishMessage(array $payload, $queueName)
{
// add published timestamp to all message payload
$payload['payload']['published'] = time();
$msg = new AMQPMessage(json_encode($payload));
$routing = array_search($queueName, AMQPConnection::$defaultRetryQueues);
$this->AMQPConnection->getBaseQueueName($queueName); // just to throw an exception if q is undefined
if (count($retryCount) && $routing != false) {
$this->_publishMessage($payload, $queueName);
}
public function publishRetryMessage(array $payload, string $baseQueueName, $retryCount, $workerMessage)
{
$retryQ = $this->AMQPConnection->getRetryQueueName($baseQueueName);
$headers = null;
if(!is_null($retryCount)) {
// add a message header information
$headers = new AMQPTable([
'x-death' => [
[
'count' => $retryCount,
'exchange' => AMQPConnection::ALCHEMY_EXCHANGE,
'queue' => $routing,
'routing-keys' => $routing,
'queue' => $baseQueueName,
'routing-keys' => $baseQueueName,
'reason' => 'rejected', // rejected is sended like nack
'time' => new \DateTime('now', new \DateTimeZone('UTC'))
'time' => new DateTime('now', new DateTimeZone('UTC'))
]
],
'worker-message' => $workerMessage
]);
}
$this->_publishMessage($payload, $retryQ, $headers);
}
public function publishDelayedMessage(array $payload, string $baseQueueName)
{
$delayedQ = $this->AMQPConnection->getDelayedQueueName($baseQueueName);
$this->_publishMessage($payload, $delayedQ);
}
public function publishFailedMessage(array $payload, AMQPTable $headers, $baseQueueName)
{
$FailedQ = $this->AMQPConnection->getFailedQueueName($baseQueueName);
$msg = new AMQPMessage(json_encode($payload));
$msg->set('application_headers', $headers);
$channel = $this->AMQPConnection->setQueue($FailedQ);
if ($channel == null) {
$this->pushLog("Can't connect to rabbit, check configuration!", "error");
return ;
}
$channel->basic_publish($msg, AMQPConnection::RETRY_ALCHEMY_EXCHANGE, $FailedQ);
$this->_publishMessage($payload, $FailedQ, $headers);
}
private function _publishMessage(array $payload, $queueName, $headers = null)
{
// add published timestamp to all message payload
$payload['payload']['published'] = time();
$msg = new AMQPMessage(json_encode($payload));
if (!is_null($headers)) {
// add a message header information
$msg->set('application_headers', $headers);
}
$channel = $this->serverConnection->setQueue($queueName);
if ($channel == null) {
if (is_null( ($channel = $this->AMQPConnection->setQueue($queueName)) )) {
$this->pushLog("Can't connect to rabbit, check configuration!", "error");
return true;
}
$exchange = in_array($queueName, AMQPConnection::$defaultQueues) ? AMQPConnection::ALCHEMY_EXCHANGE : AMQPConnection::RETRY_ALCHEMY_EXCHANGE;
$exchange = $this->AMQPConnection->getExchange($queueName); // in_array($queueName, AMQPConnection::$defaultQueues) ? AMQPConnection::ALCHEMY_EXCHANGE : AMQPConnection::RETRY_ALCHEMY_EXCHANGE;
$channel->basic_publish($msg, $exchange, $queueName);
return true;
@@ -139,16 +130,16 @@ class MessagePublisher
$payload = [
'message_type' => $type,
'payload' => [
'initTimestamp' => new \DateTime('now', new \DateTimeZone('UTC'))
'initTimestamp' => new DateTime('now', new DateTimeZone('UTC'))
]
];
$this->publishMessage($payload, AMQPConnection::$defaultQueues[$type]);
$this->publishMessage($payload, $type);
}
public function connectionClose()
{
$this->serverConnection->connectionClose();
$this->AMQPConnection->connectionClose();
}
/**
@@ -163,18 +154,4 @@ class MessagePublisher
call_user_func(array($this->logger, $method), $message, $context);
}
public function publishFailedMessage(array $payload, AMQPTable $headers, $queueName)
{
$msg = new AMQPMessage(json_encode($payload));
$msg->set('application_headers', $headers);
$channel = $this->serverConnection->setQueue($queueName);
if ($channel == null) {
$this->pushLog("Can't connect to rabbit, check configuration!", "error");
return ;
}
$channel->basic_publish($msg, AMQPConnection::RETRY_ALCHEMY_EXCHANGE, $queueName);
}
}

View File

@@ -24,6 +24,6 @@ class WebhookPublisher implements WebhookPublisherInterface
]
];
$this->messagePublisher->publishMessage($payload, MessagePublisher::WEBHOOK_QUEUE);
$this->messagePublisher->publishMessage($payload, MessagePublisher::WEBHOOK_TYPE);
}
}

View File

@@ -33,7 +33,7 @@ class AssetsIngestSubscriber implements EventSubscriberInterface
'payload' => array_merge($event->getData(), ['type' => WorkerRunningJob::TYPE_PUSH])
];
$this->messagePublisher->publishMessage($payload, MessagePublisher::ASSETS_INGEST_QUEUE);
$this->messagePublisher->publishMessage($payload, MessagePublisher::ASSETS_INGEST_TYPE);
}
public function onAssetsCreationFailure(AssetsCreationFailureEvent $event)
@@ -43,9 +43,9 @@ class AssetsIngestSubscriber implements EventSubscriberInterface
'payload' => $event->getPayload()
];
$this->messagePublisher->publishMessage(
$this->messagePublisher->publishRetryMessage(
$payload,
MessagePublisher::RETRY_ASSETS_INGEST_QUEUE,
MessagePublisher::ASSETS_INGEST_TYPE,
$event->getCount(),
$event->getWorkerMessage()
);
@@ -84,9 +84,9 @@ class AssetsIngestSubscriber implements EventSubscriberInterface
}
}
$this->messagePublisher->publishMessage(
$this->messagePublisher->publishRetryMessage(
$payload,
MessagePublisher::RETRY_CREATE_RECORD_QUEUE,
MessagePublisher::CREATE_RECORD_TYPE, // todo
$event->getCount(),
$event->getWorkerMessage()
);

View File

@@ -32,7 +32,7 @@ class ExportSubscriber implements EventSubscriberInterface
]
];
$this->messagePublisher->publishMessage($payload, MessagePublisher::EXPORT_QUEUE);
$this->messagePublisher->publishMessage($payload, MessagePublisher::EXPORT_MAIL_TYPE);
}
public function onExportMailFailure(ExportMailFailureEvent $event)
@@ -47,9 +47,9 @@ class ExportSubscriber implements EventSubscriberInterface
]
];
$this->messagePublisher->publishMessage(
$this->messagePublisher->publishRetryMessage(
$payload,
MessagePublisher::RETRY_EXPORT_QUEUE,
MessagePublisher::EXPORT_MAIL_TYPE,
$event->getCount(),
$event->getWorkerMessage()
);
@@ -66,7 +66,7 @@ class ExportSubscriber implements EventSubscriberInterface
$this->messagePublisher->publishMessage(
$payload,
MessagePublisher::FTP_QUEUE
MessagePublisher::FTP_TYPE
);
}

View File

@@ -36,7 +36,7 @@ class ExposeSubscriber implements EventSubscriberInterface
]
];
$this->messagePublisher->publishMessage($payload, MessagePublisher::EXPOSE_UPLOAD_QUEUE);
$this->messagePublisher->publishMessage($payload, MessagePublisher::EXPOSE_UPLOAD_TYPE);
}
}

View File

@@ -5,7 +5,6 @@ namespace Alchemy\Phrasea\WorkerManager\Subscriber;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Core\Event\Record\DeletedEvent;
use Alchemy\Phrasea\Core\Event\Record\DeleteEvent;
use Alchemy\Phrasea\Core\Event\Record\MetadataChangedEvent;
use Alchemy\Phrasea\Core\Event\Record\RecordEvent;
use Alchemy\Phrasea\Core\Event\Record\RecordEvents;
use Alchemy\Phrasea\Core\Event\Record\SubdefinitionCreateEvent;
@@ -22,6 +21,8 @@ use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher;
use Alchemy\Phrasea\WorkerManager\Worker\CreateRecordWorker;
use Alchemy\Phrasea\WorkerManager\Worker\Factory\WorkerFactoryInterface;
use Alchemy\Phrasea\WorkerManager\Worker\Resolver\TypeBasedWorkerResolver;
use databox;
use Exception;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class RecordSubscriber implements EventSubscriberInterface
@@ -67,7 +68,7 @@ class RecordSubscriber implements EventSubscriberInterface
]
];
$this->messagePublisher->publishMessage($payload, MessagePublisher::SUBDEF_QUEUE);
$this->messagePublisher->publishMessage($payload, MessagePublisher::SUBDEF_CREATION_TYPE);
}
}
}
@@ -87,7 +88,7 @@ class RecordSubscriber implements EventSubscriberInterface
]
];
$this->messagePublisher->publishMessage($payload, MessagePublisher::DELETE_RECORD_QUEUE);
$this->messagePublisher->publishMessage($payload, MessagePublisher::DELETE_RECORD_TYPE);
}
public function onSubdefinitionCreationFailure(SubdefinitionCreationFailureEvent $event)
@@ -123,14 +124,14 @@ class RecordSubscriber implements EventSubscriberInterface
$em->persist($workerRunningJob);
$em->flush();
$em->commit();
} catch (\Exception $e) {
} catch (Exception $e) {
$em->rollback();
}
}
$this->messagePublisher->publishMessage(
$this->messagePublisher->publishRetryMessage(
$payload,
MessagePublisher::RETRY_SUBDEF_QUEUE,
MessagePublisher::SUBDEF_CREATION_TYPE,
$event->getCount(),
$event->getWorkerMessage()
);
@@ -170,18 +171,19 @@ class RecordSubscriber implements EventSubscriberInterface
];
if ($subdef->is_physically_present()) {
$this->messagePublisher->publishMessage($payload, MessagePublisher::METADATAS_QUEUE);
} else {
$logMessage = sprintf("Subdef %s is not physically present! to be passed in the %s ! payload >>> %s",
$this->messagePublisher->publishMessage($payload, MessagePublisher::WRITE_METADATAS_TYPE);
}
else {
$logMessage = sprintf('Subdef "%s" is not physically present! to be passed in the retry q of "%s" ! payload >>> %s',
$subdef->get_name(),
MessagePublisher::RETRY_METADATAS_QUEUE,
MessagePublisher::WRITE_METADATAS_TYPE,
json_encode($payload)
);
$this->messagePublisher->pushLog($logMessage);
$this->messagePublisher->publishMessage(
$this->messagePublisher->publishRetryMessage(
$payload,
MessagePublisher::RETRY_METADATAS_QUEUE,
MessagePublisher::WRITE_METADATAS_TYPE,
2,
'Subdef is not physically present!'
);
@@ -215,10 +217,10 @@ class RecordSubscriber implements EventSubscriberInterface
]
];
$logMessage = sprintf("Subdef %s write meta failed, error : %s ! to be passed in the %s ! payload >>> %s",
$logMessage = sprintf('Subdef "%s" write meta failed, error : "%s" ! to be passed in the retry q of "%s" ! payload >>> %s',
$event->getSubdefName(),
$event->getWorkerMessage(),
MessagePublisher::RETRY_METADATAS_QUEUE,
MessagePublisher::WRITE_METADATAS_TYPE,
json_encode($payload)
);
$this->messagePublisher->pushLog($logMessage);
@@ -243,14 +245,14 @@ class RecordSubscriber implements EventSubscriberInterface
$em->persist($workerRunningJob);
$em->flush();
$em->commit();
} catch (\Exception $e) {
} catch (Exception $e) {
$em->rollback();
}
}
$this->messagePublisher->publishMessage(
$this->messagePublisher->publishRetryMessage(
$payload,
MessagePublisher::RETRY_METADATAS_QUEUE,
MessagePublisher::WRITE_METADATAS_TYPE,
$event->getCount(),
$event->getWorkerMessage()
);
@@ -276,7 +278,7 @@ class RecordSubscriber implements EventSubscriberInterface
]
];
$this->messagePublisher->publishMessage($payload, MessagePublisher::METADATAS_QUEUE);
$this->messagePublisher->publishMessage($payload, MessagePublisher::WRITE_METADATAS_TYPE);
}
}
@@ -294,19 +296,27 @@ class RecordSubscriber implements EventSubscriberInterface
]
];
$this->messagePublisher->publishMessage($payload, MessagePublisher::RECORD_EDIT_QUEUE);
$this->messagePublisher->publishMessage($payload, MessagePublisher::RECORD_EDIT_TYPE);
}
public static function getSubscribedEvents()
{
return [
/** @uses onRecordCreated */
RecordEvents::CREATED => 'onRecordCreated',
/** @uses onSubdefinitionCreate */
RecordEvents::SUBDEFINITION_CREATE => 'onSubdefinitionCreate',
/** @uses onDelete */
RecordEvents::DELETE => 'onDelete',
/** @uses onSubdefinitionCreationFailure */
WorkerEvents::SUBDEFINITION_CREATION_FAILURE => 'onSubdefinitionCreationFailure',
/** @uses onRecordsWriteMeta */
WorkerEvents::RECORDS_WRITE_META => 'onRecordsWriteMeta',
/** @uses onStoryCreateCover */
WorkerEvents::STORY_CREATE_COVER => 'onStoryCreateCover',
/** @uses onSubdefinitionWritemeta */
WorkerEvents::SUBDEFINITION_WRITE_META => 'onSubdefinitionWritemeta',
/** @uses onRecordEditInWorker */
WorkerEvents::RECORD_EDIT_IN_WORKER => 'onRecordEditInWorker'
];
}
@@ -322,12 +332,12 @@ class RecordSubscriber implements EventSubscriberInterface
}
/**
* @param \databox $databox
* @param databox $databox
* @param string $subdefType
* @param string $subdefName
* @return bool
*/
private function isSubdefMetadataUpdateRequired(\databox $databox, $subdefType, $subdefName)
private function isSubdefMetadataUpdateRequired(databox $databox, $subdefType, $subdefName)
{
if ($databox->get_subdef_structure()->hasSubdef($subdefType, $subdefName)) {
return $databox->get_subdef_structure()->get_subdef($subdefType, $subdefName)->isMetadataUpdateRequired();

View File

@@ -40,7 +40,7 @@ class SearchengineSubscriber implements EventSubscriberInterface
]
];
$this->messagePublisher->publishMessage($payload, MessagePublisher::POPULATE_INDEX_QUEUE);
$this->messagePublisher->publishMessage($payload, MessagePublisher::POPULATE_INDEX_TYPE);
}
}
@@ -83,9 +83,9 @@ class SearchengineSubscriber implements EventSubscriberInterface
}
}
$this->messagePublisher->publishMessage(
$this->messagePublisher->publishRetryMessage(
$payload,
MessagePublisher::RETRY_POPULATE_INDEX_QUEUE,
MessagePublisher::POPULATE_INDEX_TYPE,
$event->getCount(),
$event->getWorkerMessage()
);

View File

@@ -65,7 +65,7 @@ class SubtitleSubscriber implements EventSubscriberInterface
'payload' => $data
];
$this->messagePublisher->publishMessage($payload, MessagePublisher::MAIN_QUEUE);
$this->messagePublisher->publishMessage($payload, MessagePublisher::MAIN_QUEUE_TYPE);
} catch (\Exception $e) {
$em->rollback();
}

View File

@@ -29,9 +29,9 @@ class WebhookSubscriber implements EventSubscriberInterface
]
];
$this->messagePublisher->publishMessage(
$this->messagePublisher->publishRetryMessage(
$payload,
MessagePublisher::RETRY_WEBHOOK_QUEUE,
MessagePublisher::WEBHOOK_TYPE,
$event->getCount(),
$event->getWorkerMessage()
);

View File

@@ -2,8 +2,8 @@
namespace Alchemy\Phrasea\WorkerManager\Worker;
use Alchemy\Phrasea\Application\Helper\EntityManagerAware;
use Alchemy\Phrasea\Application as PhraseaApplication;
use Alchemy\Phrasea\Application\Helper\EntityManagerAware;
use Alchemy\Phrasea\Model\Entities\StoryWZ;
use Alchemy\Phrasea\Model\Entities\WorkerRunningJob;
use Alchemy\Phrasea\Model\Repositories\UserRepository;
@@ -80,7 +80,7 @@ class AssetsIngestWorker implements WorkerInterface
'commit_id' => $payload['commit_id']
];
$this->messagePublisher->publishMessage($createRecordMessage, MessagePublisher::CREATE_RECORD_QUEUE);
$this->messagePublisher->publishMessage($createRecordMessage, MessagePublisher::CREATE_RECORD_TYPE);
/** @var WorkerRunningJob $workerRunningJob */
$workerRunningJob = $this->repoWorkerJob->findOneBy([

View File

@@ -5,7 +5,6 @@ namespace Alchemy\Phrasea\WorkerManager\Worker;
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Application\Helper\NotifierAware;
use Alchemy\Phrasea\Core\LazyLocator;
use Alchemy\Phrasea\Exception\InvalidArgumentException;
use Alchemy\Phrasea\Model\Entities\FtpExport;
use Alchemy\Phrasea\Model\Entities\FtpExportElement;
use Alchemy\Phrasea\Model\Entities\WorkerRunningJob;
@@ -371,9 +370,9 @@ class FtpWorker implements WorkerInterface
'payload' => $payload
];
$this->app['alchemy_worker.message.publisher']->publishMessage(
$this->getMessagePublisher()->publishRetryMessage(
$fullPayload,
MessagePublisher::RETRY_FTP_QUEUE,
MessagePublisher::FTP_TYPE,
$count,
$workerMessage
);
@@ -503,4 +502,13 @@ class FtpWorker implements WorkerInterface
{
return $this->app['repo.ftp-exports'];
}
/**
* @return MessagePublisher
*/
private function getMessagePublisher()
{
return $this->app['alchemy_worker.message.publisher'];
}
}

View File

@@ -29,7 +29,7 @@ class MainQueueWorker implements WorkerInterface
switch ($payload['type']) {
case MessagePublisher::SUBTITLE_TYPE:
$queue = MessagePublisher::SUBTITLE_QUEUE;
$queue = MessagePublisher::SUBTITLE_TYPE;
$messageType = $payload['type'];
unset($payload['type']);

View File

@@ -82,7 +82,7 @@ class PullAssetsWorker implements WorkerInterface
]
];
$this->messagePublisher->publishMessage($payload, MessagePublisher::ASSETS_INGEST_QUEUE);
$this->messagePublisher->publishMessage($payload, MessagePublisher::ASSETS_INGEST_TYPE);
}
}

View File

@@ -75,7 +75,7 @@ class SubdefCreationWorker implements WorkerInterface
if (!$canCreateSubdef) {
// the file is in used to write meta
$this->messagePublisher->publishMessage($message, MessagePublisher::DELAYED_SUBDEF_QUEUE);
$this->messagePublisher->publishDelayedMessage($message, MessagePublisher::SUBDEF_CREATION_TYPE);
return ;
}
@@ -178,7 +178,10 @@ class SubdefCreationWorker implements WorkerInterface
// checking ended
// order to write meta for the subdef if needed
$this->dispatcher->dispatch(WorkerEvents::SUBDEFINITION_WRITE_META, new SubdefinitionWritemetaEvent($record, $payload['subdefName']));
$this->dispatcher->dispatch(WorkerEvents::SUBDEFINITION_WRITE_META, new SubdefinitionWritemetaEvent(
$record,
$payload['subdefName'])
);
$this->subdefGenerator->setLogger($oldLogger);

View File

@@ -170,7 +170,11 @@ class WebhookWorker implements WorkerInterface
$this->messagePublisher->pushLog($workerMessage);
// count = 0 mean do not retry because no api application defined
$this->dispatch(WorkerEvents::WEBHOOK_DELIVER_FAILURE, new WebhookDeliverFailureEvent($webhookevent->getId(), $workerMessage, 0));
$this->dispatch(WorkerEvents::WEBHOOK_DELIVER_FAILURE, new WebhookDeliverFailureEvent(
$webhookevent->getId(),
$workerMessage,
0)
);
return;
}
@@ -235,7 +239,7 @@ class WebhookWorker implements WorkerInterface
$this->messagePublisher->publishFailedMessage(
$payload,
new AMQPTable(['worker-message' => $e->getMessage()]),
MessagePublisher::FAILED_WEBHOOK_QUEUE
MessagePublisher::WEBHOOK_TYPE
);
}
}

View File

@@ -12,6 +12,8 @@ use Alchemy\Phrasea\Model\Repositories\WorkerRunningJobRepository;
use Alchemy\Phrasea\WorkerManager\Event\SubdefinitionWritemetaEvent;
use Alchemy\Phrasea\WorkerManager\Event\WorkerEvents;
use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher;
use DateTime;
use Exception;
use Monolog\Logger;
use PHPExiftool\Driver\Metadata\Metadata;
use PHPExiftool\Driver\Metadata\MetadataBag;
@@ -21,6 +23,7 @@ use PHPExiftool\Driver\Value\Multi;
use PHPExiftool\Exception\TagUnknown;
use PHPExiftool\Writer;
use Psr\Log\LoggerInterface;
use record_adapter;
class WriteMetadatasWorker implements WorkerInterface
{
@@ -73,7 +76,7 @@ class WriteMetadatasWorker implements WorkerInterface
if (!$canWriteMeta) {
// the file is in used to generate subdef
$this->messagePublisher->publishMessage($message, MessagePublisher::DELAYED_METADATAS_QUEUE);
$this->messagePublisher->publishDelayedMessage($message, MessagePublisher::WRITE_METADATAS_TYPE);
return ;
}
@@ -113,7 +116,7 @@ class WriteMetadatasWorker implements WorkerInterface
$em->beginTransaction();
try {
$date = new \DateTime();
$date = new DateTime();
$workerRunningJob = new WorkerRunningJob();
$workerRunningJob
->setDataboxId($databoxId)
@@ -129,14 +132,17 @@ class WriteMetadatasWorker implements WorkerInterface
$em->flush();
$em->commit();
} catch (\Exception $e) {
} catch (Exception $e) {
$em->rollback();
$this->logger->error("Error persisting WorkerRunningJob !");
return ;
}
}
try {
$subdef = $record->get_subdef($payload['subdefName']);
} catch (\Exception $e) {
} catch (Exception $e) {
$workerMessage = "Exception catched when try to get subdef " .$payload['subdefName']. " from DB for the recordID: " .$recordId;
$this->logger->error($workerMessage);
@@ -221,7 +227,7 @@ class WriteMetadatasWorker implements WorkerInterface
try {
$value = self::fixDate($value); // will return NULL if the date is not valid
}
catch (\Exception $e) {
catch (Exception $e) {
$value = null; // do NOT write back to iptc
}
}
@@ -230,7 +236,7 @@ class WriteMetadatasWorker implements WorkerInterface
$value = new Mono($value);
}
}
} catch(\Exception $e) {
} catch(Exception $e) {
// the field is not set in the record, erase it
if ($fieldStructure->is_multi()) {
$value = new Multi(array(''));
@@ -260,8 +266,8 @@ class WriteMetadatasWorker implements WorkerInterface
$this->writer->write($subdef->getRealPath(), $metadata);
$this->messagePublisher->pushLog(sprintf('meta written for sbasid=%1$d - recordid=%2$d (%3$s)', $databox->get_sbas_id(), $recordId, $subdef->get_name() ));
} catch (\Exception $e) {
$workerMessage = sprintf('meta NOT written for sbasid=%1$d - recordid=%2$d (%3$s) because "%s"', $databox->get_sbas_id(), $recordId, $subdef->get_name() , $e->getMessage());
} catch (Exception $e) {
$workerMessage = sprintf('meta NOT written for sbasid=%1$d - recordid=%2$d (%3$s) because "%4$s"', $databox->get_sbas_id(), $recordId, $subdef->get_name() , $e->getMessage());
$this->logger->error($workerMessage);
$count = isset($payload['count']) ? $payload['count'] + 1 : 2 ;
@@ -297,11 +303,11 @@ class WriteMetadatasWorker implements WorkerInterface
$em->beginTransaction();
try {
$workerRunningJob->setStatus(WorkerRunningJob::FINISHED);
$workerRunningJob->setFinished(new \DateTime('now'));
$workerRunningJob->setFinished(new DateTime('now'));
$em->persist($workerRunningJob);
$em->flush();
$em->commit();
} catch (\Exception $e) {
} catch (Exception $e) {
$em->rollback();
}
@@ -314,7 +320,7 @@ class WriteMetadatasWorker implements WorkerInterface
return str_replace("\0", "", $value);
}
private function updateJeton(\record_adapter $record)
private function updateJeton(record_adapter $record)
{
$connection = $record->getDatabox()->get_connection();
@@ -344,16 +350,16 @@ class WriteMetadatasWorker implements WorkerInterface
$a = explode(';', preg_replace('/\D+/', ';', trim($value)));
switch (count($a)) {
case 3: // yyyy;mm;dd
$date = new \DateTime($a[0] . '-' . $a[1] . '-' . $a[2]);
$date = new DateTime($a[0] . '-' . $a[1] . '-' . $a[2]);
$date = $date->format('Y-m-d H:i:s');
break;
case 6: // yyyy;mm;dd;hh;mm;ss
$date = new \DateTime($a[0] . '-' . $a[1] . '-' . $a[2] . ' ' . $a[3] . ':' . $a[4] . ':' . $a[5]);
$date = new DateTime($a[0] . '-' . $a[1] . '-' . $a[2] . ' ' . $a[3] . ':' . $a[4] . ':' . $a[5]);
$date = $date->format('Y-m-d H:i:s');
break;
}
}
catch (\Exception $e) {
catch (Exception $e) {
$date = null;
}

View File

@@ -0,0 +1,119 @@
<?php
use Alchemy\Phrasea\Application;
use Alchemy\Phrasea\Core\Configuration\PropertyAccess;
use Alchemy\Phrasea\WorkerManager\Queue\MessagePublisher;
class patch_413_PHRAS_3282 implements patchInterface
{
const OLDQ2NEWQ_ttl_retry = [
'assetsIngest' => MessagePublisher::ASSETS_INGEST_TYPE,
'createRecord' => MessagePublisher::CREATE_RECORD_TYPE,
'deleteRecord' => MessagePublisher::DELETE_RECORD_TYPE,
'exportMail' => MessagePublisher::EXPORT_MAIL_TYPE,
'exposeUpload' => MessagePublisher::EXPOSE_UPLOAD_TYPE,
'ftp' => MessagePublisher::FTP_TYPE,
'populateIndex' => MessagePublisher::POPULATE_INDEX_TYPE,
'pullAssets' => MessagePublisher::PULL_ASSETS_TYPE,
'recordEdit' => MessagePublisher::RECORD_EDIT_TYPE,
'subdefCreation' => MessagePublisher::SUBDEF_CREATION_TYPE,
'validationReminder' => MessagePublisher::VALIDATION_REMINDER_TYPE,
'writeMetadatas' => MessagePublisher::WRITE_METADATAS_TYPE,
'webhook' => MessagePublisher::WEBHOOK_TYPE,
];
const OLDQ2NEWQ_ttl_delayed = [
'delayedSubdef' => MessagePublisher::SUBDEF_CREATION_TYPE,
'delayedWriteMeta' => MessagePublisher::WRITE_METADATAS_TYPE,
];
/** @var string */
private $release = '4.1.3';
/** @var array */
private $concern = [base::APPLICATION_BOX, base::DATA_BOX];
/**
* {@inheritdoc}
*/
public function get_release()
{
return $this->release;
}
/**
* {@inheritdoc}
*/
public function getDoctrineMigrations()
{
return [];
}
/**
* {@inheritdoc}
*/
public function require_all_upgrades()
{
return false;
}
/**
* {@inheritdoc}
*/
public function concern()
{
return $this->concern;
}
/**
* {@inheritdoc}
*/
public function apply(base $base, Application $app)
{
if ($base->get_base_type() === base::DATA_BOX) {
$this->patch_databox($base, $app);
}
elseif ($base->get_base_type() === base::APPLICATION_BOX) {
$this->patch_appbox($base, $app);
}
return true;
}
private function patch_databox(base $databox, Application $app)
{
}
private function patch_appbox(base $databox, Application $app)
{
/** @var PropertyAccess $conf */
$conf = $app['conf'];
// --------------------------------------------
// PHRAS-3282_refacto-some-code-on-workers_MASTER
// patch workers settings
// --------------------------------------------
foreach(self::OLDQ2NEWQ_ttl_retry as $old=>$new) {
if(($v = $conf->get(['workers', 'retry_queue', $old], null)) !== null) {
$conf->set(['workers', 'queues', $new, 'ttl_retry'], $v);
}
}
foreach(self::OLDQ2NEWQ_ttl_delayed as $old=>$new) {
if(($v = $conf->get(['workers', 'retry_queue', $old], null)) !== null) {
$conf->set(['workers', 'queues', $new, 'ttl_delayed'], $v);
}
}
if(($v = $conf->get(['workers', 'pull_assets', 'pullInterval'], null)) !== null) {
$conf->set(['workers', 'queues', MessagePublisher::PULL_ASSETS_TYPE, 'ttl_retry'], $v * 1000);
}
if(($v = $conf->get(['workers', 'validationReminder', 'interval'], null)) !== null) {
$conf->set(['workers', 'queues', MessagePublisher::VALIDATION_REMINDER_TYPE, 'ttl_retry'], $v * 1000);
}
$conf->remove(['workers', 'retry_queue']);
$conf->remove(['workers', 'pull_assets']);
$conf->remove(['workers', 'validationReminder']);
}
}

View File

@@ -270,8 +270,7 @@ class record_preview extends record_adapter
$this->title .= parent::get_title($options);
break;
case "BASK":
$this->title .= $this->name . ' - ' . parent::get_title($options)
. ' (' . $this->getNumber() . '/' . $this->total . ') ';
$this->title .= $this->name . ' (' . $this->getNumber() . '/' . $this->total . ') - ' . parent::get_title($options);
break;
case "REG":
$title = parent::get_title($options);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2020-12-16T08:29:44Z" source-language="en" target-language="de" datatype="plaintext" original="not.available">
<file date="2021-01-13T14:26:33Z" source-language="en" target-language="de" datatype="plaintext" original="not.available">
<header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2020-12-16T08:30:32Z" source-language="en" target-language="en" datatype="plaintext" original="not.available">
<file date="2021-01-13T14:27:16Z" source-language="en" target-language="en" datatype="plaintext" original="not.available">
<header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2020-12-16T08:31:21Z" source-language="en" target-language="fr" datatype="plaintext" original="not.available">
<file date="2021-01-13T14:28:04Z" source-language="en" target-language="fr" datatype="plaintext" original="not.available">
<header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2020-12-16T08:32:13Z" source-language="en" target-language="nl" datatype="plaintext" original="not.available">
<file date="2021-01-13T14:28:55Z" source-language="en" target-language="nl" datatype="plaintext" original="not.available">
<header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note>

View File

@@ -10,7 +10,7 @@
{{ "admin::workermanager:tab:configuration: title" | trans }}
</a>
</li>
<li class="worker-info active" role="presentation">
<li class="worker-info" role="presentation">
<a href="#worker-info" aria-controls="worker-info" role="tab" data-toggle="tab" data-url="/admin/worker-manager/info">
{{ 'admin::workermanager:tab:workerinfo: title' |trans }}
</a>
@@ -56,9 +56,7 @@
<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane fade" id="worker-configuration"></div>
<div role="tabpanel" class="tab-pane fade in active" id="worker-info">
{% include "admin/worker-manager/worker_info.html.twig" %}
</div>
<div role="tabpanel" class="tab-pane fade" id="worker-info"></div>
<div role="tabpanel" class="tab-pane fade" id="worker-searchengine"></div>
<div role="tabpanel" class="tab-pane fade" id="worker-pull-assets"></div>
<div role="tabpanel" class="tab-pane fade" id="worker-ftp"></div>
@@ -101,6 +99,10 @@
});
}
});
var sel = '#configurationTabs li.{{ _fragment }} a';
$(sel).click();
</script>
{% else %}
<h1 class="alert alert-danger">

View File

@@ -1,58 +1,38 @@
{% form_theme form 'form_table_layout.html.twig' %}
{% form_theme form.queues 'bootstrap_3_horizontal_layout.html.twig' %}
<h1>{{ 'admin::workermanager:tab:workerconfig: title' |trans }}</h1>
<p class="alert alert-danger">
{{ 'admin::workermanager:tab:workerconfig: warning' |trans }}
</p>
<p>{{ 'admin::workermanager:tab:workerconfig: Set up the delay between two attempts per queue! (if not set, default 10000 millisecond)' |trans }}</p>
{{ form_start(form, {'action': path('worker_admin_configuration')}) }}
<div class="control-group">
{{ form_row(form.assetsIngest) }}
</div>
<div class="control-group">
{{ form_row(form.createRecord) }}
</div>
<div class="control-group">
{{ form_row(form.subdefCreation) }}
</div>
<div class="control-group">
{{ form_row(form.writeMetadatas) }}
</div>
<div class="control-group">
{{ form_row(form.webhook) }}
</div>
<div class="control-group">
{{ form_row(form.exportMail) }}
</div>
<div class="control-group">
{{ form_row(form.populateIndex) }}
</div>
<div class="control-group">
{{ form_row(form.ftp) }}
</div>
<h3> {{ 'admin::workermanager:tab:workerconfig: Config Worker queue delayed' |trans }}</h3>
<p> {{ 'admin::workermanager:tab:workerconfig: if not set ,default 5000 millisecond' |trans }}</p>
<div class="control-group">
{{ form_row(form.delayedSubdef) }}
</div>
<div class="control-group">
{{ form_row(form.delayedWriteMeta) }}
</div>
<div class="control-group">
<input type="submit" class="btn btn-primary" value={{ "admin::workermanager:tab:workerconfig:Apply in queue"|trans }} />
</div>
<table>
{% for child in form.queues.children %}
<tr>
<td colspan="4">
<hr/>
{{ form_label(child) }}
{{ form_errors(child) }}
</td>
</tr>
<tr>
{% for child2 in child.children %}
<td>
{{ form_row(child2) }}
</td>
{% endfor %}
{{ form_widget(child) }}
</tr>
{% endfor %}
</table>
<br/>
<br/>
{{ form_widget(form) }}
{{ form_end(form) }}
<br/>

View File

@@ -1,53 +1,40 @@
<h1>{{ 'admin::workermanager:tab:pullassets: description' |trans }}</h1>
{{ form_start(form, {'action': path('worker_admin_pullAssets')}) }}
{{ form_widget(form) }}
<br/>
<br/>
<br/>
<div class="control-group">
{{ form_row(form.UploaderApiBaseUri) }}
{% if(running) %}
<button type="submit" value="stop" class="btn btn-danger stop-pull-assets">{{ 'Stop' | trans }}</button>
{% else %}
<button type="submit" value="start" class="btn btn-primary start-pull-assets">{{ "Start"|trans }}</button>
{% endif %}
</div>
<div class="control-group">
{{ form_row(form.clientSecret) }}
</div>
<div class="control-group">
{{ form_row(form.clientId) }}
</div>
<div class="control-group">
{{ form_row(form.pullInterval) }}
</div>
<div class="control-group">
<button class="btn btn-primary start-pull-mode">{{ "admin::workermanager:tab:pullassets: Initialize pull assets"|trans }}</button>
<button class="btn btn-danger stop-pull-mode">{{ 'admin::workermanager:tab:pullassets: Stop pull assets' | trans }}</button>
</div>
{{ form_end(form) }}
<script type="text/javascript">
$("#worker-pull-assets").on('click', '.stop-pull-mode', function(e) {
$("#worker-pull-assets").on('click', 'BUTTON[type=submit]', function(e, z) {
e.preventDefault();
if (confirm("Warning! You are about to stop pull mode!")) {
$.ajax({
type: "POST",
url: "/admin/worker-manager/purge-queue",
dataType: 'json',
data : {
queueName : "pull-queue"
},
success: function (data) {
console.log(data);
return false;
var button = $(e.target);
var form = button.closest("FORM");
$("INPUT.act", form).val(button.val()); // "save", "start" or "stop"
var ok = true;
{% if(running) %}
if(button.val() === 'save') {
// saving will empty the queue, "stopping"
ok = confirm("Warning! Changing the settings will stop pulling assets!")
}
{% endif %}
if(button.val() === 'stop') {
// saving will empty the queue, "stopping"
ok = confirm("Warning! You are about to stop pulling assets!")
}
if(ok) {
form.submit();
}
});
}
});
$("#worker-pull-assets").on('click', '.start-pull-mode', function(e) {
e.preventDefault();
$('form[name="worker_pullAssets"]').submit();
});
</script>

View File

@@ -12,6 +12,7 @@
<th>{{ 'admin::workermanager:tab:queueMonitor: Message count' |trans }}</th>
<th>{{ 'admin::workermanager:tab:queueMonitor: Consumer count' |trans }}</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody class="queue-list">
@@ -19,12 +20,15 @@
{% endif %}
{% for queueStatus in queuesStatus %}
<tr>
<th>{{ queueStatus.queueName }}</th>
<td>{{ queueStatus.queueName }}</td>
<td>{{ queueStatus.messageCount }}</td>
<td>{{ queueStatus.consumerCount }}</td>
<td>
<button class="btn btn-danger btn-mini purge-queue" data-queue-name="{{ queueStatus.queueName }}">{{ 'admin::workermanager:tab:queueMonitor: Purge Queue' | trans }}</button>
</td>
<td>
<button class="btn btn-danger btn-mini delete-queue" data-queue-name="{{ queueStatus.queueName }}">{{ 'admin::workermanager:tab:queueMonitor: Delete Queue' | trans }}</button>
</td>
</tr>
{% endfor %}
@@ -60,5 +64,21 @@
}
});
$("#worker-queue-monitor").on('click', '.delete-queue', function() {
if (confirm("Warning! Are you sure? Messages cannot be recovered after deleting queue.")) {
$.ajax({
type: "POST",
url: "/admin/worker-manager/delete-queue",
dataType: 'json',
data : {
queueName : $(this).attr("data-queue-name")
},
success: function (data) {
$("#refresh-monitor").trigger("click");
}
});
}
});
</script>
{% endif %}

View File

@@ -1,45 +1,41 @@
<h1>{{ 'admin::workermanager:tab:Reminder: description' |trans }}</h1>
<form name="worker_validation_reminder" method="post" action="/admin/worker-manager/validation-reminder">
{{ form_start(form, {'action': path('worker_admin_validationReminder')}) }}
{{ form_widget(form) }}
<br/>
<br/>
<br/>
<div class="control-group">
<div>
<label for="worker_reminder_interval" class="required">{{ "admin::workermanager:tab:Reminder: Interval in second"|trans }}</label>
<input type="text" id="worker_reminder_interval" name="worker_reminder_interval" required="required" value="{{ interval }}">
{% if(running) %}
<button type="submit" value="stop" class="btn btn-danger stop-validation-reminder">{{ 'Stop' | trans }}</button>
{% else %}
<button type="submit" value="start" class="btn btn-primary start-validation-reminder">{{ "Start"|trans }}</button>
{% endif %}
</div>
</div>
<div class="control-group">
<button class="btn btn-primary start-validation-reminder">{{ "admin::workermanager:tab:Reminder: Start"|trans }}</button>
<button class="btn btn-danger stop-validation-reminder">{{ 'admin::workermanager:tab:Reminder: Stop' | trans }}</button>
</div>
</form>
{{ form_end(form) }}
<script type="text/javascript">
$("#worker-reminder").on('click', '.stop-validation-reminder', function(e) {
$("#worker-reminder").on('click', 'BUTTON[type=submit]', function(e, z) {
e.preventDefault();
if (confirm("Warning! You are about to stop validation Reminder!")) {
$.ajax({
type: "POST",
url: "/admin/worker-manager/purge-queue",
dataType: 'json',
data : {
queueName : "validationReminder-queue"
},
success: function (data) {
$('#tree li.selected a').trigger('click');
console.log(data);
var button = $(e.target);
var form = button.closest("FORM");
$("INPUT.act", form).val(button.val()); // "save", "start" or "stop"
return false;
var ok = true;
{% if(running) %}
if(button.val() === 'save') {
// saving will empty the queue, "stopping"
ok = confirm("Warning! Changing the settings will stop validation Reminder!")
}
{% endif %}
if(button.val() === 'stop') {
// saving will empty the queue, "stopping"
ok = confirm("Warning! You are about to stop validation Reminder!")
}
if(ok) {
form.submit();
}
});
}
});
$("#worker-reminder").on('click', '.start-validation-reminder', function(e) {
e.preventDefault();
$('form[name="worker_validation_reminder"]').submit();
});
</script>

View File

@@ -76,11 +76,6 @@
<tr>
<td>
<div class="context-menu context-menu-theme-vista">
<div title="" class="context-menu-item">
<div class="context-menu-item-inner export-record-action"
data-kind="basket" data-id="{{basket.getId()}}">{{ 'action::exporter' | trans }}
</div>
</div>
{% if app.getAclForUser(app.getAuthenticatedUser()).has_right(constant('\\ACL::CANMODIFRECORD')) %}
<div title="" class="context-menu-item menu3-custom-item">
<div data-kind="basket" data-id="{{basket.getId()}}"
@@ -174,15 +169,6 @@
<tr>
<td>
<div class="context-menu context-menu-theme-flat">
<div title="" class="context-menu-item">
<div class="context-menu-item-inner export-record-action"
data-kind="basket" data-id="{{basket.getId()}}">
<a href="#">
<img src="/assets/common/images/icons/export-shadow.png" style="width: 22px;" alt="{{ 'action::exporter' | trans }}">
<span>{{ 'action::exporter' | trans }}</span>
</a>
</div>
</div>
<div title="" class="context-menu-item">
<div class="context-menu-item-inner">
<a href="{{ path('lightbox_compare', { 'basket' : basket.getId() }) }}" target="_blank">
@@ -279,15 +265,6 @@
<tr>
<td>
<div class="context-menu context-menu-theme-flat">
<div title="" class="context-menu-item">
<div class="context-menu-item-inner export-record-action"
data-kind="record" data-id="{{story.getRecord(app).get_serialize_key()}}">
<a href="#">
<img src="/assets/common/images/icons/export-shadow.png" style="width: 22px;" alt="{{ 'action::exporter' | trans }}">
<span>{{ 'action::exporter' | trans }}</span>
</a>
</div>
</div>
{% if app.getAclForUser(app.getAuthenticatedUser()).has_right(constant('\\ACL::CANMODIFRECORD')) %}
<div title="" class="context-menu-item menu3-custom-item">
<div class="context-menu-item-inner edit-record-action"

View File

@@ -9,7 +9,7 @@
{% if record.is_from_basket() %}
<a sbas="{{record.get_sbas_id()}}" id="PREV_BASKDEL_{{record.get_serialize_key}}"
class="WorkZoneElementRemover record-remove-from-basket-action"
data-context="reg_train_basket"
data-context="reg_train_basket" style="display: contents;height: 24px;width: 24px;position: relative;bottom: 0px;"
href="{{ path('prod_baskets_basket_element_remove', { 'basket' : record.get_container().getId(), 'basket_element_id' : record.get_original_item().getId()}) }}">
<img src="/assets/common/images/icons/delete.png" height="16" width="16" class="btn-image" title="{{ 'Remove from basket' | trans }}">
@@ -17,7 +17,7 @@
{% endif %}
<div class="record-print-action" data-kind="record" data-id="{{record.get_sbas_id()}}_{{record.get_record_id()}}">
<img src="/assets/common/images/icons/print_history.png" height="16" width="16" class="btn-image" title="'{{ 'action : print' | trans }}">
<img src="/assets/common/images/icons/print_history.png" height="16" width="16" class="btn-image" title="{{ 'action : print' | trans }}">
</div>
{% if app.getAclForUser(app.getAuthenticatedUser()).has_right_on_base(record.get_base_id(), constant('\\ACL::CANDWNLDHD')) or app.getAclForUser(app.getAuthenticatedUser()).has_right_on_base(record.get_base_id(), constant('\\ACL::CANDWNLDPREVIEW')) %}