diff --git a/WebContent/WEB-INF/jsp/events.jsp b/WebContent/WEB-INF/jsp/events.jsp index 919977eb39..59361b56f3 100644 --- a/WebContent/WEB-INF/jsp/events.jsp +++ b/WebContent/WEB-INF/jsp/events.jsp @@ -225,6 +225,7 @@ + diff --git a/WebContent/WEB-INF/jsp/include/customEditor.jsp b/WebContent/WEB-INF/jsp/include/customEditor.jsp index 41910679d4..12d5b2b90a 100644 --- a/WebContent/WEB-INF/jsp/include/customEditor.jsp +++ b/WebContent/WEB-INF/jsp/include/customEditor.jsp @@ -66,6 +66,12 @@ + + + + + + @@ -103,6 +109,10 @@ $set("customEditorAlarmListInactivityColumn",comp.hideInactivityColumn); $set("customEditorAlarmListAckColumn",comp.hideAckColumn); + if('${isEventAssignEnabled}' !== '') { + $set("customEditorAlarmListAssigneeColumn",comp.hideAssigneeColumn); + } + } else if(comp.typeName == "yourCustomComponent") { } @@ -120,13 +130,14 @@ this.save = function() { //hideContextualMessages("graphicRendererEditorPopup"); + let customEditorAlarmListAssigneeColumn = '${isEventAssignEnabled}' !== '' ? $get("customEditorAlarmListAssigneeColumn") : false; if (customEditor.typeName == "alarmlist") ViewDwr.saveAlarmListComponent(customEditor.componentId, $get("customEditorAlarmListMinAlarmLevel"), $get("customEditorAlarmListMaxListSize"), $get("customEditorAlarmListWidth"),$get("customEditorAlarmListIdColumn"), $get("customEditorAlarmListAlarmLevelColumn"),$get("customEditorAlarmListTimestampColumn"), $get("customEditorAlarmListInactivityColumn"),$get("customEditorAlarmListAckColumn"), - viewId, customEditor.saveCB); + viewId, customEditorAlarmListAssigneeColumn, customEditor.saveCB); else if (customEditor.typeName == "yourCustomComponent") alert('save your custom component component!'); diff --git a/WebContent/WEB-INF/jsp/systemSettings.jsp b/WebContent/WEB-INF/jsp/systemSettings.jsp index 0f61a006c9..53d4de2016 100644 --- a/WebContent/WEB-INF/jsp/systemSettings.jsp +++ b/WebContent/WEB-INF/jsp/systemSettings.jsp @@ -112,6 +112,7 @@ setDisabled($(""), !settings.); setDisabled($(""), !settings. || !settings.); + $set("", settings.); var sel = $(""); sel.options[sel.options.length] = new Option("${lang.value}", "${lang.key}"); @@ -308,6 +309,7 @@ $get(""), $get(""), $get(""), + $get(""), function(response) { stopImageFader("saveMiscSettingsImg"); if (response.hasMessages) @@ -961,6 +963,12 @@ + + + + "/>" type="checkbox" /> + + diff --git a/WebContent/WEB-INF/snippet/alarmList.jsp b/WebContent/WEB-INF/snippet/alarmList.jsp index 122b4e8527..099653184f 100644 --- a/WebContent/WEB-INF/snippet/alarmList.jsp +++ b/WebContent/WEB-INF/snippet/alarmList.jsp @@ -24,6 +24,7 @@ +   @@ -33,7 +34,8 @@ ${sst:time(event.activeTimestamp)} -

+

+ @@ -48,6 +50,14 @@ + + + + ${sst:time(event.assigneeTimestamp)} + + + +
diff --git a/WebContent/WEB-INF/snippet/eventList.jsp b/WebContent/WEB-INF/snippet/eventList.jsp index ffc358ac05..c0525036b8 100644 --- a/WebContent/WEB-INF/snippet/eventList.jsp +++ b/WebContent/WEB-INF/snippet/eventList.jsp @@ -63,6 +63,7 @@ + @@ -103,71 +104,16 @@ + + + + ${sst:time(event.assigneeTimestamp)} + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (unknown event source id ${event.eventType.eventSourceId}) - diff --git a/WebContent/WEB-INF/spring-security.xml b/WebContent/WEB-INF/spring-security.xml index 43b2e159d6..557a6c77b0 100644 --- a/WebContent/WEB-INF/spring-security.xml +++ b/WebContent/WEB-INF/spring-security.xml @@ -181,6 +181,8 @@ + + @@ -392,6 +394,9 @@ + + + diff --git a/WebContent/WEB-INF/tags/alarmAck.tag b/WebContent/WEB-INF/tags/alarmAck.tag index 4216d3ed2b..c036ee8e80 100644 --- a/WebContent/WEB-INF/tags/alarmAck.tag +++ b/WebContent/WEB-INF/tags/alarmAck.tag @@ -23,6 +23,8 @@ + + @@ -33,4 +35,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (unknown event source id ${event.eventType.eventSourceId}) + + \ No newline at end of file diff --git a/WebContent/resources/common.js b/WebContent/resources/common.js index dd839b02e6..2d48f8c7a3 100644 --- a/WebContent/resources/common.js +++ b/WebContent/resources/common.js @@ -1261,6 +1261,28 @@ function OnListWebsocketStats() { } ); } +function assignEvent(eventId) { + MiscDwr.assignEvent(eventId, function(response) { + if(response) { + hide("assigneeImg"+ eventId); + var imgNode = $("assigneeImg"+ eventId); + updateImg(imgNode, "images/user_delete.png", mango.i18n["events.unassign"], true, "inline"); + imgNode.onclick = function() {}; + } + }); +} + +function unassignEvent(eventId) { + MiscDwr.unassignEvent(eventId, function(response) { + if(response) { + hide("unassigneeImg"+ eventId); + var imgNode = $("unassigneeImg"+ eventId); + updateImg(imgNode, "images/user_add.png", mango.i18n["events.assign"], true, "inline"); + imgNode.onclick = function() {}; + } + }); +} + function isEmpty(value) { return !value || (typeof value === "string" && value.trim() === ""); } \ No newline at end of file diff --git a/scadalts-ui/src/locales/en.json b/scadalts-ui/src/locales/en.json index 226ab01998..dc0451867f 100644 --- a/scadalts-ui/src/locales/en.json +++ b/scadalts-ui/src/locales/en.json @@ -1054,6 +1054,17 @@ "userDetails.view.enableFullScreen": "Enable full screen mode", "userDetails.view.hideShortcutDisableFullScreen": "Hide shortcut to disable full screen", "script.runScript": "Run script", + "systemsettings.event.pendingLimit": "Event Pending Limit", + "systemsettings.event.pendingCacheEnabled": "Enabled Event Pending Cache", + "systemsettings.workitems.reporting.enabled": "Work items reporting enabled", + "systemsettings.workitems.reporting.itemspersecond.enabled": "Items per second reporting enabled", + "systemsettings.workitems.reporting.itemspersecond.limit": "Items per second reporting limit", + "systemsettings.threads.name.additional.length": "Thread name length", + "systemsettings.webresource.uploads.path": "Uploaded images save path", + "systemsettings.webresource.graphics.path": "Custom Graphics images path", + "systemsettings.webresource.uploads.path.wrong":"Uploaded images save path must end with 'uploads' or 'uploads{0}' ", + "systemsettings.webresource.graphics.path.wrong": "Custom Graphics images path must end with 'graphics' or 'graphics{0}' ", + "systemsettings.misc.eventAssignEnabled": "Event Assign enabled", "systemsettings.misc.eventPendingCacheEnabled": "Event Pending Limit", "systemsettings.misc.eventPendingLimit": "Enabled Event Pending Cache", "systemsettings.misc.workItemsReportingEnabled": "Work items reporting enabled", diff --git a/scadalts-ui/src/views/System/SystemSettings/MiscSettingsComponent.vue b/scadalts-ui/src/views/System/SystemSettings/MiscSettingsComponent.vue index 97b422e213..c43e1f6321 100644 --- a/scadalts-ui/src/views/System/SystemSettings/MiscSettingsComponent.vue +++ b/scadalts-ui/src/views/System/SystemSettings/MiscSettingsComponent.vue @@ -109,6 +109,13 @@ :rules="[validateUploadsPath]" > + + + diff --git a/src/br/org/scadabr/api/dao/MangoDaoImpl.java b/src/br/org/scadabr/api/dao/MangoDaoImpl.java index 2e79b56767..065dddef6d 100644 --- a/src/br/org/scadabr/api/dao/MangoDaoImpl.java +++ b/src/br/org/scadabr/api/dao/MangoDaoImpl.java @@ -567,7 +567,7 @@ public EventNotification ackEvent(int eventId) throws ScadaBRAPIException { if (eventInstance != null) { // alternateAckSource? - REM - eventService.ackEvent(eventId, 1, 0, 0); + eventService.ackEvent(eventInstance, 1, this.user, 0); // new EventDao().ackEvent(eventId, 1); return APIUtils.toEventNotification(eventInstance); } else { diff --git a/src/br/org/scadabr/view/component/AlarmListComponent.java b/src/br/org/scadabr/view/component/AlarmListComponent.java index b60cb78fc8..7ba5fddb6c 100644 --- a/src/br/org/scadabr/view/component/AlarmListComponent.java +++ b/src/br/org/scadabr/view/component/AlarmListComponent.java @@ -18,6 +18,7 @@ import com.serotonin.mango.view.ImplDefinition; import com.serotonin.mango.web.dwr.BaseDwr; import org.scada_lts.mango.service.EventService; +import org.scada_lts.mango.service.SystemSettingsService; @JsonRemoteEntity public class AlarmListComponent extends CustomComponent { @@ -37,6 +38,7 @@ public class AlarmListComponent extends CustomComponent { private boolean hideTimestampColumn = false; private boolean hideInactivityColumn = true; private boolean hideAckColumn = false; + private boolean hideAssigneeColumn = false; public AlarmListComponent() {} @@ -50,15 +52,18 @@ private AlarmListComponent(AlarmListComponent alarmListComponent) { this.hideTimestampColumn = alarmListComponent.isHideTimestampColumn(); this.hideInactivityColumn = alarmListComponent.isHideInactivityColumn(); this.hideAckColumn = alarmListComponent.isHideAckColumn(); + this.hideAssigneeColumn = alarmListComponent.isHideAssigneeColumn(); } @Override public String generateContent() { + SystemSettingsService systemSettingsService = new SystemSettingsService(); Map model = new HashMap(); WebContext webContext = WebContextFactory.get(); HttpServletRequest request = webContext.getHttpServletRequest(); List toViewEvents = new EventService().getPendingEventsAlarmLevelMin(Common .getUser().getId(), minAlarmLevel, maxListSize); + boolean eventAssignEnabled = systemSettingsService.isEventAssignEnabled(); model.put("nome", "marlon"); model.put("events",toViewEvents); @@ -68,6 +73,8 @@ public String generateContent() { model.put("hideTimestampColumn", hideTimestampColumn); model.put("hideInactivityColumn", hideInactivityColumn); model.put("hideAckColumn", hideAckColumn); + model.put("hideAssigneeColumn", hideAssigneeColumn); + model.put("isEventAssignEnabled", eventAssignEnabled); String content = BaseDwr.generateContent(request, "alarmList.jsp", model); @@ -137,13 +144,21 @@ public void setHideInactivityColumn(boolean hideInactivityColumn) { this.hideInactivityColumn = hideInactivityColumn; } + public boolean isHideAssigneeColumn() { + return hideAssigneeColumn; + } + + public void setHideAssigneeColumn(boolean hideAssigneeColumn) { + this.hideAssigneeColumn = hideAssigneeColumn; + } + @Override public ViewComponent copy() { return new AlarmListComponent(this); } private static final long serialVersionUID = -1; - private static final int version = 1; + private static final int version = 2; private void writeObject(ObjectOutputStream out) throws IOException { out.writeInt(version); @@ -155,6 +170,7 @@ private void writeObject(ObjectOutputStream out) throws IOException { out.writeBoolean(hideTimestampColumn); out.writeBoolean(hideInactivityColumn); out.writeBoolean(hideAckColumn); + out.writeBoolean(hideAssigneeColumn); } @@ -171,6 +187,16 @@ private void readObject(ObjectInputStream in) throws IOException { hideTimestampColumn = in.readBoolean(); hideInactivityColumn = in.readBoolean(); hideAckColumn = in.readBoolean(); + } else if (ver == 2) { + minAlarmLevel = in.readInt(); + maxListSize = in.readInt(); + width = in.readInt(); + hideIdColumn = in.readBoolean(); + hideAlarmLevelColumn = in.readBoolean(); + hideTimestampColumn = in.readBoolean(); + hideInactivityColumn = in.readBoolean(); + hideAckColumn = in.readBoolean(); + hideAssigneeColumn = in.readBoolean(); } } @@ -197,12 +223,12 @@ public boolean equals(Object o) { if (!(o instanceof AlarmListComponent)) return false; if (!super.equals(o)) return false; AlarmListComponent that = (AlarmListComponent) o; - return getMinAlarmLevel() == that.getMinAlarmLevel() && getMaxListSize() == that.getMaxListSize() && getWidth() == that.getWidth() && isHideIdColumn() == that.isHideIdColumn() && isHideAlarmLevelColumn() == that.isHideAlarmLevelColumn() && isHideTimestampColumn() == that.isHideTimestampColumn() && isHideInactivityColumn() == that.isHideInactivityColumn() && isHideAckColumn() == that.isHideAckColumn(); + return getMinAlarmLevel() == that.getMinAlarmLevel() && getMaxListSize() == that.getMaxListSize() && getWidth() == that.getWidth() && isHideIdColumn() == that.isHideIdColumn() && isHideAlarmLevelColumn() == that.isHideAlarmLevelColumn() && isHideTimestampColumn() == that.isHideTimestampColumn() && isHideInactivityColumn() == that.isHideInactivityColumn() && isHideAckColumn() == that.isHideAckColumn() && isHideAssigneeColumn() == that.isHideAssigneeColumn(); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), getMinAlarmLevel(), getMaxListSize(), getWidth(), isHideIdColumn(), isHideAlarmLevelColumn(), isHideTimestampColumn(), isHideInactivityColumn(), isHideAckColumn()); + return Objects.hash(super.hashCode(), getMinAlarmLevel(), getMaxListSize(), getWidth(), isHideIdColumn(), isHideAlarmLevelColumn(), isHideTimestampColumn(), isHideInactivityColumn(), isHideAckColumn(), isHideAssigneeColumn()); } @@ -217,6 +243,7 @@ public String toString() { ", hideTimestampColumn=" + hideTimestampColumn + ", hideInactivityColumn=" + hideInactivityColumn + ", hideAckColumn=" + hideAckColumn + - "} " + super.toString(); + ", hideAssigneeColumn=" + hideAssigneeColumn + + '}'; } } diff --git a/src/com/serotonin/mango/MangoContextListener.java b/src/com/serotonin/mango/MangoContextListener.java index ed8bf66eeb..fd44341f76 100644 --- a/src/com/serotonin/mango/MangoContextListener.java +++ b/src/com/serotonin/mango/MangoContextListener.java @@ -388,6 +388,14 @@ private void constantsInitialize(ServletContext ctx) { SystemEventType.TYPE_POINT_LINK_FAILURE); ctx.setAttribute("constants.SystemEventType.TYPE_PROCESS_FAILURE", SystemEventType.TYPE_PROCESS_FAILURE); + ctx.setAttribute("constants.SystemEventType.TYPE_SMS_SEND_FAILURE", + SystemEventType.TYPE_SMS_SEND_FAILURE); + ctx.setAttribute("constants.SystemEventType.TYPE_SCRIPT_HANDLER_FAILURE", + SystemEventType.TYPE_SCRIPT_HANDLER_FAILURE); + ctx.setAttribute("constants.SystemEventType.TYPE_ASSIGNED_EVENT", + SystemEventType.TYPE_ASSIGNED_EVENT); + ctx.setAttribute("constants.SystemEventType.TYPE_UNASSIGNED_EVENT", + SystemEventType.TYPE_UNASSIGNED_EVENT); ctx.setAttribute("constants.AuditEventType.TYPE_DATA_SOURCE", AuditEventType.TYPE_DATA_SOURCE); diff --git a/src/com/serotonin/mango/rt/EventManager.java b/src/com/serotonin/mango/rt/EventManager.java index 27392e49ee..77d74fdf40 100644 --- a/src/com/serotonin/mango/rt/EventManager.java +++ b/src/com/serotonin/mango/rt/EventManager.java @@ -42,7 +42,6 @@ import org.scada_lts.mango.service.UserService; import org.scada_lts.service.IHighestAlarmLevelService; import org.scada_lts.web.beans.ApplicationBeans; -import org.scada_lts.web.ws.model.WsEventMessage; import org.scada_lts.web.ws.services.UserEventServiceWebSocket; import java.util.*; @@ -139,7 +138,7 @@ public void raiseEvent(EventType type, long time, boolean rtnApplicable, eventConfirmForUsers.add(user); if(evt.getAlarmLevel() > AlarmLevels.NONE) - notifyEventUpdate(user, WsEventMessage.create(evt)); + notifyEventCreate(user, evt); } } @@ -158,9 +157,9 @@ public void raiseEvent(EventType type, long time, boolean rtnApplicable, User admin = userService.getUser("admin"); if(admin != null) { eventService.ackEvent( - evt.getId(), + evt, time, - admin.getId(), + admin, EventInstance.AlternateAcknowledgementSources.MAINTENANCE_MODE, false); // no signaling of AlarmLevel change for(User user: eventConfirmForUsers) { @@ -450,14 +449,15 @@ public void notifyAlarmTimestampChange(long alarmTimestamp) { } - public void resetHighestAlarmLevels() { - NotifyEventUtils.resetHighestAlarmLevels(highestAlarmLevelService, userService, userEventServiceWebSocket); + public void notifyEventReset() { + NotifyEventUtils.notifyEventReset(highestAlarmLevelService, userEventServiceWebSocket); } public int getHighestAlarmLevel(int userId) { return highestAlarmLevelService.getAlarmLevel(User.onlyId(userId)); } + @Deprecated(since = "2.8.0") public void notifyEventRaise(int eventId, int userId) { if(eventId != Common.NEW_ID) { EventInstance evt = eventService.getEvent(eventId); @@ -466,6 +466,15 @@ public void notifyEventRaise(int eventId, int userId) { } } + @Deprecated(since = "2.8.0") + public void notifyEventRaise(int eventId) { + if(eventId != Common.NEW_ID) { + for(int userId: ApplicationBeans.getLoggedUsersBean().getUserIds()) { + notifyEventRaise(eventId, userId); + } + } + } + public void notifyEventRaise(EventInstance evt, User user) { NotifyEventUtils.notifyEventRaise(highestAlarmLevelService, evt, user, userEventServiceWebSocket); } @@ -474,6 +483,7 @@ public void notifyEventAck(EventInstance evt, User user) { NotifyEventUtils.notifyEventAck(highestAlarmLevelService, evt, user, userEventServiceWebSocket); } + @Deprecated(since = "2.8.0") public void notifyEventAck(int eventId, User user) { if(eventId != Common.NEW_ID) { EventInstance evt = eventService.getEvent(eventId); @@ -481,10 +491,19 @@ public void notifyEventAck(int eventId, User user) { } } + @Deprecated(since = "2.8.0") public void notifyEventAck(int eventId) { if(eventId != Common.NEW_ID) { - for (User user : userService.getActiveUsers()) - notifyEventAck(eventId, user); + for (int userId : ApplicationBeans.getLoggedUsersBean().getUserIds()) + notifyEventAck(eventId, userService.getUser(userId)); + } + } + + @Deprecated(since = "2.8.0") + public void notifyEventAssignee(int eventId) { + if(eventId != Common.NEW_ID) { + for (int userId : ApplicationBeans.getLoggedUsersBean().getUserIds()) + notifyEventAck(eventId, userService.getUser(userId)); } } @@ -494,8 +513,9 @@ public void notifyEventRtn(EventInstance evt, User user) { public void notifyEventRtn(EventInstance event) { if(event.getId() != Common.NEW_ID) { - for (User user : userService.getActiveUsers()) + for (User user : ApplicationBeans.getLoggedUsersBean().getUsers()) { notifyEventRtn(event, user); + } } } @@ -503,6 +523,7 @@ public void notifyEventToggle(EventInstance evt, User user) { NotifyEventUtils.notifyEventToggle(highestAlarmLevelService, evt, user, userEventServiceWebSocket); } + @Deprecated(since = "2.8.0") public void notifyEventToggle(int eventId, int userId) { if(eventId != Common.NEW_ID) { EventInstance evt = eventService.getEvent(eventId); @@ -511,7 +532,31 @@ public void notifyEventToggle(int eventId, int userId) { } } - public void notifyEventUpdate(User user, WsEventMessage message) { - NotifyEventUtils.notifyEventUpdate(user, message, userEventServiceWebSocket); + public void notifyEventCreate(User user, EventInstance event) { + NotifyEventUtils.notifyEventCreate(event, user, userEventServiceWebSocket); + } + + public void notifyEventRaise(EventInstance event) { + if(event.getId() != Common.NEW_ID) { + for (User user : ApplicationBeans.getLoggedUsersBean().getUsers()) { + notifyEventRaise(event, user); + } + } + } + + public void notifyEventAck(EventInstance event) { + if(event.getId() != Common.NEW_ID) { + for (User user : ApplicationBeans.getLoggedUsersBean().getUsers()) { + notifyEventAck(event, user); + } + } + } + + public void notifyEventAssignee(EventInstance event) { + if(event.getId() != Common.NEW_ID) { + for (User user : ApplicationBeans.getLoggedUsersBean().getUsers()) { + notifyEventToggle(event, user); + } + } } } diff --git a/src/com/serotonin/mango/rt/event/EventInstance.java b/src/com/serotonin/mango/rt/event/EventInstance.java index a8cb1bda4b..c1c9650ec0 100644 --- a/src/com/serotonin/mango/rt/event/EventInstance.java +++ b/src/com/serotonin/mango/rt/event/EventInstance.java @@ -97,6 +97,9 @@ public interface AlternateAcknowledgementSources { private String acknowledgedByUsername; private int alternateAckSource; + private long assigneeTimestamp; + private String assigneeUsername; + // // // These fields are used only in the context of access by a particular user, providing state filled in from @@ -185,6 +188,14 @@ public LocalizableMessage getAckMessage() { return new LocalizableMessage("event.auto.acknowledge"); } + public LocalizableMessage getAssigneeMessage() { + if (isAssignee()) { + if (!StringUtils.isEmpty(assigneeUsername)) + return new LocalizableMessage("events.assigneeByUser", assigneeUsername); + } + return new LocalizableMessage("common.noMessage"); + } + public LocalizableMessage getExportAckMessage() { if (isAcknowledged()) { if (acknowledgedByUserId != 0 || !StringUtils.isEmpty(acknowledgedByUsername)) @@ -246,6 +257,10 @@ public boolean isAcknowledged() { return acknowledgedTimestamp > 0; } + public boolean isAssignee() { + return assigneeTimestamp > 0; + } + public long getActiveTimestamp() { return activeTimestamp; } @@ -350,6 +365,22 @@ public void setAlternateAckSource(int alternateAckSource) { this.alternateAckSource = alternateAckSource; } + public long getAssigneeTimestamp() { + return assigneeTimestamp; + } + + public void setAssigneeTimestamp(long assigneeTimestamp) { + this.assigneeTimestamp = assigneeTimestamp; + } + + public String getAssigneeUsername() { + return assigneeUsername; + } + + public void setAssigneeUsername(String assigneeUsername) { + this.assigneeUsername = assigneeUsername; + } + public Map getContext() { return context; } @@ -459,6 +490,8 @@ public EventInstance copyWithContext(Map context) { eventInstance.setHandlers(handlers); eventInstance.setSilenced(silenced); eventInstance.setUserNotified(userNotified); + eventInstance.setAssigneeTimestamp(assigneeTimestamp); + eventInstance.setAssigneeUsername(assigneeUsername); return eventInstance; } diff --git a/src/com/serotonin/mango/rt/event/type/SystemEventType.java b/src/com/serotonin/mango/rt/event/type/SystemEventType.java index 39f4019a58..754ea64dd5 100644 --- a/src/com/serotonin/mango/rt/event/type/SystemEventType.java +++ b/src/com/serotonin/mango/rt/event/type/SystemEventType.java @@ -60,6 +60,8 @@ public class SystemEventType extends EventType { public static final int TYPE_PROCESS_FAILURE = 10; public static final int TYPE_SCRIPT_HANDLER_FAILURE = 11; public static final int TYPE_SMS_SEND_FAILURE = 12; + public static final int TYPE_ASSIGNED_EVENT = 13; + public static final int TYPE_UNASSIGNED_EVENT = 14; public static final ExportCodes TYPE_CODES = new ExportCodes(); static { @@ -78,6 +80,8 @@ public class SystemEventType extends EventType { TYPE_CODES.addElement(TYPE_PROCESS_FAILURE, "PROCESS_FAILURE"); TYPE_CODES.addElement(TYPE_SCRIPT_HANDLER_FAILURE, "SCRIPT_HANDLER_FAILURE"); TYPE_CODES.addElement(TYPE_SMS_SEND_FAILURE, "SMS_SEND_FAILURE"); + TYPE_CODES.addElement(TYPE_ASSIGNED_EVENT, "ASSIGNED_EVENT"); + TYPE_CODES.addElement(TYPE_UNASSIGNED_EVENT, "UNASSIGNED_EVENT"); } private static List systemEventTypes; @@ -109,7 +113,11 @@ public static List getSystemEventTypes() { addEventTypeVO(TYPE_SCRIPT_HANDLER_FAILURE, "event.system.script", AlarmLevels.URGENT); addEventTypeVO(TYPE_SMS_SEND_FAILURE, "event.system.sms", - AlarmLevels.URGENT); + AlarmLevels.INFORMATION); + addEventTypeVO(TYPE_ASSIGNED_EVENT, "event.system.assigned", + AlarmLevels.INFORMATION); + addEventTypeVO(TYPE_UNASSIGNED_EVENT, "event.system.unassigned", + AlarmLevels.INFORMATION); } return systemEventTypes; } diff --git a/src/com/serotonin/mango/util/NotifyEventUtils.java b/src/com/serotonin/mango/util/NotifyEventUtils.java index 41f49c02c8..027f2d2eab 100644 --- a/src/com/serotonin/mango/util/NotifyEventUtils.java +++ b/src/com/serotonin/mango/util/NotifyEventUtils.java @@ -3,8 +3,8 @@ import com.serotonin.mango.rt.event.AlarmLevels; import com.serotonin.mango.rt.event.EventInstance; import com.serotonin.mango.vo.User; -import org.scada_lts.mango.adapter.MangoUser; import org.scada_lts.service.IHighestAlarmLevelService; +import org.scada_lts.web.beans.ApplicationBeans; import org.scada_lts.web.ws.model.WsEventMessage; import org.scada_lts.web.ws.services.UserEventServiceWebSocket; @@ -12,9 +12,12 @@ public final class NotifyEventUtils { private NotifyEventUtils() {} - public static void resetHighestAlarmLevels(IHighestAlarmLevelService highestAlarmLevelService, MangoUser userService, UserEventServiceWebSocket userEventService) { + public static void notifyEventReset(IHighestAlarmLevelService highestAlarmLevelService, UserEventServiceWebSocket userEventService) { highestAlarmLevelService.doResetAlarmLevels(userEventService::sendAlarmLevel); - notifyEventReset(userService, userEventService); + ApplicationBeans.Lazy.getLoggedUsersBean().ifPresent(loggedUsers -> { + for(User user: loggedUsers.getUsers()) + notifyEventUpdate(user, WsEventMessage.reset(), userEventService); + }); } public static void notifyEventRaise(IHighestAlarmLevelService highestAlarmLevelService, EventInstance evt, User user, UserEventServiceWebSocket userEventService) { @@ -44,12 +47,13 @@ public static void notifyEventAck(IHighestAlarmLevelService highestAlarmLevelSer } } - public static void notifyEventReset(MangoUser userService, UserEventServiceWebSocket userEventService) { - for(User user: userService.getActiveUsers()) - userEventService.sendEventUpdate(user, WsEventMessage.reset()); + public static void notifyEventCreate(EventInstance evt, User user, UserEventServiceWebSocket userEventService) { + if(evt.getAlarmLevel() > AlarmLevels.NONE) { + notifyEventUpdate(user, WsEventMessage.create(evt), userEventService); + } } - public static void notifyEventUpdate(User user, WsEventMessage message, UserEventServiceWebSocket userEventService) { + private static void notifyEventUpdate(User user, WsEventMessage message, UserEventServiceWebSocket userEventService) { userEventService.sendEventUpdate(user, message); } } diff --git a/src/com/serotonin/mango/vo/User.java b/src/com/serotonin/mango/vo/User.java index 9aa16e2b50..97416496f2 100644 --- a/src/com/serotonin/mango/vo/User.java +++ b/src/com/serotonin/mango/vo/User.java @@ -224,11 +224,13 @@ public void raiseRecursionFailureEvent() { @Override public void valueBound(HttpSessionBindingEvent event) { ApplicationBeans.Lazy.getLoggedUsersBean().ifPresent(loggedUsers -> loggedUsers.addUser(this, event.getSession())); + ApplicationBeans.Lazy.getHighestAlarmLevelServiceBean().ifPresent(highestAlarmLevelService -> highestAlarmLevelService.doResetAlarmLevels((a, b) -> {})); } @Override public void valueUnbound(HttpSessionBindingEvent event) { ApplicationBeans.Lazy.getLoggedUsersBean().ifPresent(loggedUsers -> loggedUsers.removeUser(this, event.getSession())); + ApplicationBeans.Lazy.getHighestAlarmLevelServiceBean().ifPresent(highestAlarmLevelService -> highestAlarmLevelService.doResetAlarmLevels((a, b) -> {})); } // Convenience method for JSPs diff --git a/src/com/serotonin/mango/web/dwr/BaseDwr.java b/src/com/serotonin/mango/web/dwr/BaseDwr.java index f7e7bad1d3..8c3e1bb8b1 100644 --- a/src/com/serotonin/mango/web/dwr/BaseDwr.java +++ b/src/com/serotonin/mango/web/dwr/BaseDwr.java @@ -21,11 +21,6 @@ import java.util.*; import javax.servlet.http.HttpServletRequest; -import javax.servlet.jsp.jstl.core.Config; -import javax.servlet.jsp.jstl.fmt.LocalizationContext; - -import org.directwebremoting.WebContext; -import org.directwebremoting.WebContextFactory; import org.joda.time.DateTime; import org.joda.time.IllegalFieldValueException; @@ -54,9 +49,9 @@ import com.serotonin.web.content.ContentGenerator; import com.serotonin.web.i18n.I18NUtils; import com.serotonin.web.i18n.LocalizableMessage; -import org.scada_lts.dao.pointvalues.PointValueAdnnotationsDAO; import org.scada_lts.mango.adapter.MangoEvent; import org.scada_lts.mango.service.EventService; +import org.scada_lts.mango.service.SystemSettingsService; abstract public class BaseDwr { public static final String MODEL_ATTR_EVENTS = "events"; @@ -100,6 +95,10 @@ protected PointValueTime prepareBasePointState(String componentId, BasePointStat model.put("pointValue", pointValue); } + User user = Common.getUser(); + if(user != null) + model.put(Common.SESSION_USER, user); + return pointValue; } @@ -170,6 +169,8 @@ protected void setChart(DataPointVO point, BasePointState state, HttpServletRequ protected void setMessages(BasePointState state, HttpServletRequest request, String snippet, Map model) { + SystemSettingsService systemSettingsService = new SystemSettingsService(); + model.put("isEventAssignEnabled", systemSettingsService.isEventAssignEnabled()); state.setMessages(generateContent(request, snippet + ".jsp", model).trim()); } diff --git a/src/com/serotonin/mango/web/dwr/EventsDwr.java b/src/com/serotonin/mango/web/dwr/EventsDwr.java index f3fb0269e1..91a0ad487f 100644 --- a/src/com/serotonin/mango/web/dwr/EventsDwr.java +++ b/src/com/serotonin/mango/web/dwr/EventsDwr.java @@ -40,6 +40,7 @@ import com.serotonin.web.i18n.LocalizableMessage; import org.scada_lts.mango.adapter.MangoEvent; import org.scada_lts.mango.service.EventService; +import org.scada_lts.mango.service.SystemSettingsService; public class EventsDwr extends BaseDwr { private static final int PAGE_SIZE = 50; @@ -49,6 +50,7 @@ public class EventsDwr extends BaseDwr { public static final String STATUS_ACTIVE = "A"; public static final String STATUS_RTN = "R"; public static final String STATUS_NORTN = "N"; + public static final String STATUS_ASSIGNEE = "U"; public static final int DATE_RANGE_TYPE_NONE = 1; public static final int DATE_RANGE_TYPE_RELATIVE = 2; @@ -88,6 +90,9 @@ public DwrResponseI18n searchOld(int eventId, int eventSourceType, model.put("events", results); model.put("showControls", false); + SystemSettingsService systemSettingsService = new SystemSettingsService(); + model.put("isEventAssignEnabled", systemSettingsService.isEventAssignEnabled()); + response.addData("content", generateContent(request, "eventList.jsp", model)); response.addData("resultCount", new LocalizableMessage( @@ -182,6 +187,9 @@ status, alarmLevel, getKeywords(keywordStr), dateRange.getL1(), model.put("page", page); model.put("pendingEvents", false); + SystemSettingsService systemSettingsService = new SystemSettingsService(); + model.put("isEventAssignEnabled", systemSettingsService.isEventAssignEnabled()); + response.addData("content", generateContent(request, "eventList.jsp", model)); response.addData("resultCount", new LocalizableMessage( diff --git a/src/com/serotonin/mango/web/dwr/MiscDwr.java b/src/com/serotonin/mango/web/dwr/MiscDwr.java index 31ea7a737b..92b8e741dd 100644 --- a/src/com/serotonin/mango/web/dwr/MiscDwr.java +++ b/src/com/serotonin/mango/web/dwr/MiscDwr.java @@ -42,9 +42,7 @@ import org.directwebremoting.WebContextFactory; import org.scada_lts.dao.SystemSettingsDAO; import org.scada_lts.mango.adapter.MangoEvent; -import org.scada_lts.mango.service.EventService; -import org.scada_lts.mango.service.UserService; -import org.scada_lts.mango.service.ViewService; +import org.scada_lts.mango.service.*; import com.serotonin.io.StreamUtils; import com.serotonin.mango.Common; @@ -88,8 +86,9 @@ public DwrResponseI18n toggleSilence(int eventId) { User user = Common.getUser(); if (user != null) { - boolean result = new EventService() - .toggleSilence(eventId, user.getId()); + EventService eventService = new EventService(); + EventInstance event = eventService.getEvent(eventId); + boolean result = eventService.toggleSilence(event, user); resetLastAlarmLevelChange(); response.addData("silenced", result); } else @@ -100,14 +99,11 @@ public DwrResponseI18n toggleSilence(int eventId) { public DwrResponseI18n silenceAll() { - List silenced = new ArrayList(); + List silenced = new ArrayList<>(); User user = Common.getUser(); - MangoEvent eventService = new EventService(); - for (EventInstance evt : eventService.getPendingEvents(user.getId())) { - if (!evt.isSilenced()) { - eventService.toggleSilence(evt.getId(), user.getId()); - silenced.add(evt.getId()); - } + if (user != null) { + MangoEvent eventService = new EventService(); + silenced = eventService.silenceEvents(user); } resetLastAlarmLevelChange(); @@ -123,23 +119,46 @@ public int acknowledgeEvent(int eventId) { if (user != null) { EventInstance evt = eventService.getEvent(eventId); if(evt != null && !evt.isActive()) { - eventService.ackEvent(evt.getId(), System.currentTimeMillis(), - user.getId(), 0); + eventService.ackEvent(evt, System.currentTimeMillis(), user, 0); resetLastAlarmLevelChange(); } } return eventId; } + public boolean assignEvent(int eventId) { + User user = Common.getUser(); + MangoEvent eventService = new EventService(); + boolean result = false; + if (user != null) { + EventInstance evt = eventService.getEvent(eventId); + if(evt != null) { + result = eventService.assignEvent(evt, user); + resetLastAlarmLevelChange(); + } + } + return result; + } + + public boolean unassignEvent(int eventId) { + User user = Common.getUser(); + MangoEvent eventService = new EventService(); + boolean result = false; + if (user != null) { + EventInstance evt = eventService.getEvent(eventId); + if(evt != null) { + result = eventService.unassignEvent(evt, user); + resetLastAlarmLevelChange(); + } + } + return result; + } + public void acknowledgeAllPendingEvents() { User user = Common.getUser(); if (user != null) { MangoEvent eventService = new EventService(); - long now = System.currentTimeMillis(); - for (EventInstance evt : eventService.getPendingEvents(user.getId())) { - if(!evt.isActive()) - eventService.ackEvent(evt.getId(), now, user.getId(), 0); - } + eventService.ackEvents(user); resetLastAlarmLevelChange(); } } @@ -438,6 +457,9 @@ public Map doLongPoll(int pollSessionId) { model.put("events", eventService.getPendingEvents(user.getId())); model.put("pendingEvents", true); model.put("noContentWhenEmpty", true); + SystemSettingsService service = new SystemSettingsService(); + model.put("isEventAssignEnabled", service.isEventAssignEnabled()); + String currentContent = generateContent(httpRequest, "eventList.jsp", model); currentContent = StringUtils.trimWhitespace(currentContent); diff --git a/src/com/serotonin/mango/web/dwr/SystemSettingsDwr.java b/src/com/serotonin/mango/web/dwr/SystemSettingsDwr.java index a87f8b82bd..6aa6f9a802 100644 --- a/src/com/serotonin/mango/web/dwr/SystemSettingsDwr.java +++ b/src/com/serotonin/mango/web/dwr/SystemSettingsDwr.java @@ -177,6 +177,8 @@ public Map getSettings() { systemSettingsService.getMiscSettings().getWebResourceGraphicsPath()); settings.put(SystemSettingsDAO.WEB_RESOURCE_UPLOADS_PATH, systemSettingsService.getMiscSettings().getWebResourceUploadsPath()); + settings.put(SystemSettingsDAO.EVENT_ASSIGN_ENABLED, + systemSettingsService.getMiscSettings().isEventAssignEnabled()); return settings; } @@ -329,7 +331,8 @@ public DwrResponseI18n saveMiscSettings(int uiPerformance, String dataPointRtVal int eventPendingLimit, boolean eventPendingCacheEnabled, boolean workItemsReportingEnabled, boolean workItemsReportingItemsPerSecondEnabled, int workItemsReportingItemsPerSecondLimit, int threadsNameAdditionalLength, - String webResourceGraphicsPath, String webResourceUploadsPath) { + String webResourceGraphicsPath, String webResourceUploadsPath, + boolean eventAssignEnabled) { Permissions.ensureAdmin(); SystemSettingsDAO systemSettingsDAO = new SystemSettingsDAO(); DwrResponseI18n response = new DwrResponseI18n(); @@ -387,7 +390,8 @@ public DwrResponseI18n saveMiscSettings(int uiPerformance, String dataPointRtVal else { response.addContextualMessage(SystemSettingsDAO.WEB_RESOURCE_UPLOADS_PATH, "systemsettings.webresource.uploads.path.wrong", File.separator); } - + SystemSettingsService systemSettingsService = new SystemSettingsService(); + systemSettingsService.saveEventAssignEnabled(eventAssignEnabled); return response; } diff --git a/src/com/serotonin/mango/web/dwr/ViewDwr.java b/src/com/serotonin/mango/web/dwr/ViewDwr.java index f1dd722052..cb93dbe5bb 100644 --- a/src/com/serotonin/mango/web/dwr/ViewDwr.java +++ b/src/com/serotonin/mango/web/dwr/ViewDwr.java @@ -897,7 +897,10 @@ public DwrResponseI18n saveFlexComponent(String viewComponentId, int width, int } - public DwrResponseI18n saveAlarmListComponent(String viewComponentId, int minAlarmLevel, int maxListSize, int width, boolean hideIdColumn, boolean hideAlarmLevelColumn, boolean hideTimestampColumn, boolean hideInactivityColumn, boolean hideAckColumn, int viewId) { + public DwrResponseI18n saveAlarmListComponent(String viewComponentId, int minAlarmLevel, int maxListSize, int width, + boolean hideIdColumn, boolean hideAlarmLevelColumn, boolean hideTimestampColumn, + boolean hideInactivityColumn, boolean hideAckColumn, int viewId, + boolean hideAssigneeColumn) { DwrResponseI18n response = new DwrResponseI18n(); // Validate @@ -916,6 +919,7 @@ public DwrResponseI18n saveAlarmListComponent(String viewComponentId, int minAla c.setHideTimestampColumn(hideTimestampColumn); c.setHideInactivityColumn(hideInactivityColumn); c.setHideAckColumn(hideAckColumn); + c.setHideAssigneeColumn(hideAssigneeColumn); // resetPointComponent(c); } diff --git a/src/org/scada_lts/dao/HighestAlarmLevelDAO.java b/src/org/scada_lts/dao/HighestAlarmLevelDAO.java index e06b7b56d1..f88434f51a 100644 --- a/src/org/scada_lts/dao/HighestAlarmLevelDAO.java +++ b/src/org/scada_lts/dao/HighestAlarmLevelDAO.java @@ -23,6 +23,7 @@ public class HighestAlarmLevelDAO implements IHighestAlarmLevelDAO { + "join events e on u.eventId=e.id " + "where " + "(e.ackTs is null or e.ackTs = 0) " + + "and (e.assigneeTs is null or e.assigneeTs = 0) " + "and u.silenced='N' and " + COLUMN_NAME_USERID + "=? " + "and (e.rtnCause is null or e.rtnCause = 0) " + "and e.rtnApplicable = 'Y'"; @@ -37,6 +38,7 @@ public class HighestAlarmLevelDAO implements IHighestAlarmLevelDAO { + "join events e on u.eventId=e.id " + "where " + "(e.ackTs is null or e.ackTs = 0) " + + "and (e.assigneeTs is null or e.assigneeTs = 0) " + "and u.silenced='N' " + "and (e.rtnCause is null or e.rtnCause = 0) " + "and e.rtnApplicable = 'Y' " diff --git a/src/org/scada_lts/dao/PendingEventsDAO.java b/src/org/scada_lts/dao/PendingEventsDAO.java index 14f19b659c..6cebd167eb 100644 --- a/src/org/scada_lts/dao/PendingEventsDAO.java +++ b/src/org/scada_lts/dao/PendingEventsDAO.java @@ -61,6 +61,8 @@ public class PendingEventsDAO { private final static String COLUMN_NAME_EVENT_USERNAME = "username"; private final static String COLUMN_NAME_EVENT_ALTERNATE_ACK_SOURCE = "alternateAckSource"; private final static String COLUMN_NAME_EVENT_SILENCED = "silenced"; + private final static String COLUMN_NAME_EVENT_ASSIGNEE_TS = "assigneeTs"; + private final static String COLUMN_NAME_EVENT_ASSIGNEE_USERNAME = "assigneeUsername"; // @formatter:off private static final String SQL_EVENTS = "" @@ -80,7 +82,9 @@ public class PendingEventsDAO { + "e.ackUserId, " + "u.username, " + "e.alternateAckSource, " - + "ue.silenced " + + "ue.silenced, " + + "e.assigneeTs, " + + "e.assigneeUsername " + "from " + "events e " + "left join users u on e.ackUserId=u.id " @@ -163,6 +167,12 @@ private EventInstance mapToEvent(Map> comments, Resul } event.setEventComments(comments.get(event.getId())); + + long assigneeTs = rs.getLong(COLUMN_NAME_EVENT_ASSIGNEE_TS); + if (!rs.wasNull()) { + event.setAssigneeTimestamp(assigneeTs); + event.setAssigneeUsername(rs.getString(COLUMN_NAME_EVENT_ASSIGNEE_USERNAME)); + } return event; } diff --git a/src/org/scada_lts/dao/SystemSettingsDAO.java b/src/org/scada_lts/dao/SystemSettingsDAO.java index 9a6ff52fdd..8afb2748d8 100644 --- a/src/org/scada_lts/dao/SystemSettingsDAO.java +++ b/src/org/scada_lts/dao/SystemSettingsDAO.java @@ -20,7 +20,6 @@ import com.serotonin.InvalidArgumentException; import com.serotonin.ShouldNeverHappenException; import com.serotonin.mango.Common; -import com.serotonin.mango.rt.maint.work.WorkItemPriority; import com.serotonin.mango.vo.DataPointVO; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -164,6 +163,7 @@ public class SystemSettingsDAO { public static final String THREADS_NAME_ADDITIONAL_LENGTH = "threadsNameAdditionalLength"; public static final String WEB_RESOURCE_GRAPHICS_PATH = "webResourceGraphicsPath"; public static final String WEB_RESOURCE_UPLOADS_PATH = "webResourceUploadsPath"; + public static final String EVENT_ASSIGN_ENABLED = "eventAssignEnabled"; // @formatter:off private static final String SELECT_SETTING_VALUE_WHERE = "" @@ -413,6 +413,7 @@ public String getDatabaseSchemaVersion(String key, String defaultValue) { DEFAULT_VALUES.put(THREADS_NAME_ADDITIONAL_LENGTH, SystemSettingsUtils.getThreadsNameAdditionalLength()); DEFAULT_VALUES.put(WEB_RESOURCE_GRAPHICS_PATH, SystemSettingsUtils.getWebResourceGraphicsPath()); DEFAULT_VALUES.put(WEB_RESOURCE_UPLOADS_PATH, SystemSettingsUtils.getWebResourceUploadsPath()); + DEFAULT_VALUES.put(EVENT_ASSIGN_ENABLED, SystemSettingsUtils.isEventAssignEnabled()); } @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED, rollbackFor = SQLException.class) diff --git a/src/org/scada_lts/dao/event/EventDAO.java b/src/org/scada_lts/dao/event/EventDAO.java index 49f34a3707..9fd0d19578 100644 --- a/src/org/scada_lts/dao/event/EventDAO.java +++ b/src/org/scada_lts/dao/event/EventDAO.java @@ -29,10 +29,7 @@ import com.serotonin.mango.vo.User; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.scada_lts.dao.DAO; -import org.scada_lts.dao.GenericDaoCR; -import org.scada_lts.dao.IUserCommentDAO; -import org.scada_lts.dao.SerializationData; +import org.scada_lts.dao.*; import com.serotonin.mango.rt.event.type.AuditEventUtils; import org.scada_lts.utils.QueryUtils; import org.scada_lts.utils.SQLPageWithTotal; @@ -92,6 +89,9 @@ public class EventDAO implements GenericDaoCR { private static final String COLUMN_NAME_USER_COMMENT_COUNT = "comments"; private static final String COLUMN_NAME_EVENT_ID = "eventId"; private static final String COLUMN_NAME_USER_ID = "userId"; + + private static final String COLUMN_NAME_ASSIGNEE_TS = "assigneeTs"; + private static final String COLUMN_NAME_ASSIGNEE_USERNAME = "assigneeUsername"; //------------- User comments //TODO rewrite to another class @@ -135,7 +135,8 @@ public class EventDAO implements GenericDaoCR { "e." + COLUMN_NAME_ACT_TS + ", " + "e." + COLUMN_NAME_ALTERNATE_ACK_SOURCE + ", " + "dp."+ COLUMN_NAME_DATAPOINT_XID + ", " + - "ue."+ COLUMN_NAME_SILENCED + " "; + "ue."+ COLUMN_NAME_SILENCED + ", " + + "e."+ COLUMN_NAME_ASSIGNEE_TS +" "; // @formatter:off private static final String BASIC_EVENT_SELECT = "" @@ -154,7 +155,9 @@ public class EventDAO implements GenericDaoCR { + "e."+COLUMN_NAME_ACT_TS+", " + "e."+COLUMN_NAME_ACT_USER_ID+", " + "u."+COLUMN_NAME_USER_NAME+"," - + "e."+COLUMN_NAME_ALTERNATE_ACK_SOURCE+" " + + "e."+COLUMN_NAME_ALTERNATE_ACK_SOURCE+", " + + "e."+ COLUMN_NAME_ASSIGNEE_TS +", " + + "e."+ COLUMN_NAME_ASSIGNEE_USERNAME +" " + "from " + "events e " + "left join users u on e."+COLUMN_NAME_ACT_USER_ID+"=u.id "; @@ -174,7 +177,9 @@ public class EventDAO implements GenericDaoCR { + "e."+COLUMN_NAME_SHORT_MESSAGE+", " + "e."+COLUMN_NAME_ACT_TS+", " + "e."+COLUMN_NAME_ACT_USER_ID+", " - + "e."+COLUMN_NAME_ALTERNATE_ACK_SOURCE+" " + + "e."+COLUMN_NAME_ALTERNATE_ACK_SOURCE+", " + + "e."+ COLUMN_NAME_ASSIGNEE_TS +", " + + "e."+ COLUMN_NAME_ASSIGNEE_USERNAME +" " + "from " + "events e where " + COLUMN_NAME_ID + " " @@ -211,11 +216,11 @@ public class EventDAO implements GenericDaoCR { private static final String EVENT_UPDATE = "" + "update " - + "events set " - + COLUMN_NAME_RTN_TS+"=?," - + COLUMN_NAME_RTN_CAUSE+"=? " + + "events e set " + + "e."+COLUMN_NAME_RTN_TS+"=?," + + "e."+COLUMN_NAME_RTN_CAUSE+"=? " + "where " - + COLUMN_NAME_ID+"=?"; + + "e."+COLUMN_NAME_ID+"=?"; private static final String EVENT_SELECT_BASE_ON_ID = ""+ @@ -225,38 +230,55 @@ public class EventDAO implements GenericDaoCR { private static final String EVENT_ACT ="" + "update " - +"events set " - + COLUMN_NAME_ACT_TS+"=?, " - + COLUMN_NAME_ACT_USER_ID+"=?, " - + COLUMN_NAME_ALTERNATE_ACK_SOURCE+"=? " + +"events e set " + + "e."+COLUMN_NAME_ACT_TS+"=?, " + + "e."+COLUMN_NAME_ACT_USER_ID+"=?, " + + "e."+COLUMN_NAME_ALTERNATE_ACK_SOURCE+"=? " + "where " - + COLUMN_NAME_ID+"=? and " - + "("+COLUMN_NAME_ACT_TS+" is null or "+COLUMN_NAME_ACT_TS+" = 0) "; + + "e."+COLUMN_NAME_ID+"=? and " + + "("+"e."+COLUMN_NAME_ACT_TS+" is null or "+"e."+COLUMN_NAME_ACT_TS+" = 0) "; + + private static final String EVENT_ASSIGN_EVENT ="" + + "update " + +"events e set " + + "e."+COLUMN_NAME_ASSIGNEE_TS +"=?, " + + "e."+COLUMN_NAME_ASSIGNEE_USERNAME +"=? " + + "where " + + "e."+COLUMN_NAME_ID+"=? and " + + "("+ "e."+COLUMN_NAME_ASSIGNEE_TS +" is null or "+ "e."+COLUMN_NAME_ASSIGNEE_TS +" = 0) "; + + private static final String EVENT_UNASSIGN_EVENT ="" + + "update " + +"events e set " + + "e."+COLUMN_NAME_ASSIGNEE_TS +"=null, " + + "e."+COLUMN_NAME_ASSIGNEE_USERNAME +"=null " + + "where " + + "e."+COLUMN_NAME_ID+"=? "; private static final String EVENT_ACT_ALL ="" + "update " - +" events set " - + COLUMN_NAME_ACT_TS+"=?, " - + COLUMN_NAME_ACT_USER_ID+"=?, " - + COLUMN_NAME_ALTERNATE_ACK_SOURCE+"=? " + +" events e set " + + "e."+COLUMN_NAME_ACT_TS+"=?, " + + "e."+COLUMN_NAME_ACT_USER_ID+"=?, " + + "e."+COLUMN_NAME_ALTERNATE_ACK_SOURCE+"=? " + "where " - + "("+COLUMN_NAME_ACT_TS+" is null or "+COLUMN_NAME_ACT_TS+" = 0) "; + + "("+"e."+COLUMN_NAME_ACT_TS+" is null or "+"e."+COLUMN_NAME_ACT_TS+" = 0) "; private static final String EVENT_SILENCE_ALL ="" + "UPDATE " - + "userEvents SET " - + COLUMN_NAME_ALARM_SILENCED+"='Y' " - + "WHERE userId=? "; + + "userEvents ue SET " + + "ue."+COLUMN_NAME_ALARM_SILENCED+"='Y' " + + "WHERE ue." + COLUMN_NAME_USER_ID + "=? "; private static final String EVENT_ACT_IDS ="" + "update " - +"events set " - + COLUMN_NAME_ACT_TS+"=?, " - + COLUMN_NAME_ACT_USER_ID+"=?, " - + COLUMN_NAME_ALTERNATE_ACK_SOURCE+"=? " + +"events e set " + + "e."+COLUMN_NAME_ACT_TS+"=?, " + + "e."+COLUMN_NAME_ACT_USER_ID+"=?, " + + "e."+COLUMN_NAME_ALTERNATE_ACK_SOURCE+"=? " + "where " - + COLUMN_NAME_ID+" rlike ? and " - + "("+COLUMN_NAME_ACT_TS+" is null or "+COLUMN_NAME_ACT_TS+" = 0) "; + + "e."+COLUMN_NAME_ID+" rlike ? and " + + "("+"e."+COLUMN_NAME_ACT_TS+" is null or "+"e."+COLUMN_NAME_ACT_TS+" = 0) "; public static final String EVENT_FILTER_ACTIVE=" " +"e."+ COLUMN_NAME_RTN_APPLICABLE+"=? and (e."+ COLUMN_NAME_RTN_TS+" is null or e."+COLUMN_NAME_RTN_TS+"=0)"; @@ -278,7 +300,9 @@ public class EventDAO implements GenericDaoCR { + "e."+COLUMN_NAME_ACT_USER_ID+", " + "u."+COLUMN_NAME_USER_NAME+"," + "e."+COLUMN_NAME_ALTERNATE_ACK_SOURCE+", " - + "ue."+COLUMN_NAME_SILENCED+" " + + "ue."+COLUMN_NAME_SILENCED+", " + + "e."+ COLUMN_NAME_ASSIGNEE_TS +", " + + "e."+ COLUMN_NAME_ASSIGNEE_USERNAME +" " + "from " + "events e " + "left join users u on e."+COLUMN_NAME_ACT_USER_ID+"=u.id " @@ -328,10 +352,10 @@ public class EventDAO implements GenericDaoCR { +"order by uc."+COLUMN_NAME_TIME_STAMP; private static final String EVENT_DELETE_BEFORE= "" - +"delete from events " - + "where "+COLUMN_NAME_ACTIVE_TS+" { "e." + COLUMN_NAME_MESSAGE + ", " + "e." + COLUMN_NAME_ACT_TS + ", " + "u." + COLUMN_NAME_USER_NAME + ", " + - "e." + COLUMN_NAME_ALTERNATE_ACK_SOURCE + " " + + "e." + COLUMN_NAME_ALTERNATE_ACK_SOURCE+", " + + "e."+ COLUMN_NAME_ASSIGNEE_TS +" "+ "FROM events e " + "LEFT JOIN users u ON u.id=e.ackUserId " + "WHERE typeId=? AND typeRef1=? " + @@ -495,7 +520,8 @@ public class EventDAO implements GenericDaoCR { "e." + COLUMN_NAME_MESSAGE + ", " + "e." + COLUMN_NAME_ACT_TS + ", " + "u." + COLUMN_NAME_USER_NAME + ", " + - "e." + COLUMN_NAME_ALTERNATE_ACK_SOURCE + " " + + "e." + COLUMN_NAME_ALTERNATE_ACK_SOURCE + ", " + + "e." + COLUMN_NAME_ASSIGNEE_TS + " " + "FROM events e " + "LEFT JOIN users u ON u.id=e.ackUserId " + "WHERE typeId=? AND typeRef1=? " + @@ -516,6 +542,17 @@ public class EventDAO implements GenericDaoCR { private static final String STATUS_ACTIVE_CONDITION_SQL = "e.rtnApplicable='Y' and (e.rtnTs is null or e.rtnTs = 0)"; private static final String STATUS_RTN_CONDITION_SQL = "e.rtnApplicable='Y' and (e.rtnTs is not null and e.rtnTs <> 0)"; private static final String STATUS_NORTN_CONDITION_SQL = "e.rtnApplicable='N'"; + private static final String STATUS_ASSIGNEE_CONDITION_SQL = "(e."+ COLUMN_NAME_ASSIGNEE_TS+" is not null and e."+COLUMN_NAME_ASSIGNEE_TS+" <> 0)"; + + private static final String STATUS_NO_ACTIVE_CONDITION_SQL = "(e.rtnApplicable='N' or (e.rtnTs is not null and e.rtnTs <> 0))"; + + private static final String UNASSIGN_EVENT_ALL ="" + + "update " + +"events e set " + + "e." + COLUMN_NAME_ASSIGNEE_TS +"=null, " + + "e." + COLUMN_NAME_ASSIGNEE_USERNAME +"=null " + + "where " + + STATUS_ACTIVE_CONDITION_SQL; // @formatter:on @@ -595,6 +632,12 @@ public EventInstance mapRow(ResultSet rs, int rowNum) throws SQLException { event.setAcknowledgedByUsername(rs.getString(COLUMN_NAME_USER_NAME)); event.setAlternateAckSource(rs.getInt(COLUMN_NAME_ALTERNATE_ACK_SOURCE)); } + long assigneeTs = rs.getLong(COLUMN_NAME_ASSIGNEE_TS); + + if (!rs.wasNull()) { + event.setAssigneeTimestamp(assigneeTs); + event.setAssigneeUsername(rs.getString(COLUMN_NAME_ASSIGNEE_USERNAME)); + } return event; @@ -809,6 +852,8 @@ public SQLPageWithTotal findEvents( filterCondtions.add("e.rtnTs > 0"); } else if (EventsDwr.STATUS_NORTN.equals(query.getStatus())) { filterCondtions.add("e.rtnApplicable='N'"); + } else if (EventsDwr.STATUS_ASSIGNEE.equals(query.getStatus())) { + filterCondtions.add(STATUS_ASSIGNEE_CONDITION_SQL); } List userCommentKeywordConditions = new ArrayList(); @@ -965,22 +1010,27 @@ public void updateAck(long actTS, long userId, int alternateAckSource, long even } - public void ackAllPending(long actTS, long userId, int alternateAckSource) { + public void ackEvents(long actTS, long userId, int alternateAckSource) { if (LOG.isTraceEnabled()) { LOG.trace("actTS:"+actTS+" userId:"+userId+" alternateAckSource:"+alternateAckSource); } - DAO.getInstance().getJdbcTemp().update( EVENT_ACT_ALL, new Object[] { actTS, userId, alternateAckSource } ); + DAO.getInstance().getJdbcTemp().update( EVENT_ACT_ALL + "and " + STATUS_NO_ACTIVE_CONDITION_SQL, new Object[] { actTS, userId, alternateAckSource } ); } - public void silenceAll(long userId) { + public void silenceEvents(long userId) { if (LOG.isTraceEnabled()) { LOG.trace(" userId:"+userId); } DAO.getInstance().getJdbcTemp().update( EVENT_SILENCE_ALL, new Object[] { userId } ); } - + public void unassignEvents() { + if (LOG.isTraceEnabled()) { + LOG.trace(" unassignEvents"); + } + DAO.getInstance().getJdbcTemp().update(UNASSIGN_EVENT_ALL, new Object[] {} ); + } public void ackAllPendingSelected(long actTS, long userId, int alternateAckSource, List ids) { if (LOG.isTraceEnabled()) { @@ -1090,6 +1140,8 @@ public List searchOld(int eventId, int eventSourceType, String st where.add(STATUS_RTN_CONDITION_SQL); } else if (EventsDwr.STATUS_NORTN.equals(status)) { where.add(STATUS_NORTN_CONDITION_SQL); + } else if (EventsDwr.STATUS_ASSIGNEE.equals(status)) { + where.add(STATUS_ASSIGNEE_CONDITION_SQL); } if (alarmLevel != -1) { @@ -1194,6 +1246,8 @@ public List search(int eventId, int eventSourceType, where.add(STATUS_RTN_CONDITION_SQL); } else if (EventsDwr.STATUS_NORTN.equals(status)) { where.add(STATUS_NORTN_CONDITION_SQL); + } else if (EventsDwr.STATUS_ASSIGNEE.equals(status)) { + where.add(STATUS_ASSIGNEE_CONDITION_SQL); } if (alarmLevel != -1) { @@ -1481,4 +1535,38 @@ public String joinOr(List conditions) { } return result.toString(); } + + public boolean isSilence(int eventId, int userId) { + String result = null; + try { + result = DAO.getInstance().getJdbcTemp().queryForObject(SILENCED_SELECT, new Object[]{eventId, userId}, String.class); + } catch (Exception ex) { + LOG.warn(ex); + } + if (result == null) { + return true; + } else { + return DAO.charToBool(result); + } + } + + public boolean assign(long eventId, long acceptTs, User user) { + + if (LOG.isTraceEnabled()) { + LOG.trace("acceptTs:"+acceptTs+" username:"+user.getUsername()+" eventId:"+eventId); + } + + int updates = DAO.getInstance().getJdbcTemp().update(EVENT_ASSIGN_EVENT, new Object[] { acceptTs, user.getUsername(), eventId } ); + return updates > 0; + } + + public boolean unassign(long eventId) { + + if (LOG.isTraceEnabled()) { + LOG.trace("eventId:"+eventId); + } + + int updates = DAO.getInstance().getJdbcTemp().update(EVENT_UNASSIGN_EVENT, new Object[] { eventId } ); + return updates > 0; + } } diff --git a/src/org/scada_lts/dao/migration/mysql/V2_8_0__AddAssigneeColumnsToEvents.java b/src/org/scada_lts/dao/migration/mysql/V2_8_0__AddAssigneeColumnsToEvents.java new file mode 100644 index 0000000000..74c5412114 --- /dev/null +++ b/src/org/scada_lts/dao/migration/mysql/V2_8_0__AddAssigneeColumnsToEvents.java @@ -0,0 +1,71 @@ +package org.scada_lts.dao.migration.mysql; + +import com.serotonin.mango.view.View; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.scada_lts.dao.DAO; +import org.scada_lts.dao.SerializationData; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.util.List; +import java.util.Objects; + +public class V2_8_0__AddAssigneeColumnsToEvents extends BaseJavaMigration { + + private static final Log LOG = LogFactory.getLog(V2_8_0__AddAssigneeColumnsToEvents.class); + + @Override + public void migrate(Context context) throws Exception { + + final JdbcTemplate jdbcTmp = DAO.getInstance().getJdbcTemp(); + + try { + addAssigneeColumn(jdbcTmp); + updateViewComponents(jdbcTmp); + } catch (Exception ex) { + LOG.error(ex.getMessage(), ex); + throw ex; + } + } + + private void addAssigneeColumn(JdbcTemplate jdbcTmp) { + + boolean existsAssigneeTsColumn = jdbcTmp.queryForObject("SELECT (SELECT `TABLE_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA`= DATABASE() AND `TABLE_NAME`='events' AND `COLUMN_NAME`='assigneeTs') IS NOT NULL;", boolean.class); + boolean existsAssigneeUsernameColumn = jdbcTmp.queryForObject("SELECT (SELECT `TABLE_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA`= DATABASE() AND `TABLE_NAME`='events' AND `COLUMN_NAME`='assigneeUsername') IS NOT NULL;", boolean.class); + + if(!existsAssigneeTsColumn) + jdbcTmp.update("ALTER TABLE events ADD COLUMN assigneeTs bigint DEFAULT NULL;"); + + if(!existsAssigneeUsernameColumn) + jdbcTmp.update("ALTER TABLE events ADD COLUMN assigneeUsername VARCHAR(40) DEFAULT NULL;"); + } + + private void updateViewComponents(JdbcTemplate jdbcTmp) { + List views = jdbcTmp.query("SELECT id, data FROM mangoViews", (resultSet, i) -> { + try (InputStream inputStream = resultSet.getBinaryStream("data"); + ObjectInputStream objectInputStream = new ObjectInputStream(inputStream)) { + View view = (View) objectInputStream.readObject(); + view.setId(resultSet.getInt("id")); + return view; + } catch (IOException | ClassNotFoundException ex) { + LOG.error(ex.getMessage(), ex); + return null; + } + }); + + boolean isNull = views.stream().anyMatch(Objects::isNull); + if (isNull) { + throw new IllegalStateException("View is null!"); + } + + for (View view : views) { + jdbcTmp.update("UPDATE mangoViews set data = ? WHERE id = ?", + new SerializationData().writeObject(view), view.getId()); + } + } +} diff --git a/src/org/scada_lts/login/LoggedUsers.java b/src/org/scada_lts/login/LoggedUsers.java index 6f67fe10c8..71b8913652 100644 --- a/src/org/scada_lts/login/LoggedUsers.java +++ b/src/org/scada_lts/login/LoggedUsers.java @@ -19,21 +19,16 @@ public class LoggedUsers implements ILoggedUsers { private static final Log LOG = LogFactory.getLog(LoggedUsers.class); - public LoggedUsers() {} - private final Map loggedUsers = new ConcurrentHashMap<>(); private final Map> loggedSessions = new ConcurrentHashMap<>(); private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - private final ThreadLocal blocked = new ThreadLocal<>(); + + public LoggedUsers() {} @Override public User addUser(User user, HttpSession session) { lock.writeLock().lock(); try { - if("setAttribute".equals(blocked.get())) { - LOG.warn("blocked addUser: " + LoggingUtils.userInfo(user) + ", thread: " + Thread.currentThread().getName()); - return null; - } loggedSessions.putIfAbsent(user.getId(), new CopyOnWriteArrayList<>()); if(!loggedSessions.get(user.getId()).contains(session)) { loggedSessions.get(user.getId()).add(session); @@ -50,7 +45,7 @@ public User addUser(User user, HttpSession session) { public void updateUser(User user) { lock.writeLock().lock(); try { - update(user, loggedUsers, loggedSessions, blocked); + update(user, loggedUsers, loggedSessions); } finally { lock.writeLock().unlock(); } @@ -63,7 +58,7 @@ public void updateUsers(UsersProfileVO profile) { for(User user: new ArrayList<>(loggedUsers.values())) { if(user.getUserProfile() == profile.getId()) { profile.apply(user); - update(user, loggedUsers, loggedSessions, blocked); + update(user, loggedUsers, loggedSessions); } } } finally { @@ -75,10 +70,6 @@ public void updateUsers(UsersProfileVO profile) { public User removeUser(User user, HttpSession session) { lock.writeLock().lock(); try { - if("setAttribute".equals(blocked.get())) { - LOG.warn("blocked removeUser: " + LoggingUtils.userInfo(user) + ", thread: " + Thread.currentThread().getName()); - return null; - } if (loggedSessions.get(user.getId()) == null || (loggedSessions.get(user.getId()).remove(session) && loggedSessions.get(user.getId()).isEmpty())) { loggedSessions.remove(user.getId()); @@ -121,8 +112,7 @@ public User getUser(int id) { } private static void update(User user, Map loggedUsers, - Map> loggedSessions, - ThreadLocal blocked) { + Map> loggedSessions) { User loggedUser = loggedUsers.get(user.getId()); if(loggedUser == null) { LOG.warn("not logged user: " + LoggingUtils.userInfo(user) + ", thread: " + Thread.currentThread().getName()); @@ -133,9 +123,7 @@ private static void update(User user, Map loggedUsers, user.setAttribute("roles", roles); loggedSessions.putIfAbsent(user.getId(), new CopyOnWriteArrayList<>()); for(HttpSession session : loggedSessions.get(user.getId())) { - blocked.set("setAttribute"); session.setAttribute(SESSION_USER, user); - blocked.set(""); } loggedUsers.put(user.getId(), user); } diff --git a/src/org/scada_lts/mango/adapter/MangoEvent.java b/src/org/scada_lts/mango/adapter/MangoEvent.java index cab5948e6e..253c35b240 100644 --- a/src/org/scada_lts/mango/adapter/MangoEvent.java +++ b/src/org/scada_lts/mango/adapter/MangoEvent.java @@ -24,6 +24,7 @@ import com.serotonin.mango.rt.event.EventInstance; import com.serotonin.mango.rt.event.type.EventType; +import com.serotonin.mango.vo.User; import com.serotonin.mango.vo.UserComment; import com.serotonin.mango.vo.event.EventHandlerVO; import com.serotonin.mango.vo.event.EventTypeVO; @@ -37,9 +38,11 @@ public interface MangoEvent { void saveEvent(EventInstance event); - + + @Deprecated(since = "2.8.0") void ackEvent(int eventId, long time, int userId, int alternateAckSource, boolean signalAlarmLevelChange); - + + @Deprecated(since = "2.8.0") void ackEvent(int eventId, long time, int userId, int alternateAckSource); void silenceEvent(int eventId, int userId); @@ -50,8 +53,10 @@ public interface MangoEvent { void unsilenceEvents(List eventIds, int userId); + @Deprecated(since = "2.8.0") void ackAllPending(long time, int userId, int alternateAckSource); + @Deprecated(since = "2.8.0") void silenceAll(int userId); void ackSelected(long time, int userId, int alternateAckSource, List ids); @@ -116,9 +121,10 @@ public interface MangoEvent { void updateEventHandler(EventHandlerVO handler); void deleteEventHandler(final int handlerId); - + + @Deprecated(since = "2.8.0") boolean toggleSilence(int eventId, int userId); - + int getHighestUnsilencedAlarmLevel(int userId); EventInstance getEvent(int eventId); @@ -128,4 +134,15 @@ public interface MangoEvent { boolean isXidUnique(String xid, int excludeId); List getPendingEventsAlarmLevelMin(int userId, int alarmLevelMin, int limit); + + boolean toggleSilence(EventInstance event, User user); + + boolean assignEvent(EventInstance event, User user); + boolean unassignEvent(EventInstance event, User user); + + void ackEvent(EventInstance event, long time, User user, int alternateAckSource, boolean signalAlarmLevelChange); + void ackEvent(EventInstance event, long time, User user, int alternateAckSource); + void unassignEvents(); + void ackEvents(User user); + List silenceEvents(User user); } diff --git a/src/org/scada_lts/mango/service/EventService.java b/src/org/scada_lts/mango/service/EventService.java index 25b53c9222..306b4e795d 100644 --- a/src/org/scada_lts/mango/service/EventService.java +++ b/src/org/scada_lts/mango/service/EventService.java @@ -24,12 +24,15 @@ import com.serotonin.mango.rt.event.type.AuditEventType; import com.serotonin.mango.rt.event.type.AuditEventUtils; import com.serotonin.mango.rt.event.type.EventType; +import com.serotonin.mango.rt.event.type.SystemEventType; import com.serotonin.mango.rt.maint.work.AbstractBeforeAfterWorkItem; import com.serotonin.mango.rt.maint.work.WorkItemPriority; +import com.serotonin.mango.util.LoggingUtils; import com.serotonin.mango.vo.User; import com.serotonin.mango.vo.UserComment; import com.serotonin.mango.vo.event.EventHandlerVO; import com.serotonin.mango.vo.event.EventTypeVO; +import com.serotonin.web.i18n.LocalizableMessage; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.scada_lts.cache.PendingEventsCache; @@ -135,13 +138,15 @@ public void saveEvent(EventInstance event) { updateCache(event); } } - + + @Deprecated(since = "2.8.0") @Transactional(readOnly = false,propagation= Propagation.REQUIRES_NEW,isolation= Isolation.READ_COMMITTED,rollbackFor=SQLException.class) @Override public void ackEvent(int eventId, long time, int userId, int alternateAckSource, boolean signalAlarmLevelChange) { _ackEvent(eventId, time, userId, alternateAckSource, signalAlarmLevelChange); } + @Deprecated(since = "2.8.0") @Transactional(readOnly = false,propagation= Propagation.REQUIRES_NEW,isolation= Isolation.READ_COMMITTED,rollbackFor=SQLException.class) @Override public void ackEvent(int eventId, long time, int userId, int alternateAckSource) { @@ -170,12 +175,18 @@ public void unsilenceEvents(List eventIds, int userId) { @Override public void ackAllPending(long time, int userId, int alternateAckSource) { - eventDAO.ackAllPending(time, userId, alternateAckSource); + MangoEvent eventService = new EventService(); + UserService userEvent = new UserService(); + User user = userEvent.getUser(userId); + for (EventInstance evt : eventService.getPendingEvents(user.getId())) { + if(!evt.isActive()) + eventService.ackEvent(evt, time, user, alternateAckSource); + } } @Override public void silenceAll(int userId) { - eventDAO.silenceAll(userId); + eventDAO.silenceEvents(userId); } @Override @@ -310,7 +321,7 @@ public EventInstance insertEventComment(int eventId, UserComment comment) { @Override public int purgeEventsBefore(long time) { int result = eventDAO.purgeEventsBefore(time); - Common.ctx.getEventManager().resetHighestAlarmLevels(); + Common.ctx.getEventManager().notifyEventReset(); return result; } @@ -424,6 +435,7 @@ public void deleteEventHandler(final String handlerXid) { } @Override + @Deprecated(since = "2.8.0") public boolean toggleSilence(int eventId, int userId) { boolean updated = eventDAO.toggleSilence(eventId, userId, false); if (updated) { @@ -550,6 +562,7 @@ public SQLPageWithTotal getEventsWithLimit(JsonEventSearch query, User return eventDAO.findEvents(query, user); } + @Deprecated(since = "2.8.0") private void notifyEventAck(int eventId) { Common.ctx.getEventManager().notifyEventAck(eventId); } @@ -568,6 +581,7 @@ public boolean isXidUnique(String xid, int excludeId) { return DAO.getInstance().isXidUnique(xid, excludeId, "eventHandlers"); } + @Deprecated(since = "2.8.0") private void _ackEvent(int eventId, long time, int userId, int alternateAckSource, boolean signalAlarmLevelChange) { eventDAO.updateAck(time, userId, alternateAckSource, eventId); // true silenced @@ -576,4 +590,128 @@ private void _ackEvent(int eventId, long time, int userId, int alternateAckSourc clearCache(); notifyEventAck(eventId); } + + @Override + public boolean toggleSilence(EventInstance event, User user) { + boolean updated = eventDAO.toggleSilence(event.getId(), user.getId(), true); + if (updated) { + Common.ctx.getEventManager().setLastAlarmTimestamp(System.currentTimeMillis()); + Common.ctx.getEventManager().notifyEventToggle(event, user); + } else if(!event.isAssignee()) { + Common.ctx.getEventManager().notifyEventRaise(event, user); + } + return updated; + } + + @Transactional(readOnly = false,propagation= Propagation.REQUIRES_NEW,isolation= Isolation.READ_COMMITTED,rollbackFor=SQLException.class) + @Override + public void ackEvent(EventInstance event, long time, User user, int alternateAckSource, boolean signalAlarmLevelChange) { + _ackEvent(event, time, user, alternateAckSource, signalAlarmLevelChange); + } + + @Transactional(readOnly = false,propagation= Propagation.REQUIRES_NEW,isolation= Isolation.READ_COMMITTED,rollbackFor=SQLException.class) + @Override + public void ackEvent(EventInstance event, long time, User user, int alternateAckSource) { + _ackEvent(event, time, user, alternateAckSource, true); + } + + @Override + public boolean assignEvent(EventInstance event, User user) { + if(!isAssignPermission(event, user)) { + return false; + } + long time = System.currentTimeMillis(); + boolean updated = eventDAO.assign(event.getId(), time, user); + if (updated) { + Common.ctx.getEventManager().setLastAlarmTimestamp(time); + Common.ctx.getEventManager().notifyEventAssignee(event); + removeUserIdFromCache(user.getId()); + SystemEventType.raiseEvent(new SystemEventType(SystemEventType.TYPE_ASSIGNED_EVENT), time, false, + new LocalizableMessage("events.assignedBy", LoggingUtils.userInfo(user), LoggingUtils.eventInfo(event))); + } else { + Common.ctx.getEventManager().notifyEventRaise(event); + } + return updated; + } + + @Override + public boolean unassignEvent(EventInstance event, User user) { + if(!isAssignPermission(event, user)) { + return false; + } + long time = System.currentTimeMillis(); + boolean updated = eventDAO.unassign(event.getId()); + if (updated) { + Common.ctx.getEventManager().setLastAlarmTimestamp(time); + removeUserIdFromCache(user.getId()); + SystemEventType.raiseEvent(new SystemEventType(SystemEventType.TYPE_UNASSIGNED_EVENT), time, false, + new LocalizableMessage("events.unassignedBy", event.getAssigneeUsername(), LoggingUtils.userInfo(user), + LoggingUtils.eventInfo(event))); + } + boolean silence = eventDAO.isSilence(event.getId(), user.getId()); + if(!silence) { + Common.ctx.getEventManager().notifyEventRaise(event); + } + return updated; + } + + @Override + public void unassignEvents() { + eventDAO.unassignEvents(); + Common.ctx.getEventManager().notifyEventReset(); + } + + @Override + public void ackEvents(User user) { + long now = System.currentTimeMillis(); + if(user.isAdmin()) { + eventDAO.ackEvents(now, user.getId(), 0); + clearCache(); + Common.ctx.getEventManager().notifyEventReset(); + } else { + for (EventInstance evt : getPendingEvents(user.getId())) { + if(!evt.isActive()) + ackEvent(evt, now, user, 0); + } + } + } + + @Override + public List silenceEvents(User user) { + List silenced = new ArrayList<>(); + if(user.isAdmin()) { + eventDAO.silenceEvents(user.getId()); + clearCache(); + Common.ctx.getEventManager().notifyEventReset(); + } else { + for (EventInstance evt : getPendingEvents(user.getId())) { + if (!evt.isSilenced()) { + toggleSilence(evt, user); + silenced.add(evt.getId()); + } + } + } + return silenced; + } + + private static boolean isAssignPermission(EventInstance event, User user) { + return !event.isAssignee() || (user.isAdmin() || Objects.equals(user.getUsername(), event.getAssigneeUsername())); + } + + private void notifyEventAck(EventInstance event) { + Common.ctx.getEventManager().notifyEventAck(event); + } + + private void _ackEvent(EventInstance event, long time, User user, int alternateAckSource, boolean signalAlarmLevelChange) { + if(event.isActive()) { + LOG.warn("Event is active! This event cannot be acknowledged."); + return; + } + eventDAO.updateAck(time, user.getId(), alternateAckSource, event.getId()); + // true silenced + userEventDAO.updateAck(event.getId(), true); + + clearCache(); + notifyEventAck(event); + } } diff --git a/src/org/scada_lts/mango/service/PendingEventService.java b/src/org/scada_lts/mango/service/PendingEventService.java index ea9557399e..cecbf8308b 100644 --- a/src/org/scada_lts/mango/service/PendingEventService.java +++ b/src/org/scada_lts/mango/service/PendingEventService.java @@ -20,7 +20,6 @@ import com.serotonin.mango.rt.event.type.AlarmLevelType; import com.serotonin.mango.rt.event.EventInstance; -import org.scada_lts.login.ILoggedUsers; import com.serotonin.mango.vo.User; import com.serotonin.mango.vo.UserComment; import org.apache.commons.logging.Log; @@ -48,45 +47,44 @@ public class PendingEventService { private final SystemSettingsService systemSettingsService; private final IHighestAlarmLevelService highestAlarmLevelService; - private final ILoggedUsers loggedUsers; public PendingEventService() { userCommentDAO = ApplicationBeans.getUserCommentDaoBean(); highestAlarmLevelService = ApplicationBeans.getHighestAlarmLevelServiceBean(); - loggedUsers = ApplicationBeans.getLoggedUsersBean(); pendingEventsDAO = new PendingEventsDAO(); systemSettingsService = new SystemSettingsService(); } public Map> getPendingEvents() { - - Set users = loggedUsers.getUserIds(); - Map> comments = getCacheUserComments(userCommentDAO.getEventComments()); - int limit = systemSettingsService.getMiscSettings().getEventPendingLimit(); - - Map> cacheEvents = new ConcurrentHashMap<>(); - for (int userId: users) { - Set events = pendingEventsDAO.getPendingEvents(userId, comments, AlarmLevelType.NONE, - SystemSettingsUtils.getEventPendingUpdateLimit(), 0).stream() - .map(EventInstanceEqualsById::new) - .collect(Collectors.toSet()); - if(!events.isEmpty()) { - int highestAlarmLevelForUser = highestAlarmLevelService.getAlarmLevel(User.onlyId(userId)); - for (AlarmLevelType alarmLevelType : AlarmLevelType.getAlarmLevelsWithoutNone()) { - if(alarmLevelType.getCode() <= highestAlarmLevelForUser) { - long count = events.stream() - .filter(a -> a.getEventInstance().getAlarmLevel() >= alarmLevelType.getCode()) - .count(); - if (count < limit) { - events.addAll(pendingEventsDAO.getPendingEvents(userId, comments, alarmLevelType, limit, 0).stream() - .map(EventInstanceEqualsById::new) - .collect(Collectors.toSet())); + Map> cacheEvents = new ConcurrentHashMap<>(); + ApplicationBeans.Lazy.getLoggedUsersBean().ifPresent(loggedUsers -> { + Set users = loggedUsers.getUserIds(); + Map> comments = getCacheUserComments(userCommentDAO.getEventComments()); + int limit = systemSettingsService.getMiscSettings().getEventPendingLimit(); + for (int userId: users) { + Set events = pendingEventsDAO.getPendingEvents(userId, comments, AlarmLevelType.NONE, + SystemSettingsUtils.getEventPendingUpdateLimit(), 0).stream() + .map(EventInstanceEqualsById::new) + .collect(Collectors.toSet()); + if(!events.isEmpty()) { + int highestAlarmLevelForUser = highestAlarmLevelService.getAlarmLevel(User.onlyId(userId)); + for (AlarmLevelType alarmLevelType : AlarmLevelType.getAlarmLevelsWithoutNone()) { + if(alarmLevelType.getCode() <= highestAlarmLevelForUser) { + long count = events.stream() + .filter(a -> a.getEventInstance().getAlarmLevel() >= alarmLevelType.getCode()) + .count(); + if (count < limit) { + events.addAll(pendingEventsDAO.getPendingEvents(userId, comments, alarmLevelType, limit, 0).stream() + .map(EventInstanceEqualsById::new) + .collect(Collectors.toSet())); + } } } } + cacheEvents.put(userId, events.stream().map(EventInstanceEqualsById::getEventInstance).collect(Collectors.toList())); } - cacheEvents.put(userId, events.stream().map(EventInstanceEqualsById::getEventInstance).collect(Collectors.toList())); - } + }); + return cacheEvents; } diff --git a/src/org/scada_lts/mango/service/SystemSettingsService.java b/src/org/scada_lts/mango/service/SystemSettingsService.java index 8498b0dac6..ad1530bbc2 100644 --- a/src/org/scada_lts/mango/service/SystemSettingsService.java +++ b/src/org/scada_lts/mango/service/SystemSettingsService.java @@ -24,6 +24,7 @@ import org.scada_lts.utils.SystemSettingsUtils; import org.scada_lts.web.mvc.api.AggregateSettings; import org.scada_lts.web.mvc.api.json.*; +import org.springframework.stereotype.Service; import java.io.File; import java.io.IOException; @@ -38,6 +39,7 @@ * * @author Radoslaw Jajko */ +@Service public class SystemSettingsService { private static final org.apache.commons.logging.Log LOG = LogFactory.getLog(SystemSettingsService.class); @@ -142,6 +144,7 @@ public JsonSettingsMisc getMiscSettings() { json.setWorkItemsReportingItemsPerSecondLimit(SystemSettingsDAO.getIntValue(SystemSettingsDAO.WORK_ITEMS_REPORTING_ITEMS_PER_SECOND_LIMIT)); json.setWebResourceGraphicsPath(SystemSettingsDAO.getValue(SystemSettingsDAO.WEB_RESOURCE_GRAPHICS_PATH)); json.setWebResourceUploadsPath(SystemSettingsDAO.getValue(SystemSettingsDAO.WEB_RESOURCE_UPLOADS_PATH)); + json.setEventAssignEnabled(SystemSettingsDAO.getBooleanValue(SystemSettingsDAO.EVENT_ASSIGN_ENABLED)); return json; } @@ -158,6 +161,7 @@ public void saveMiscSettings(JsonSettingsMisc json) { systemSettingsDAO.setIntValue(SystemSettingsDAO.WORK_ITEMS_REPORTING_ITEMS_PER_SECOND_LIMIT, json.getWorkItemsReportingItemsPerSecondLimit()); systemSettingsDAO.setValue(SystemSettingsDAO.WEB_RESOURCE_GRAPHICS_PATH, json.getWebResourceGraphicsPath()); systemSettingsDAO.setValue(SystemSettingsDAO.WEB_RESOURCE_UPLOADS_PATH, json.getWebResourceUploadsPath()); + saveEventAssignEnabled(json.isEventAssignEnabled()); } public SettingsDataRetention getDataRetentionSettings() { @@ -382,6 +386,14 @@ public void saveAggregateSettings(AggregateSettings aggregateSettings) { systemSettingsDAO.setValue(SystemSettingsDAO.AGGREGATION_ENABLED, String.valueOf(aggregateSettings.isEnabled())); } + public void saveEventAssignEnabled(boolean eventAssignEnabled) { + systemSettingsDAO.setBooleanValue(SystemSettingsDAO.EVENT_ASSIGN_ENABLED, eventAssignEnabled); + if(!eventAssignEnabled) { + EventService eventService = new EventService(); + eventService.unassignEvents(); + } + } + public DataPointSyncMode getDataPointRtValueSynchronized() { return SystemSettingsDAO.getObject(SystemSettingsDAO.DATAPOINT_RUNTIME_VALUE_SYNCHRONIZED, DataPointSyncMode::typeOf); } @@ -455,6 +467,16 @@ public int getThreadsNameAdditionalLength() { } } + public boolean isEventAssignEnabled() { + boolean defaultValue = SystemSettingsUtils.isEventAssignEnabled(); + try { + return SystemSettingsDAO.getBooleanValue(SystemSettingsDAO.EVENT_ASSIGN_ENABLED, defaultValue); + } catch (Exception e) { + LOG.error(e.getMessage()); + return defaultValue; + } + } + private static String getHttpResponseHeaders(JsonSettingsHttp json) { try { String httpResponseHeaders = json.getHttpResponseHeaders(); diff --git a/src/org/scada_lts/quartz/ResetCacheHighestAlarmLevel.java b/src/org/scada_lts/quartz/ResetCacheHighestAlarmLevel.java index ae55f27914..64103ee264 100644 --- a/src/org/scada_lts/quartz/ResetCacheHighestAlarmLevel.java +++ b/src/org/scada_lts/quartz/ResetCacheHighestAlarmLevel.java @@ -24,7 +24,6 @@ import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.StatefulJob; -import org.scada_lts.mango.service.UserService; import org.scada_lts.service.IHighestAlarmLevelService; import org.scada_lts.web.beans.ApplicationBeans; import org.scada_lts.web.ws.services.UserEventServiceWebSocket; @@ -42,18 +41,16 @@ public class ResetCacheHighestAlarmLevel implements StatefulJob { private final IHighestAlarmLevelService highestAlarmLevelService; private final UserEventServiceWebSocket userEventServiceWebSocket; - private final UserService userService; public ResetCacheHighestAlarmLevel() { this.highestAlarmLevelService = ApplicationBeans.getHighestAlarmLevelServiceBean(); - this.userService = new UserService(); this.userEventServiceWebSocket = ApplicationBeans.getUserEventServiceWebsocketBean(); } @Override public void execute(JobExecutionContext arg0) throws JobExecutionException { long time = System.currentTimeMillis(); - NotifyEventUtils.resetHighestAlarmLevels(highestAlarmLevelService, userService, userEventServiceWebSocket); + NotifyEventUtils.notifyEventReset(highestAlarmLevelService, userEventServiceWebSocket); LOG.info(ResetCacheHighestAlarmLevel.class.getSimpleName() + " executed in [" + (System.currentTimeMillis() - time)+ "] ms"); } } diff --git a/src/org/scada_lts/service/HighestAlarmLevelService.java b/src/org/scada_lts/service/HighestAlarmLevelService.java index ee3a86459b..0fc8ab55fb 100644 --- a/src/org/scada_lts/service/HighestAlarmLevelService.java +++ b/src/org/scada_lts/service/HighestAlarmLevelService.java @@ -4,8 +4,7 @@ import com.serotonin.mango.vo.User; import org.scada_lts.dao.IHighestAlarmLevelDAO; import org.scada_lts.dao.model.UserAlarmLevel; -import org.scada_lts.mango.adapter.MangoUser; -import org.scada_lts.mango.service.UserService; +import org.scada_lts.web.beans.ApplicationBeans; import org.scada_lts.web.ws.model.WsAlarmLevelMessage; import java.util.function.BiConsumer; @@ -13,11 +12,9 @@ public class HighestAlarmLevelService implements IHighestAlarmLevelService { private final IHighestAlarmLevelDAO highestAlarmLevelDAO; - private final MangoUser userService; public HighestAlarmLevelService(IHighestAlarmLevelDAO highestAlarmLevelDAO) { this.highestAlarmLevelDAO = highestAlarmLevelDAO; - this.userService = new UserService(); } @Override @@ -57,8 +54,10 @@ public boolean doRemoveAlarmLevel(User user, EventInstance event, BiConsumer send) { - for(User user: userService.getActiveUsers()) - doSend(user, send); + ApplicationBeans.Lazy.getLoggedUsersBean().ifPresent(loggedUsers -> { + for(User user: loggedUsers.getUsers()) + doSend(user, send); + }); } private boolean doSend(User user, BiConsumer send) { diff --git a/src/org/scada_lts/service/HighestAlarmLevelServiceWithCache.java b/src/org/scada_lts/service/HighestAlarmLevelServiceWithCache.java index 577d69a623..6f210d0479 100644 --- a/src/org/scada_lts/service/HighestAlarmLevelServiceWithCache.java +++ b/src/org/scada_lts/service/HighestAlarmLevelServiceWithCache.java @@ -12,6 +12,7 @@ import org.scada_lts.mango.adapter.MangoUser; import org.scada_lts.mango.service.UserService; import org.scada_lts.quartz.CronTriggerScheduler; +import org.scada_lts.web.beans.ApplicationBeans; import org.scada_lts.web.ws.model.WsAlarmLevelMessage; import java.util.List; @@ -111,8 +112,10 @@ public void doResetAlarmLevels(BiConsumer send) { } finally { this.lock.writeLock().unlock(); } - for(User user: userService.getActiveUsers()) - doSend(user, send); + ApplicationBeans.Lazy.getLoggedUsersBean().ifPresent(loggedUsers -> { + for(User user: loggedUsers.getUsers()) + doSend(user, send); + }); } private boolean doSend(User user, BiConsumer send) { diff --git a/src/org/scada_lts/utils/SystemSettingsUtils.java b/src/org/scada_lts/utils/SystemSettingsUtils.java index 6d92988b67..77919caa16 100644 --- a/src/org/scada_lts/utils/SystemSettingsUtils.java +++ b/src/org/scada_lts/utils/SystemSettingsUtils.java @@ -65,6 +65,8 @@ private SystemSettingsUtils() {} public static final String HTTP_PROTOCOL_ALLOW_CIRCULAR_REDIRECTS_KEY = "http.protocol.allow-circular-redirects"; public static final String HTTP_PROTOCOL_TIMEOUT_MS_KEY = "http.protocol.timeout-ms"; + public static final String EVENT_ASSIGN_ENABLED_KEY = "event.assign.enabled"; + private static final org.apache.commons.logging.Log LOG = LogFactory.getLog(SystemSettingsUtils.class); public static DataPointSyncMode getDataPointSynchronizedMode() { @@ -526,4 +528,14 @@ public static int getHttpTimeoutMs() { return 15001; } } + + public static boolean isEventAssignEnabled() { + try { + String eventAssignEnabled = ScadaConfig.getInstance().getConf().getProperty(EVENT_ASSIGN_ENABLED_KEY, "true"); + return Boolean.parseBoolean(eventAssignEnabled); + } catch (Exception e) { + LOG.error(e.getMessage()); + return false; + } + } } diff --git a/src/org/scada_lts/web/beans/ApplicationBeans.java b/src/org/scada_lts/web/beans/ApplicationBeans.java index 0399e95a1c..5c34cc4e98 100644 --- a/src/org/scada_lts/web/beans/ApplicationBeans.java +++ b/src/org/scada_lts/web/beans/ApplicationBeans.java @@ -156,6 +156,13 @@ private Lazy() {} public static Optional getLoggedUsersBean() { return getBeanFromContext("loggedUsers", ILoggedUsers.class); } + + public static Optional getHighestAlarmLevelServiceBean() { + boolean highestAlarmLevelCacheEnabled = Common.getEnvironmentProfile().getBoolean(HighestAlarmLevelCacheable.CACHE_ENABLED_KEY, true); + return highestAlarmLevelCacheEnabled ? getBeanFromContext("highestAlarmLevelServiceWithCache", IHighestAlarmLevelService.class) : + getBeanFromContext("highestAlarmLevelService", IHighestAlarmLevelService.class); + } + private static Optional getBeanFromContext(String beanName, Class clazz) { try { return Optional.ofNullable(get(beanName, clazz)); diff --git a/src/org/scada_lts/web/mvc/api/EventsAPI.java b/src/org/scada_lts/web/mvc/api/EventsAPI.java index d981fac217..dd77a00cf6 100644 --- a/src/org/scada_lts/web/mvc/api/EventsAPI.java +++ b/src/org/scada_lts/web/mvc/api/EventsAPI.java @@ -1,6 +1,7 @@ package org.scada_lts.web.mvc.api; import com.serotonin.mango.Common; +import com.serotonin.mango.rt.event.EventInstance; import com.serotonin.mango.vo.User; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -84,7 +85,8 @@ public ResponseEntity acknowledgeEvent(@PathVariable("id") int eventId, User user = Common.getUser(request); if (user != null) { Date time = new Date(); - eventService.ackEvent(eventId, time.getTime(), user.getId(), 0); + EventInstance event = eventService.getEvent(eventId); + eventService.ackEvent(event, time.getTime(), user, 0); return new ResponseEntity<>(HttpStatus.OK); } else { return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); @@ -107,7 +109,6 @@ public ResponseEntity silenceEvent(@PathVariable("id") int eventId, Http try { User user = Common.getUser(request); if (user != null) { - Date time = new Date(); eventService.silenceEvent(eventId, user.getId()); return new ResponseEntity<>(HttpStatus.OK); } else { @@ -131,7 +132,6 @@ public ResponseEntity unsilenceEvent(@PathVariable("id") int eventId, Ht try { User user = Common.getUser(request); if (user != null) { - Date time = new Date(); eventService.unsilenceEvent(eventId, user.getId()); return new ResponseEntity<>(HttpStatus.OK); } else { @@ -200,8 +200,7 @@ public ResponseEntity acknowledgeAllEvents(HttpServletRequest request) { try { User user = Common.getUser(request); if (user != null) { - Date time = new Date(); - eventService.ackAllPending(time.getTime(), user.getId(), 0); + eventService.ackEvents(user); return new ResponseEntity<>(HttpStatus.OK); } else { return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); @@ -223,8 +222,7 @@ public ResponseEntity silenceAllEvents(HttpServletRequest request) { try { User user = Common.getUser(request); if (user != null) { - Date time = new Date(); - eventService.silenceAll(user.getId()); + eventService.silenceEvents(user); return new ResponseEntity<>(HttpStatus.OK); } else { return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); @@ -339,4 +337,52 @@ public ResponseEntity> getCommentsByEventId(@PathVariable( return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } + + /** + * Accept specific Event Alarm from REST API + * + * @param eventId Event ID number + * @param request Request containing user data + * @return Response + */ + @PutMapping(value = "/assign/{id}") + public ResponseEntity assigneeEvent(@PathVariable("id") int eventId, HttpServletRequest request) { + LOG.info("PUT::/api/assignee/" + eventId); + try { + User user = Common.getUser(request); + if (user != null) { + EventInstance event = eventService.getEvent(eventId); + eventService.assignEvent(event, user); + return new ResponseEntity<>(HttpStatus.OK); + } else { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } + } catch (Exception e) { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + /** + * Accept specific Event Alarm from REST API + * + * @param eventId Event ID number + * @param request Request containing user data + * @return Response + */ + @PutMapping(value = "/unassign/{id}") + public ResponseEntity clearAssigneeEvent(@PathVariable("id") int eventId, HttpServletRequest request) { + LOG.info("PUT::/api/clear-assignee/" + eventId); + try { + User user = Common.getUser(request); + if (user != null) { + EventInstance event = eventService.getEvent(eventId); + eventService.unassignEvent(event, user); + return new ResponseEntity<>(HttpStatus.OK); + } else { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } + } catch (Exception e) { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } } diff --git a/src/org/scada_lts/web/mvc/api/json/JsonSettingsMisc.java b/src/org/scada_lts/web/mvc/api/json/JsonSettingsMisc.java index e9a2ab8d1f..ac53c2df44 100644 --- a/src/org/scada_lts/web/mvc/api/json/JsonSettingsMisc.java +++ b/src/org/scada_lts/web/mvc/api/json/JsonSettingsMisc.java @@ -16,6 +16,7 @@ public class JsonSettingsMisc implements Serializable { public int threadsNameAdditionalLength; public String webResourceGraphicsPath; public String webResourceUploadsPath; + public boolean eventAssignEnabled; public JsonSettingsMisc() {} @@ -114,4 +115,12 @@ public String getWebResourceUploadsPath() { public void setWebResourceUploadsPath(String webResourceUploadsPath) { this.webResourceUploadsPath = webResourceUploadsPath; } + + public boolean isEventAssignEnabled() { + return eventAssignEnabled; + } + + public void setEventAssignEnabled(boolean eventAssignEnabled) { + this.eventAssignEnabled = eventAssignEnabled; + } } diff --git a/src/org/scada_lts/web/mvc/controller/ViewEditController.java b/src/org/scada_lts/web/mvc/controller/ViewEditController.java index 3a8dec4b4e..710b1713ef 100644 --- a/src/org/scada_lts/web/mvc/controller/ViewEditController.java +++ b/src/org/scada_lts/web/mvc/controller/ViewEditController.java @@ -33,6 +33,7 @@ import org.apache.commons.io.FilenameUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.scada_lts.mango.service.SystemSettingsService; import org.scada_lts.mango.service.UsersProfileService; import org.scada_lts.mango.service.ViewService; import org.scada_lts.permissions.service.GetViewsWithAccess; @@ -83,10 +84,12 @@ public class ViewEditController { private final ViewService viewService; private final ViewEditValidator validator; + private final SystemSettingsService systemSettingsService; - public ViewEditController(ViewService viewService, ViewEditValidator validator) { + public ViewEditController(ViewService viewService, ViewEditValidator validator, SystemSettingsService systemSettingsService) { this.viewService = viewService; this.validator = validator; + this.systemSettingsService = systemSettingsService; } public void setSuccessUrl(String successUrl) { @@ -125,6 +128,7 @@ protected ModelAndView showForm(HttpServletRequest request) throws Exception { model.put(IMAGE_SETS_ATTRIBUTE, Common.ctx.getImageSets()); model.put(DYNAMIC_IMAGES_ATTRIBUTE, Common.ctx.getDynamicImages()); model.put("currentView", view); + model.put("isEventAssignEnabled", systemSettingsService.isEventAssignEnabled()); return new ModelAndView(FORM_VIEW, model); } @@ -159,6 +163,7 @@ protected ModelAndView handleImage(HttpServletRequest request, HttpServletRespon model.put(IMAGE_SETS_ATTRIBUTE, Common.ctx.getImageSets()); model.put(DYNAMIC_IMAGES_ATTRIBUTE, Common.ctx.getDynamicImages()); model.put("currentView", view); + model.put("isEventAssignEnabled", systemSettingsService.isEventAssignEnabled()); return new ModelAndView(FORM_VIEW, model); } @@ -180,6 +185,7 @@ protected ModelAndView save(HttpServletRequest request, @ModelAttribute(FORM_OBJ model.put(IMAGE_SETS_ATTRIBUTE, Common.ctx.getImageSets()); model.put(DYNAMIC_IMAGES_ATTRIBUTE, Common.ctx.getDynamicImages()); model.put("currentView", view); + model.put("isEventAssignEnabled", systemSettingsService.isEventAssignEnabled()); return new ModelAndView(FORM_VIEW, model); } diff --git a/src/org/scada_lts/web/ws/model/WsEventMessage.java b/src/org/scada_lts/web/ws/model/WsEventMessage.java index e985d7e27c..817e7ab152 100644 --- a/src/org/scada_lts/web/ws/model/WsEventMessage.java +++ b/src/org/scada_lts/web/ws/model/WsEventMessage.java @@ -43,6 +43,10 @@ public String getAckMessage() { return getString(instance.getAckMessage()); } + public LocalizableMessage getAssigneeMessage() { + return instance.getAssigneeMessage(); + } + public String getExportAckMessage() { return getString(instance.getExportAckMessage()); } @@ -159,6 +163,18 @@ public String getAction() { return action; } + public long getAssigneeTimestamp() { + return instance.getAssigneeTimestamp(); + } + + public String getAssigneeUsername() { + return instance.getAssigneeUsername(); + } + + public boolean isAssignee() { + return instance.isAssignee(); + } + public int getTypeId() { return instance.getEventType().getEventSourceId(); } diff --git a/test/br/org/scadabr/view/component/GenerateAlarmListComponentTest.java b/test/br/org/scadabr/view/component/GenerateAlarmListComponentTest.java index 0306b31aa8..bd86f3e548 100644 --- a/test/br/org/scadabr/view/component/GenerateAlarmListComponentTest.java +++ b/test/br/org/scadabr/view/component/GenerateAlarmListComponentTest.java @@ -16,6 +16,7 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.scada_lts.mango.service.EventService; +import org.scada_lts.mango.service.SystemSettingsService; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; @@ -39,6 +40,8 @@ public class GenerateAlarmListComponentTest { private WebContext webContext; private EventService eventService; + private SystemSettingsService systemSettingsService; + @Mock private User user; @@ -60,6 +63,10 @@ public void setup() throws Exception { eventService = PowerMockito.mock(EventService.class); whenNew(EventService.class).withNoArguments().thenReturn(eventService); + systemSettingsService = PowerMockito.mock(SystemSettingsService.class); + whenNew(SystemSettingsService.class).withNoArguments().thenReturn(systemSettingsService); + + subject = new AlarmListComponent(); } diff --git a/test/org/scada_lts/dao/EventServiceTest.java b/test/org/scada_lts/dao/EventServiceTest.java index 0675046707..4d9e59e2b2 100644 --- a/test/org/scada_lts/dao/EventServiceTest.java +++ b/test/org/scada_lts/dao/EventServiceTest.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Properties; +import com.serotonin.mango.vo.User; import org.junit.Before; import org.junit.Test; import org.scada_lts.config.ScadaConfig; @@ -109,9 +110,12 @@ public void ackEvent() { int alarmLevel = 3; EventInstance e = new EventInstance(type, activeTS, applicable, alarmLevel, null, null); eventService.saveEvent(e); + + User user = new User(); + user.setId(ADMIN_USER_ID); long currentTime = System.currentTimeMillis(); - eventService.ackEvent(e.getId(), currentTime, ADMIN_USER_ID, -1, false); + eventService.ackEvent(e, currentTime, user, -1, false); List lstAckEvents = eventService.getActiveEvents(); boolean checkAckEvent = lstAckEvents.get(0).getAcknowledgedTimestamp() == currentTime; @@ -128,9 +132,12 @@ public void ackEventSingleAlarmLevelChange() { int alarmLevel = 3; EventInstance e = new EventInstance(type, activeTS, applicable, alarmLevel, null, null); eventService.saveEvent(e); + + User user = new User(); + user.setId(ADMIN_USER_ID); long currentTime = System.currentTimeMillis(); - eventService.ackEvent(e.getId(), currentTime, ADMIN_USER_ID, -1); + eventService.ackEvent(e, currentTime, user, -1); List lstAckEvents = eventService.getActiveEvents(); boolean checkAckEventSingleAlarmLevelChange = lstAckEvents.size() == 1; @@ -540,8 +547,11 @@ public void toggleSilence() { UserEventDAO userEventDAO = new UserEventDAO(); userEventDAO.create(userEvent); + + User user = new User(); + user.setId(ADMIN_USER_ID); - eventService.toggleSilence(e.getId(), ADMIN_USER_ID); + eventService.toggleSilence(e, user); List events = eventService.searchOld(e.getId(),EventType.EventSources.DATA_SOURCE,"*",alarmLevel,null,0,ADMIN_USER_ID,null); diff --git a/test/utils/mock/EventServiceMock.java b/test/utils/mock/EventServiceMock.java index 2b28d870ef..f546cf6e48 100644 --- a/test/utils/mock/EventServiceMock.java +++ b/test/utils/mock/EventServiceMock.java @@ -32,12 +32,12 @@ public void saveEvent(EventInstance event) { } @Override - public void ackEvent(int eventId, long time, int userId, int alternateAckSource, boolean signalAlarmLevelChange) { + public void ackEvent(EventInstance eventId, long time, User userId, int alternateAckSource, boolean signalAlarmLevelChange) { } @Override - public void ackEvent(int eventId, long time, int userId, int alternateAckSource) { + public void ackEvent(EventInstance eventId, long time, User userId, int alternateAckSource) { } @@ -224,8 +224,8 @@ public void deleteEventHandler(String handlerXid) { } @Override - public boolean toggleSilence(int eventId, int userId) { - return super.toggleSilence(eventId, userId); + public boolean toggleSilence(EventInstance event, User user) { + return false; } @Override @@ -300,4 +300,14 @@ public void setAdded(boolean added) { public void addEvent(EventInstance eventInstance) { this.events.add(eventInstance); } + + @Override + public void ackEvent(int eventId, long time, int userId, int alternateAckSource, boolean signalAlarmLevelChange) { + + } + + @Override + public void ackEvent(int eventId, long time, int userId, int alternateAckSource) { + + } } diff --git a/webapp-resources/env.properties b/webapp-resources/env.properties index 02301d935f..9261124db5 100644 --- a/webapp-resources/env.properties +++ b/webapp-resources/env.properties @@ -185,3 +185,4 @@ http.protocol.method.follow-redirects=false http.protocol.max-redirects=100 http.protocol.allow-circular-redirects=false http.protocol.timeout-ms=15000 +event.assign.enabled=true diff --git a/webapp-resources/messages_de.properties b/webapp-resources/messages_de.properties index aaf46bb440..0a8baf304d 100644 --- a/webapp-resources/messages_de.properties +++ b/webapp-resources/messages_de.properties @@ -3335,14 +3335,24 @@ systemsettings.webresource.uploads.path=Uploads images path systemsettings.webresource.graphics.path=Graphics images path systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" +annotation.api=REST API pointEdit.buttons.note.prefix=Hinweis: sql.warning.prefix=Achtung: views.noViews.prefix=jetzt. -annotation.api=REST API dsEdit.sql.statementLimit=Statement limit dsEdit.sql.statementLimit.warning=Setting the value 0 in the Statement limit field means there is no limit for the select query, which may lead to a serious application failure due to filling up the memory needed for the application to run. Do you confirm setting Statement limit equals 0? event.sms.failure=Failed to send sms titled "{0}" to "{1}". Message: "{2}" event.script.failure=Failed execute script: "{0}", Message: "{1}" event.system.sms=Sms send failure event.system.script=Script event handler failure -validate.valueRestored=Previous value restored \ No newline at end of file +validate.valueRestored=Previous value restored +events.assigneeByUser=- {0} +events.assign=Assign +events.unassign=Unassign +events.unassignedBy=Unassigned {0} by {1} for {2} +events.assignedBy=Assigned by {0} for {1} +common.assignee=Assignee +event.system.assigned=Assigned Event +event.system.unassigned=Unassigned Event +event.assign.enabled=Event Assign enabled +viewEdit.graphic.hideAssigneeColumn=Hide Assignee column \ No newline at end of file diff --git a/webapp-resources/messages_en.properties b/webapp-resources/messages_en.properties index 6e7b88e7d0..839dfe29fb 100644 --- a/webapp-resources/messages_en.properties +++ b/webapp-resources/messages_en.properties @@ -3338,14 +3338,24 @@ systemsettings.webresource.uploads.path=Uploads images path systemsettings.webresource.graphics.path=Graphics images path systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" +annotation.api=REST API pointEdit.buttons.note.prefix=Note: sql.warning.prefix=Warning: views.noViews.prefix=now. -annotation.api=REST API dsEdit.sql.statementLimit=Statement limit dsEdit.sql.statementLimit.warning=Setting the value 0 in the Statement limit field means there is no limit for the select query, which may lead to a serious application failure due to filling up the memory needed for the application to run. Do you confirm setting Statement limit equals 0? event.sms.failure=Failed to send sms titled "{0}" to "{1}". Message: "{2}" event.script.failure=Failed execute script: "{0}", Message: "{1}" event.system.sms=Sms send failure event.system.script=Script event handler failure -validate.valueRestored=Previous value restored \ No newline at end of file +validate.valueRestored=Previous value restored +events.assigneeByUser=- {0} +events.assign=Assign +events.unassign=Unassign +events.unassignedBy=Unassigned {0} by {1} for {2} +events.assignedBy=Assigned by {0} for {1} +common.assignee=Assignee +event.system.assigned=Assigned Event +event.system.unassigned=Unassigned Event +event.assign.enabled=Event Assign enabled +viewEdit.graphic.hideAssigneeColumn=Hide Assignee column \ No newline at end of file diff --git a/webapp-resources/messages_es.properties b/webapp-resources/messages_es.properties index 61ad6c2aaa..ec70aa6438 100644 --- a/webapp-resources/messages_es.properties +++ b/webapp-resources/messages_es.properties @@ -3378,10 +3378,10 @@ systemsettings.webresource.uploads.path=Uploads images path systemsettings.webresource.graphics.path=Graphics images path systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" +annotation.api=REST API pointEdit.buttons.note.prefix=Nota: sql.warning.prefix=Advertencia: views.noViews.prefix=nueva. -annotation.api=REST API dsEdit.sql.statementLimit=Statement limit dsEdit.sql.statementLimit.warning=Setting the value 0 in the Statement limit field means there is no limit for the select query, which may lead to a serious application failure due to filling up the memory needed for the application to run. Do you confirm setting Statement limit equals 0? event.sms.failure=Failed to send sms titled "{0}" to "{1}". Message: "{2}" @@ -3389,3 +3389,13 @@ event.script.failure=Failed execute script: "{0}", Message: "{1}" event.system.sms=Sms send failure event.system.script=Script event handler failure validate.valueRestored=Previous value restored +events.assigneeByUser=- {0} +events.assign=Assign +events.unassign=Unassign +events.unassignedBy=Unassigned {0} by {1} for {2} +events.assignedBy=Assigned by {0} for {1} +common.assignee=Assignee +event.system.assigned=Assigned Event +event.system.unassigned=Unassigned Event +event.assign.enabled=Event Assign enabled +viewEdit.graphic.hideAssigneeColumn=Hide Assignee column \ No newline at end of file diff --git a/webapp-resources/messages_fi.properties b/webapp-resources/messages_fi.properties index 622be38d3e..998df6d812 100644 --- a/webapp-resources/messages_fi.properties +++ b/webapp-resources/messages_fi.properties @@ -3463,10 +3463,10 @@ systemsettings.webresource.uploads.path=Uploads images path systemsettings.webresource.graphics.path=Graphics images path systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" +annotation.api=REST API pointEdit.buttons.note.prefix=Huom: sql.warning.prefix=Varoitus: views.noViews.prefix=nyt. -annotation.api=REST API dsEdit.sql.statementLimit=Statement limit dsEdit.sql.statementLimit.warning=Setting the value 0 in the Statement limit field means there is no limit for the select query, which may lead to a serious application failure due to filling up the memory needed for the application to run. Do you confirm setting Statement limit equals 0? event.sms.failure=Failed to send sms titled "{0}" to "{1}". Message: "{2}" @@ -3474,3 +3474,13 @@ event.script.failure=Failed execute script: "{0}", Message: "{1}" event.system.sms=Sms send failure event.system.script=Script event handler failure validate.valueRestored=Previous value restored +events.assigneeByUser=- {0} +events.assign=Assign +events.unassign=Unassign +events.unassignedBy=Unassigned {0} by {1} for {2} +events.assignedBy=Assigned by {0} for {1} +common.assignee=Assignee +event.system.assigned=Assigned Event +event.system.unassigned=Unassigned Event +event.assign.enabled=Event Assign enabled +viewEdit.graphic.hideAssigneeColumn=Hide Assignee column \ No newline at end of file diff --git a/webapp-resources/messages_fr.properties b/webapp-resources/messages_fr.properties index 808397af5d..639734ee6a 100644 --- a/webapp-resources/messages_fr.properties +++ b/webapp-resources/messages_fr.properties @@ -3332,10 +3332,10 @@ systemsettings.webresource.uploads.path=Uploads images path systemsettings.webresource.graphics.path=Graphics images path systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" +annotation.api=REST API pointEdit.buttons.note.prefix=Note: sql.warning.prefix=Attention: views.noViews.prefix=maintenant. -annotation.api=REST API dsEdit.sql.statementLimit=Statement limit dsEdit.sql.statementLimit.warning=Setting the value 0 in the Statement limit field means there is no limit for the select query, which may lead to a serious application failure due to filling up the memory needed for the application to run. Do you confirm setting Statement limit equals 0? event.sms.failure=Failed to send sms titled "{0}" to "{1}". Message: "{2}" @@ -3343,3 +3343,13 @@ event.script.failure=Failed execute script: "{0}", Message: "{1}" event.system.sms=Sms send failure event.system.script=Script event handler failure validate.valueRestored=Previous value restored +events.assigneeByUser=- {0} +events.assign=Assign +events.unassign=Unassign +events.unassignedBy=Unassigned {0} by {1} for {2} +events.assignedBy=Assigned by {0} for {1} +common.assignee=Assignee +event.system.assigned=Assigned Event +event.system.unassigned=Unassigned Event +event.assign.enabled=Event Assign enabled +viewEdit.graphic.hideAssigneeColumn=Hide Assignee column \ No newline at end of file diff --git a/webapp-resources/messages_lu.properties b/webapp-resources/messages_lu.properties index a7dddb36dc..bb090dfc2e 100644 --- a/webapp-resources/messages_lu.properties +++ b/webapp-resources/messages_lu.properties @@ -3351,10 +3351,10 @@ systemsettings.webresource.uploads.path=Uploads images path systemsettings.webresource.graphics.path=Graphics images path systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" +annotation.api=REST API pointEdit.buttons.note.prefix=Hinweis: sql.warning.prefix=Warnung: views.noViews.prefix=elo. -annotation.api=REST API dsEdit.sql.statementLimit=Statement limit dsEdit.sql.statementLimit.warning=Setting the value 0 in the Statement limit field means there is no limit for the select query, which may lead to a serious application failure due to filling up the memory needed for the application to run. Do you confirm setting Statement limit equals 0? event.sms.failure=Failed to send sms titled "{0}" to "{1}". Message: "{2}" @@ -3362,3 +3362,13 @@ event.script.failure=Failed execute script: "{0}", Message: "{1}" event.system.sms=Sms send failure event.system.script=Script event handler failure validate.valueRestored=Previous value restored +events.assigneeByUser=- {0} +events.assign=Assign +events.unassign=Unassign +events.unassignedBy=Unassigned {0} by {1} for {2} +events.assignedBy=Assigned by {0} for {1} +common.assignee=Assignee +event.system.assigned=Assigned Event +event.system.unassigned=Unassigned Event +event.assign.enabled=Event Assign enabled +viewEdit.graphic.hideAssigneeColumn=Hide Assignee column \ No newline at end of file diff --git a/webapp-resources/messages_nl.properties b/webapp-resources/messages_nl.properties index 7859e79d93..4e77d3a0ba 100644 --- a/webapp-resources/messages_nl.properties +++ b/webapp-resources/messages_nl.properties @@ -3453,10 +3453,10 @@ systemsettings.webresource.uploads.path=Uploads images path systemsettings.webresource.graphics.path=Graphics images path systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" +annotation.api=REST API pointEdit.buttons.note.prefix=Noot: sql.warning.prefix=Waarschuwing: views.noViews.prefix=nu een aan. -annotation.api=REST API dsEdit.sql.statementLimit=Statement limit dsEdit.sql.statementLimit.warning=Setting the value 0 in the Statement limit field means there is no limit for the select query, which may lead to a serious application failure due to filling up the memory needed for the application to run. Do you confirm setting Statement limit equals 0? event.sms.failure=Failed to send sms titled "{0}" to "{1}". Message: "{2}" @@ -3464,3 +3464,13 @@ event.script.failure=Failed execute script: "{0}", Message: "{1}" event.system.sms=Sms send failure event.system.script=Script event handler failure validate.valueRestored=Previous value restored +events.assigneeByUser=- {0} +events.assign=Assign +events.unassign=Unassign +events.unassignedBy=Unassigned {0} by {1} for {2} +events.assignedBy=Assigned by {0} for {1} +common.assignee=Assignee +event.system.assigned=Assigned Event +event.system.unassigned=Unassigned Event +event.assign.enabled=Event Assign enabled +viewEdit.graphic.hideAssigneeColumn=Hide Assignee column \ No newline at end of file diff --git a/webapp-resources/messages_pl.properties b/webapp-resources/messages_pl.properties index fe10b2296b..3d2ec0f914 100644 --- a/webapp-resources/messages_pl.properties +++ b/webapp-resources/messages_pl.properties @@ -3475,10 +3475,10 @@ systemsettings.webresource.uploads.path=Uploads images path systemsettings.webresource.graphics.path=Graphics images path systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" +annotation.api=REST API pointEdit.buttons.note.prefix=Notatka: sql.warning.prefix=Ostrzeżenie: views.noViews.prefix=teraz. -annotation.api=REST API dsEdit.sql.statementLimit=Statement limit dsEdit.sql.statementLimit.warning=Setting the value 0 in the Statement limit field means there is no limit for the select query, which may lead to a serious application failure due to filling up the memory needed for the application to run. Do you confirm setting Statement limit equals 0? event.sms.failure=Failed to send sms titled "{0}" to "{1}". Message: "{2}" @@ -3486,3 +3486,13 @@ event.script.failure=Failed execute script: "{0}", Message: "{1}" event.system.sms=Sms send failure event.system.script=Script event handler failure validate.valueRestored=Previous value restored +events.assigneeByUser=- {0} +events.assign=Assign +events.unassign=Unassign +events.unassignedBy=Unassigned {0} by {1} for {2} +events.assignedBy=Assigned by {0} for {1} +common.assignee=Assignee +event.system.assigned=Assigned Event +event.system.unassigned=Unassigned Event +event.assign.enabled=Event Assign enabled +viewEdit.graphic.hideAssigneeColumn=Hide Assignee column \ No newline at end of file diff --git a/webapp-resources/messages_pt.properties b/webapp-resources/messages_pt.properties index 5589ba8659..ebeb0467a7 100644 --- a/webapp-resources/messages_pt.properties +++ b/webapp-resources/messages_pt.properties @@ -3490,10 +3490,10 @@ systemsettings.webresource.uploads.path=Uploads images path systemsettings.webresource.graphics.path=Graphics images path systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" +annotation.api=REST API pointEdit.buttons.note.prefix=Nota: sql.warning.prefix=Alerta: views.noViews.prefix=agora. -annotation.api=REST API dsEdit.sql.statementLimit=Statement limit dsEdit.sql.statementLimit.warning=Setting the value 0 in the Statement limit field means there is no limit for the select query, which may lead to a serious application failure due to filling up the memory needed for the application to run. Do you confirm setting Statement limit equals 0? event.sms.failure=Failed to send sms titled "{0}" to "{1}". Message: "{2}" @@ -3501,3 +3501,13 @@ event.script.failure=Failed execute script: "{0}", Message: "{1}" event.system.sms=Sms send failure event.system.script=Script event handler failure validate.valueRestored=Previous value restored +events.assigneeByUser=- {0} +events.assign=Assign +events.unassign=Unassign +events.unassignedBy=Unassigned {0} by {1} for {2} +events.assignedBy=Assigned by {0} for {1} +common.assignee=Assignee +event.system.assigned=Assigned Event +event.system.unassigned=Unassigned Event +event.assign.enabled=Event Assign enabled +viewEdit.graphic.hideAssigneeColumn=Hide Assignee column \ No newline at end of file diff --git a/webapp-resources/messages_ru.properties b/webapp-resources/messages_ru.properties index 2dbf571da5..657574e998 100644 --- a/webapp-resources/messages_ru.properties +++ b/webapp-resources/messages_ru.properties @@ -3486,10 +3486,10 @@ systemsettings.webresource.uploads.path=Uploads images path systemsettings.webresource.graphics.path=Graphics images path systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" +annotation.api=REST API pointEdit.buttons.note.prefix=\u041f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u0435: sql.warning.prefix=\u0412\u043d\u0438\u043c\u0430\u043d\u0438\u0435: views.noViews.prefix=\u0441\u0435\u0439\u0447\u0430\u0441. -annotation.api=REST API dsEdit.sql.statementLimit=Statement limit dsEdit.sql.statementLimit.warning=Setting the value 0 in the Statement limit field means there is no limit for the select query, which may lead to a serious application failure due to filling up the memory needed for the application to run. Do you confirm setting Statement limit equals 0? event.sms.failure=Failed to send sms titled "{0}" to "{1}". Message: "{2}" @@ -3497,3 +3497,13 @@ event.script.failure=Failed execute script: "{0}", Message: "{1}" event.system.sms=Sms send failure event.system.script=Script event handler failure validate.valueRestored=Previous value restored +events.assigneeByUser=- {0} +events.assign=Assign +events.unassign=Unassign +events.unassignedBy=Unassigned {0} by {1} for {2} +events.assignedBy=Assigned by {0} for {1} +common.assignee=Assignee +event.system.assigned=Assigned Event +event.system.unassigned=Unassigned Event +event.assign.enabled=Event Assign enabled +viewEdit.graphic.hideAssigneeColumn=Hide Assignee column \ No newline at end of file diff --git a/webapp-resources/messages_zh.properties b/webapp-resources/messages_zh.properties index 79aab44325..764d5ae04b 100644 --- a/webapp-resources/messages_zh.properties +++ b/webapp-resources/messages_zh.properties @@ -3438,10 +3438,10 @@ systemsettings.webresource.uploads.path=Uploads images path systemsettings.webresource.graphics.path=Graphics images path systemsettings.webresource.uploads.path.wrong=Uploaded images save path must end with "uploads" or "uploads{0}" systemsettings.webresource.graphics.path.wrong=Graphics images path must end with "graphics" or "graphics{0}" +annotation.api=REST API pointEdit.buttons.note.prefix=\u6ce8\u610f: sql.warning.prefix=\u8b66\u544a\uff1a: views.noViews.prefix=\u9a6c\u4e0a\u521b\u5efa. -annotation.api=REST API dsEdit.sql.statementLimit=Statement limit dsEdit.sql.statementLimit.warning=Setting the value 0 in the Statement limit field means there is no limit for the select query, which may lead to a serious application failure due to filling up the memory needed for the application to run. Do you confirm setting Statement limit equals 0? event.sms.failure=Failed to send sms titled "{0}" to "{1}". Message: "{2}" @@ -3449,3 +3449,13 @@ event.script.failure=Failed execute script: "{0}", Message: "{1}" event.system.sms=Sms send failure event.system.script=Script event handler failure validate.valueRestored=Previous value restored +events.assigneeByUser=- {0} +events.assign=Assign +events.unassign=Unassign +events.unassignedBy=Unassigned {0} by {1} for {2} +events.assignedBy=Assigned by {0} for {1} +common.assignee=Assignee +event.system.assigned=Assigned Event +event.system.unassigned=Unassigned Event +event.assign.enabled=Event Assign enabled +viewEdit.graphic.hideAssigneeColumn=Hide Assignee column \ No newline at end of file