• D
    diesel

    @shawn-hateley did you ever figure out a way to do this? thanks.

    posted in User help read more
  • D
    diesel

    @JoelHaggar so right now I have BACnet devices (>`150 data points) where I need to modify the incoming data (convert 4-20mA or 0-10V to whatever scale). The same needs to go for output data where it needs to get scaled.

    I'm using a meta data source which uses the BACnet devices/inputs or outputs as the external context data point and use the script to change things as needed.
    I prefer not using scripting because then I would have >200 scripting data sources (for each input/output) listed or if done in a few scripting data sources, the scripts would get pretty long, messy, and hard to edit quickly.

    The meta source approach works for inputs but is an issue for outputs as just setting the meta data source doesnt trigger its execution, so now need to make an event handler for every point.

    Maybe you may have a better approach?

    It seems Point Links would be ideal, but don't want to do that if they will be removed in a future version.

    posted in Mango Automation read more
  • D
    diesel

    Point Links in the Admin Dashboard (lastest version) just goes to data sources. Still accessible in the old UI. Is this on purpose?

    posted in Mango Automation read more
  • D
    diesel

    nevermind. I was using the eventsTable html and js from github, however, I should not have used files from master but from ver 3.7.x

    posted in Dashboard Designer & Custom AngularJS Pages read more
  • D
    diesel

    I'm want to modify the existing events table and delete a couple columns and add some formatting. Rather than starting from scratch, I'd like to take the existing eventTable.js and .html and make my own directive.
    I modified the code similar to this thread; https://forum.infiniteautomation.com/topic/3467/gauges-function-amcharts/26.
    It sort of works but Status for all is 'No RTN' and duration is 'ui.translate.instantaneous' (console shows no valid translation'.
    Below is my eventsTableMin.js. The userModule.js file has "mainModule.directive('eventsTableMin', eventsTableMin);" No changes yet to the html file.

    Any idea why this is not working correctly?

    /**
     * @copyright 2018 {@link http://infiniteautomation.com|Infinite Automation Systems, Inc.} All rights reserved.
     * @author Jared Wiltshire
     */
    
    //import eventsTableMinTemplate from './eventsTableMin.html';
    //import './eventsTableMin.css';
    //import moment from 'moment-timezone';
    
    define(['require', 'angular', 'moment-timezone'], function (require, angular, moment) {
        'use strict';
    
        /**
         * @ngdoc directive
         * @name ngMango.directive:EventsTableMin
         * @restrict E
         * @description
         * `<events-table-min></events-table-min>`
         * - Displays a list of Events in a table format.
         * - Allows for filtering of events by several attributes as explained below.
         * - Can be set to query for events within a specific date range.
         * - The table includes the ability to filter and sort by alarm level and timestamp.
         * - Events can be acknowledged one at a time or a button is shown to acknowledge all events matching the query.
         * - Note in usage examples below raw string literals are wrapped in single quotes where as variable names / numbers / booleans are not.
         * - <a ui-sref="ui.examples.utilities.eventsTableMin">View Demo</a>
         *
         * @param {number=} limit Set the initial limit of the pagination.
         * @param {number=} point-id Filter on the Id property of a point, use with `single-point="true"`.
         * @param {boolean=} single-point Set to `true` and use with point-id attribute to return events related to just a single Data Point.
         * @param {number=} event-id Filter on a specific Event Id, should return a single event.
         * @param {expression=} alarm-level Expression which should evaluate to a string. Filter on Alarm Level. Possible values are:
         *     `'NONE'`, `'INFORMATION'`, `'IMPORTANT'`, `'WARNING'`, `'URGENT'`, `'CRITICAL'`, `'LIFE_SAFETY'` or `'any'`.
         * @param {expression=} event-type Expression which should evaluate to a string. Filter on Event Type.
         *     Possible values are: `'DATA_POINT'`, `'DATA_SOURCE'`, `'SYSTEM'` or `'any'`.
         * @param {expression=} acknowledged Expression which should evaluate to a boolean or the string 'any'.
         *     Filter on whether the event has been acknowledged. Possible values are: `true`, `false` or `'any'` for either.
         * @param {string=} active-status Filter on Active Status. Possible values are: `'active'`, `'noRtn'`, `'normal'` or `'any'`.
         * @param {string=} sort Set the initial sorting column of the table. Possible values are:
         *     `'alarmLevel'`, `'activeTimestamp'`, `'message'` or `'acknowledged'`.
         *     Precede value with a negative (eg. `'-activeTimestamp'`) to reverse sorting.
         * @param {expression=} from Should evaluate to a date, moment or time-stamp. From time used for filtering by date range.
         *     Pass the value from a `<ma-date-picker>`.
         * @param {expression=} to Should evaluate to a date, moment or time-stamp. To time used for filtering by date range.
         * @param {boolean=} [date-filter=false] Turn on date filtering of events. Set value to `true` and use with from/to attribute to use.
         * @param {string=} timezone Display the timestamps in this timezone
    
         *
         * @usage
         * <!-- Example Using filters on Table Attributes -->
         * <events-table-min event-type="'SYSTEM'" alarm-level="'URGENT'" acknowledged="'any'"
         * active-status="'active' date-filter="true" from="fromTime" to="toTime" limit="50" 
         * sort="'-alarmLevel'"></events-table-min>
         *
         * <!-- Example For Restricting Events to those Related to a Data Point -->
         * <events-table-min single-point="true" point-id="myPoint.id" limit="5" from="fromTime" to="toTime"></events-table-min>
         */
    
        eventsTableMin.$inject = ['maEvents', 'maUserNotes', '$mdMedia', '$injector', '$sanitize', 'MA_DATE_FORMATS', 'MA_EVENT_LINK_INFO', '$timeout',
            'maEventHandler', 'maTranslate', 'maCssInjector'
        ];
    
        function eventsTableMin(Events, UserNotes, $mdMedia, $injector, $sanitize, mangoDateFormats, MA_EVENT_LINK_INFO, $timeout,
          EventHandler, Translate, cssInjector) {
            
            cssInjector.injectLink('/ui/css/eventsTableMin.css', 'eventsTableMin', '', false);
            const ANY_KEYWORD = 'any';
    
            class Equals {
                constructor(value, filter) {
                    this.value = value;
                    this.filter = filter;
                }
    
                test() {
                    if (this.filter == null || this.filter === ANY_KEYWORD)
                        return true;
                    return this.testOp();
                }
    
                testOp() {
                    return this.value === this.filter;
                }
            }
    
            class EqualsIgnoreZero extends Equals {
                test() {
                    if (this.filter === 0) return true;
                    return super.test();
                }
            }
    
            class GreaterThanEquals extends Equals {
                testOp() {
                    return this.value >= this.filter;
                }
            }
    
            class LessThan extends Equals {
                testOp() {
                    return this.value < this.filter;
                }
            }
    
            class InArray extends Equals {
                testOp() {
                    return Array.isArray(this.filter) && this.filter.includes(this.value);
                }
            }
    
            class EventsTableMinController {
                static get $$ngIsClass() {
                    return true;
                }
                static get $inject() {
                    return ['$scope', '$attrs', '$element', 'maEventTypeInfo'];
                }
    
                constructor($scope, $attrs, $element, EventTypeInfo) {
                    this.$scope = $scope;
                    this.$attrs = $attrs;
                    this.$element = $element;
    
                    this.$mdMedia = $mdMedia;
                    this.start = 0;
                    this.page = 1;
                    this.total = 0;
                    this.limit = 50;
                    this.events = [];
                    this.sort = '-activeTimestamp';
                    this.totalUnAcknowledged = 0;
                    this.linkInfo = MA_EVENT_LINK_INFO;
    
                    this.onPaginateBound = (...args) => this.onPaginate(...args);
                    this.onReorderBound = (...args) => this.onReorder(...args);
    
                    this.handlersForType = new EventTypeInfo.EventTypeMap();
                }
    
                $onInit() {
                    Events.notificationManager.subscribe((event, mangoEvent) => {
                        // temporary fix/work-around for audit events / DO_NOT_LOG events coming through websocket
                        if (mangoEvent.id < 0) {
                            return;
                        }
    
                        if (event.name === 'ACKNOWLEDGED' && (!this.acknowledged || this.acknowledged === ANY_KEYWORD) &&
                            this.totalUnAcknowledged > 0 && this.eventMatchesFilters(mangoEvent, true)) {
                            this.totalUnAcknowledged--;
                        }
    
                        if (this.eventMatchesFilters(mangoEvent)) {
                            // if event is already in current page replace it
                            this.removeEvent(mangoEvent.id, mangoEvent);
    
                            if (event.name === 'RAISED' && !this.dateFilter) {
                                if (this.sort === '-activeTimestamp' && this.start === 0) {
                                    // sorted by descending time and on the first page
                                    this.events.unshift(mangoEvent);
                                } else if (this.sort === 'activeTimestamp' && this.events.length < this.limit) {
                                    // sorted by ascending time and on the last page
                                    this.events.push(mangoEvent);
                                }
    
                                // ensure that we don't have more items than items per page
                                if (this.events.length > this.limit) {
                                    this.events.pop();
                                }
    
                                this.total++;
                                this.totalUnAcknowledged++;
                            }
                        } else {
                            // event may no longer match the filters, remove it if so
                            this.removeEvent(mangoEvent.id);
                        }
                    }, this.$scope, ['RAISED', 'ACKNOWLEDGED', 'RETURN_TO_NORMAL', 'DEACTIVATED']);
    
                    this.$scope.$on('maWatchdog', (event, current, previous) => {
                        if (current.status === 'LOGGED_IN') {
                            $timeout(() => {
                                this.doQuery();
                            }, 5000);
                        }
                    });
    
                    // TODO update with websocket
                    EventHandler.buildQuery()
                        .limit(10000)
                        .query().then(handlers => {
                            this.handlers = handlers;
                            this.rebuildHandlersMap();
                        });
                }
    
                $onChanges(changes) {
                    const numChanges = Object.keys(changes).length;
    
                    // don't re-query if only the to and from changed and we are not using the date filter
                    if ((changes.from || changes.to) && !this.dateFilter) {
                        const both = changes.from && changes.to;
                        if (both && numChanges === 2 || numChanges === 1) {
                            return;
                        }
                    }
    
                    this.doQuery();
                }
    
                rebuildHandlersMap() {
                    this.handlersForType.clear();
                    this.handlers.forEach(handler => {
                        handler.eventTypes.forEach(et => {
                            this.handlersForType.set(et, handler);
                        });
                    });
                }
    
                baseQuery() {
                    const queryBuilder = Events.buildQuery();
                    queryBuilder.addToRql = function (propertyName, op, propertyValue) {
                        if (propertyValue != null && propertyValue !== ANY_KEYWORD) {
                            this[op](propertyName, propertyValue);
                        }
                    };
                    queryBuilder.addToRql('alarmLevel', 'eq', this.alarmLevel);
                    queryBuilder.addToRql('id', 'eq', this.eventId);
    
                    if (this.pointId != null && this.pointId !== ANY_KEYWORD) {
                        queryBuilder.addToRql('eventType', 'eq', 'DATA_POINT');
                        queryBuilder.addToRql('referenceId1', 'eq', this.pointId);
                    } else if (Array.isArray(this.pointIds)) {
                        queryBuilder.addToRql('eventType', 'eq', 'DATA_POINT');
                        queryBuilder.addToRql('referenceId1', 'in', this.pointIds);
                    } else if (this.sourceId != null && this.sourceId !== ANY_KEYWORD) {
                        queryBuilder.addToRql('eventType', 'eq', 'DATA_SOURCE');
                        queryBuilder.addToRql('referenceId1', 'eq', this.sourceId);
                    } else {
                        const {
                            eventType,
                            subType,
                            referenceId1,
                            referenceId2
                        } = this.eventTypeObject || this;
    
                        queryBuilder.addToRql('eventType', 'eq', eventType);
                        queryBuilder.addToRql('subtypeName', 'eq', subType);
                        if (!Number.isFinite(referenceId1) || referenceId1 > 0) {
                            queryBuilder.addToRql('referenceId1', 'eq', referenceId1);
                        }
                        if (!Number.isFinite(referenceId2) || referenceId2 > 0) {
                            queryBuilder.addToRql('referenceId2', 'eq', referenceId2);
                        }
                    }
    
                    if (this.activeStatus === 'active') {
                        queryBuilder.addToRql('active', 'eq', true);
                    } else if (this.activeStatus === 'noRtn') {
                        queryBuilder.addToRql('rtnApplicable', 'eq', false);
                    } else if (this.activeStatus === 'normal') {
                        queryBuilder.addToRql('active', 'eq', false);
                    }
    
                    if (this.dateFilter) {
                        queryBuilder.addToRql('activeTimestamp', 'ge', this.from != null && this.from.valueOf());
                        queryBuilder.addToRql('activeTimestamp', 'lt', this.to != null && this.to.valueOf());
                    }
    
                    return queryBuilder;
                }
    
                displayQuery() {
                    const queryBuilder = this.baseQuery();
    
                    queryBuilder.addToRql('acknowledged', 'eq', this.acknowledged);
    
                    if (this.sort === 'activeRtn') {
                        queryBuilder.sort('rtnTimestamp', 'rtnApplicable');
                    } else if (this.sort === '-activeRtn') {
                        queryBuilder.sort('-rtnTimestamp', '-rtnApplicable');
                    } else if (this.sort != null) {
                        if (Array.isArray(this.sort)) {
                            queryBuilder.sort(...this.sort);
                        } else {
                            queryBuilder.sort(this.sort);
                        }
                    }
    
                    if (this.limit != null) {
                        queryBuilder.limit(this.limit, this.start || 0);
                    }
    
                    return queryBuilder;
                }
    
                doQuery() {
                    // dont query if element has a pointId attribute but its not defined
                    if (this.$attrs.hasOwnProperty('pointId') && this.pointId == null || this.$attrs.hasOwnProperty('sourceId') && this.sourceId == null) {
                        this.events = [];
                        this.total = 0;
                        this.totalUnAcknowledged = 0;
                        return;
                    }
    
                    // cancel the previous request if its ongoing
                    if (this.queryResource) {
                        this.queryResource.$cancelRequest();
                    }
    
                    const rqlQuery = this.displayQuery().toString();
                    const separator = rqlQuery.length ? '&' : '';
                    this.csvUrl = `/rest/v2/events?${rqlQuery}${separator}format=csv2`;
    
                    this.queryResource = Events.query({
                        rqlQuery
                    });
    
                    this.tableQueryPromise = this.queryResource.$promise.then(data => {
                        // Set Events For Table
                        this.events = data;
                        this.total = this.events.$total;
                        if (!this.hideAckButton) {
                            this.countUnacknowledged();
                        }
                    });
                }
    
                /**
                 *  Also query with limit(0) with RQLforAcknowldege to get a count of unacknowledged events to display on acknowledgeAll button
                 */
                countUnacknowledged() {
                    // cancel the previous request if its ongoing
                    if (this.countUnacknowledgedResource) {
                        this.countUnacknowledgedResource.$cancelRequest();
                    }
    
                    if (!this.acknowledged || this.acknowledged === ANY_KEYWORD) {
                        const rqlQuery = this.baseQuery()
                            .eq('acknowledged', false)
                            .limit(0)
                            .toString();
    
                        this.countUnacknowledgedResource = Events.query({
                            rqlQuery
                        }, null);
                        this.countUnacknowledgedResource.$promise.then(data => {
                            this.totalUnAcknowledged = data.$total;
                        });
                    } else {
                        this.totalUnAcknowledged = 0;
                    }
                }
    
                addNote($event, event) {
                    return UserNotes.addNote($event, 'Event', event.id).then((note) => {
                        event.comments.push(note);
                    });
                }
    
                onPaginate(page, limit) {
                    this.start = (page - 1) * limit;
                    this.doQuery();
                }
    
                onReorder() {
                    this.doQuery();
                }
    
                parseHTML(text) {
                    return $sanitize(text);
                }
    
                formatDate(date) {
                    const m = moment(date);
                    if (this.timezone) {
                        m.tz(this.timezone);
                    }
                    return m.format(mangoDateFormats.shortDateTimeSeconds);
                }
    
                removeEvent(eventId, replacement) {
                    const index = this.events.findIndex(item => item.id === eventId);
                    if (index >= 0) {
                        let removed;
                        if (replacement) {
                            removed = this.events.splice(index, 1, replacement);
                        } else {
                            removed = this.events.splice(index, 1);
                        }
                        return removed[0];
                    }
                }
    
                // Acknowledge single event
                acknowledgeEvent(event) {
                    const didMatchFilters = this.eventMatchesFilters(event);
    
                    event.$acknowledge().then(() => {
                        event.acknowledged = true;
    
                        if (!Events.notificationManager.socketConnected()) {
                            if (didMatchFilters && this.totalUnAcknowledged > 0) {
                                this.totalUnAcknowledged--;
                            }
    
                            if (!this.eventMatchesFilters(event)) {
                                this.removeEvent(event);
                            }
                        }
                    });
                }
    
                // Acknowledge multiple events
                acknowledgeEvents(events) {
                    events.forEach((event) => {
                        this.acknowledgeEvent(event);
                    });
                }
    
                // Acknowledge all matching RQL with button
                acknowledgeAll() {
                    const rqlQuery = this.baseQuery()
                        .eq('acknowledged', false)
                        .toString();
    
                    Events.acknowledgeViaRql({
                        rqlQuery
                    }, null).$promise.then((data) => {
                        if (data.count) {
                            // re-query
                            this.doQuery();
                        }
                    });
                }
    
                eventMatchesFilters(event, ignoreAckFilter) {
                    const tests = [];
    
                    if (this.$attrs.hasOwnProperty('pointId')) {
                        if (this.pointId == null) {
                            return false;
                        }
                        tests.push(new Equals(event.eventType.eventType, 'DATA_POINT'));
                    }
                    if (this.$attrs.hasOwnProperty('sourceId')) {
                        if (this.sourceId == null) {
                            return false;
                        }
                        tests.push(new Equals(event.eventType.eventType, 'DATA_SOURCE'));
                    }
    
                    const {
                        eventType,
                        subType,
                        referenceId1,
                        referenceId2
                    } = this.eventTypeObject || this;
    
                    let active, rtnApplicable;
                    switch (this.activeStatus) {
                        case 'noRtn':
                            rtnApplicable = false;
                            break;
                        case 'active':
                            rtnApplicable = true;
                            active = true;
                            break;
                        case 'normal':
                            rtnApplicable = true;
                            active = false;
                            break;
                    }
    
                    tests.push(
                        new Equals(event.eventType.eventType, eventType),
                        new Equals(event.eventType.subType, subType),
                        new EqualsIgnoreZero(event.eventType.referenceId1, referenceId1),
                        new EqualsIgnoreZero(event.eventType.referenceId2, referenceId2),
                        new Equals(event.alarmLevel, this.alarmLevel),
                        new Equals(event.active, active),
                        new Equals(event.rtnApplicable, rtnApplicable),
                        new Equals(event.eventType.referenceId1, this.pointId),
                        new Equals(event.eventType.referenceId1, this.sourceId),
                        new InArray(event.eventType.referenceId1, this.pointIds),
                        new Equals(event.id, this.eventId));
    
                    if (!ignoreAckFilter) {
                        tests.push(new Equals(event.acknowledged, this.acknowledged));
                    }
    
                    if (this.dateFilter) {
                        tests.push(
                            new GreaterThanEquals(event.activeTimestamp, this.from != null && this.from.valueOf()),
                            new LessThan(event.activeTimestamp, this.to != null && this.to.valueOf())
                        );
                    }
    
                    return tests.reduce((accum, test) => {
                        return accum && test.test();
                    }, true);
                }
    
                formatDuration(duration) {
                    if (duration < 1000) {
                        return Translate.trSync('ui.time.milliseconds', [duration]);
                    } else if (duration < 5000) {
                        return Translate.trSync('ui.time.seconds', [Math.round(duration / 100) / 10]);
                    } else if (duration < 60000) {
                        return Translate.trSync('ui.time.seconds', [Math.round(duration / 1000)]);
                    }
                    return moment.duration(duration).humanize();
                }
            }
    
            return {
                restrict: 'E',
                templateUrl: require.toUrl('../views/eventsTableMin.html'),
                scope: {},
                controller: EventsTableMinController,
                controllerAs: '$ctrl',
                bindToController: {
                    pointId: '<?',
                    pointIds: '<?',
                    eventId: '<?',
                    alarmLevel: '<?',
                    eventTypeObject: '<?',
                    eventType: '<?',
                    subType: '<?',
                    referenceId1: '<?',
                    referenceId2: '<?',
                    acknowledged: '<?',
                    activeStatus: '<?',
                    limit: '<?',
                    sort: '<?',
                    from: '<?',
                    to: '<?',
                    dateFilter: '<?',
                    timezone: '@',
                    hideLink: '@?',
                    hideAckButton: '<?',
                    hideCsvButton: '<?',
                    sourceId: '<?'
                },
                designerInfo: {
                    translation: 'ui.app.eventsTableMin',
                    icon: 'alarm'
                }
            };
        }
    
        return eventsTableMin;
    
    });
    
    

    posted in Dashboard Designer & Custom AngularJS Pages read more
  • D
    diesel

    Hey everyone, working on a supervisory control system and looking to see if anyone is will to share their dashboard designs. Just trying to get some ideas how to set up the layout. I know everyone's system is different but it would be cool to see how everyone else set their dashboard layout, just pics is fine.
    Thanks.

    posted in Dashboard Designer & Custom AngularJS Pages read more
  • D
    diesel

    ok i think i now understand (maybe). Do you mean that within the meta data point script, that adding itself as an external context point?
    This seems to work partially. When the event gets triggered, it changes the meta point value but does not run the script. I try to add it from the external context point list but then it throws an error when trying to get the meta data point value.

    posted in Scripting general Discussion read more
  • D
    diesel

    @MattFox sort of confused. How do I use the event to set a point value and trigger the script vs setting the meta data point value?
    just not following what you mean..

    posted in Scripting general Discussion read more
  • D
    diesel

    Using latest Mango version.
    I have a BACnet device that reports data, then I essentially copy that data into a meta data point to run a script to scale its value. I do the same for both inputs and outputs.
    One of my points takes data, and has an event handler so if it exceeds a value, the event is triggers and sends a binary value to the meta data point. The value of the point does change but does not run it's script and therefore the scaled value does not get sent to the BACnet.

    When a meta data point is changed, shouldn't the script within execute? It will only run when I force it in Data Point Details (refresh counterclockwise arrow thingy).

    If the script isn't supposed to execute, do you have any ideas on how to make it run when the meta data point value changes, or maybe a better way to approach how to scale BACnet data with event handlers?

    Thanks

    posted in Scripting general Discussion read more