Source: component/notifications.js

/*! meta-admin/modules/notifications */
/*jslint
    browser, long
*/
/*global
    L
*/
import {} from "leaflet";
import {} from "leaflet.markercluster";
import Logger from "js-logger";
import ko from "knockout";
import protocol from "meta-core/modules/protocol";
import _ from "underscore";
import dates from "util-web/modules/dates";
import i18next from "util-web/modules/i18next";
import keyval from "util-web/modules/keyval";
import nav from "util-web/modules/nav";
import ui from "util-web/modules/ui";
import template from "./notifications.html";

const LOG = Logger.get("meta-admin/notifications");
const DEFAULT_CHANNELS = [protocol.CLIENT_STATE_CHANNEL, protocol.WATCHDOG_STATUS_CHANNEL];
const SUBSCRIBE_CHANNELS_KEY = "admin-sub-chan";

/**
 * notifications component.
 *
 * @module meta-admin/modules/component/notifications
 */
export default Object.freeze({
    template,
    viewModel: {
        createViewModel: function ({
            sizeLimit = 250,
            viewmodel
        }) {
            const sanitizeArray = function (inputString) {
                if (_.isEmpty(inputString) === false) {
                    return inputString.split(",");
                }
            };
            const spliceNotificationsToSizeLimit = function (notifications) {
                const length = notifications.length;
                if (sizeLimit < length) {
                    LOG.debug(`spliceNotificationsToSizeLimit, length=${length}, sizeLimit=${sizeLimit}`);
                    notifications.splice(sizeLimit, length - sizeLimit);
                }
            };
            const vm = {};

            vm.viewmodel = viewmodel;

            vm.activeTab = ko.observable();
            vm.errorReply = ko.observable();

            vm.copyClientId = (clientNotification) => navigator.clipboard.writeText(clientNotification.clientId);
            vm.copyMetaId = (metaNotification) => navigator.clipboard.writeText(metaNotification.log.metaId);

            vm.limit = ko.observable(50);

            const configDefaultChannels = vm.viewmodel.config?.notifications?.defaultChannels;
            const defaultChannels = (
                _.isEmpty(configDefaultChannels)
                    ? DEFAULT_CHANNELS
                    : configDefaultChannels
            );
            vm.channels = ko.observable(defaultChannels.join(","));

            vm.fromTimestamp = ko.observable();
            vm.toTimestamp = ko.observable();
            vm.fromTimestampSub = dates.insertDateTimes({
                fromDateObservable: vm.fromTimestamp,
                toDateObservable: vm.toTimestamp
            });
            vm.isLoading = ko.observable();

            vm.subscribeClient = ko.observable(true);
            vm.subscribeMeta = ko.observable(true);
            vm.subscribeMetas = ko.observable();
            vm.subscribeMetaTypeIds = ko.observable();
            vm.subscribeMetaChanges = ko.observable();
            vm.subscribeMetaDistanceFrom = ko.observable();
            vm.subscribeMetaDistanceMeters = ko.observable(500);
            vm.subscribeMetaDistanceCircle = L.circle([0, 0], {
                color: "#008B8B",
                radius: vm.subscribeMetaDistanceMeters()
            });
            vm.subscribeMetaDistanceComputed = ko.computed(function () {
                const from = vm.subscribeMetaDistanceFrom();
                const meters = vm.subscribeMetaDistanceMeters();
                if (from) {
                    vm.subscribeMetaDistanceCircle.setLatLng(from);
                    vm.subscribeMetaDistanceCircle.setRadius(Number(meters));
                } else {
                    vm.subscribeMetaDistanceCircle.setLatLng([0, 0]);
                }
            }).extend({rateLimit: 500});
            vm.subscribeChannels = ko.observable(DEFAULT_CHANNELS.join(","));

            vm.isSubscribeActive = ko.observable();
            vm.activateSubscribe = function () {
                vm.isSubscribeActive(true);
            };

            vm.pickSubscribeDistanceFrom = function () {
                const mapApi = vm.viewmodel.mapApiProvider();
                if (Boolean(mapApi) === false) {
                    return Promise.resolve(undefined);
                }
                vm.isSubscribeActive(false);
                return mapApi.pickPosition().then(function (position) {
                    vm.subscribeMetaDistanceFrom(position);
                    vm.isSubscribeActive(true);
                });
            };

            vm.clearSubscribeDistanceFrom = () => vm.subscribeMetaDistanceFrom(undefined);

            vm.subscribe = function (form) {
                const typeIds = sanitizeArray(vm.subscribeMetaTypeIds());
                const changes = sanitizeArray(vm.subscribeMetaChanges());
                const distanceFrom = vm.subscribeMetaDistanceFrom();
                if (ui.validateForm(form) === false) {
                    return;
                }
                let metaCriteria;
                if (typeIds || changes || distanceFrom) {
                    metaCriteria = protocol.subscriptionRequestMetaCriteria({
                        change: changes,
                        distanceFrom,
                        distanceMeters: Number(vm.subscribeMetaDistanceMeters()),
                        typeId: typeIds
                    });
                }
                const channels = sanitizeArray(vm.subscribeChannels());
                LOG.debug("subscribe");
                return vm.viewmodel.client.subscribe(protocol.subscriptionRequest({
                    channels,
                    client: vm.subscribeClient(),
                    meta: vm.subscribeMeta(),
                    metaCriteria
                })).then(function () {
                    vm.errorReply(undefined);
                    vm.isSubscribeActive(false);
                    ui.resetForm(form);
                    return keyval.set(SUBSCRIBE_CHANNELS_KEY, vm.subscribeChannels() || "");
                }, function (exc) {
                    vm.errorReply(JSON.stringify(exc, undefined, 4));
                });
            };

            vm.isClientNotificationActive = ko.observable();
            vm.clientNotificationChannel = ko.observable("");
            vm.clientNotificationAction = ko.observable("");
            vm.clientNotificationMessage = ko.observable();
            vm.clientNotificationData = ko.observable();
            vm.isClientNotificationDataValid = ko.observable(true);
            vm.clientNotificationDataString = vm.viewmodel.newComputedJsonField(vm.clientNotificationData, undefined, vm.isClientNotificationDataValid);

            vm.activateClientNotification = () => vm.isClientNotificationActive(true);

            vm.sendClientNotification = function (form) {
                if (ui.validateForm(form) === false) {
                    return;
                }
                const channel = vm.clientNotificationChannel();
                LOG.debug(`sendClientNotification, channel=${channel}`);
                vm.viewmodel.client.postClientNotification(protocol.clientNotification({
                    action: vm.clientNotificationAction(),
                    channel,
                    data: vm.clientNotificationData(),
                    message: vm.clientNotificationMessage(),
                    timestamp: dates.nowDateTimeString()
                }));
                vm.clientNotificationChannel("");
                vm.clientNotificationAction("");
                vm.clientNotificationMessage(undefined);
                vm.clientNotificationDataString(undefined);
                ui.resetForm(form);
            };

            vm.clientNotifications = ko.observableArray();
            vm.metaNotifications = ko.observableArray();

            vm.onClientNotification = function (clientNotification) {
                vm.clientNotifications.unshift(clientNotification);
            };
            vm[protocol.CLIENT_NOTIFICATION] = vm.onClientNotification;
            vm.onMetaNotification = function (metaNotification) {
                vm.metaNotifications.unshift(metaNotification);
            };
            vm[protocol.META_NOTIFICATION] = vm.onMetaNotification;
            const eventHandle = vm.viewmodel.client.registerOnEvent(vm);

            vm.cleanup = function () {
                spliceNotificationsToSizeLimit(vm.clientNotifications());
                spliceNotificationsToSizeLimit(vm.metaNotifications());
            };
            const cleanupIntervalHandle = setInterval(vm.cleanup, 10000);

            vm.navigateToTab = vm.viewmodel.navigateToTab.bind(undefined, vm.viewmodel.COMPONENTS.NOTIFICATIONS);

            vm.onNavUpdate = function (tab) {
                const activeTab = (
                    _.isEmpty(tab)
                        ? vm.viewmodel.NOTIFICATIONS_TABS.CLIENT
                        : tab
                );
                LOG.debug(`onNavUpdate, tab=${tab}, activeTab=${activeTab}`);
                vm.activeTab(activeTab);
            };

            nav.register({
                id: vm.viewmodel.COMPONENTS.NOTIFICATIONS,
                onUpdate: vm.onNavUpdate
            });

            vm.dispose = function () {
                LOG.debug("dispose");
                vm.viewmodel.client.unregisterOnEvent(eventHandle);
                vm.fromTimestampSub.dispose();
                clearInterval(cleanupIntervalHandle);
            };

            ko.when(function () {
                return _.isObject(vm.viewmodel.mapApiProvider());
            }).then(function () {
                vm.viewmodel.mapApiProvider().addOverlay(vm.subscribeMetaDistanceCircle, i18next.t("subscribe_distance"));
            });

            ko.when(function () {
                return vm.viewmodel.client.isAuth() && vm.viewmodel.isAdmin();
            }).then(function () {
                return keyval.get(SUBSCRIBE_CHANNELS_KEY);
            }).then(function (channels) {
                if (_.isEmpty(channels) === false) {
                    vm.subscribeChannels(channels);
                }
                return vm.subscribe();
            });

            return Object.freeze(vm);
        }
    }
});