Source: viewmodel.js

/*! meta-admin/modules/viewmodel */
/*jslint
    browser, long
*/
/*global
*/
/**
 * viewmodel module.
 *
 * @module meta-admin/modules/viewmodel
 */
import ko from "knockout";
import Logger from "js-logger";
import clientViewmodel from "meta-client/modules/viewmodel";
import metaModel from "meta-core/modules/model";
import _ from "underscore";
import ext from "util-web/modules/ext";
import nav from "util-web/modules/nav";

const LOG = Logger.get("meta-admin/viewmodel");
const CLIENT_LIST_ENTRY_KO_MAPPING = {
    copy: ["clientId"]
};
const CONFIG_KO_MAPPING = {
    config: clientViewmodel.newObjectMapping("config"),
    group: clientViewmodel.newObjectMapping("group"),
    user: clientViewmodel.newObjectMapping("user")
};
const GROUP_KO_MAPPING = {
    grants: clientViewmodel.newObjectMapping("grants")
};
const LOGGING_ENTRY_KO_MAPPING = {
    copy: ["properties"]
};
const META_KO_MAPPING = {
    copy: [metaModel.META_TYPE_PROPERTY, metaModel.META_ID],
    data: clientViewmodel.newObjectMapping("data"),
    paramSchema: clientViewmodel.newObjectMapping("paramSchema"),
    position: clientViewmodel.newObjectMapping("position"),
    resultSchema: clientViewmodel.newObjectMapping("resultSchema"),
    schema: clientViewmodel.newObjectMapping("schema"),
    value: clientViewmodel.newObjectMapping("value")
};
const USER_KO_MAPPING = {
    grants: clientViewmodel.newObjectMapping("grants"),
    otp: clientViewmodel.newObjectMapping("otp"),
    webAuthn: clientViewmodel.newObjectMapping("webAuthn")
};
const WATCHDOG_STATUS_KO_MAPPING = {
    updateStatus: clientViewmodel.newObjectMapping("updateStatus")
};

const formatJsonField = (obj) => JSON.stringify(obj, undefined, 4);
const newComputedJsonField = function (observable, defaultValue, validator) {
    let isValid = false;
    let json;
    const computed = ko.computed({
        read: function () {
            observable(); // Touch to keep dependency
            return json || formatJsonField(observable());
        },
        write: function (value) {
            json = value;
            if (_.isEmpty(value)) {
                observable(defaultValue);
                isValid = true;
            } else {
                try {
                    observable(JSON.parse(value));
                    isValid = true;
                } catch (ignore) {
                    isValid = false;
                }
            }
            if (ko.isWritableObservable(validator)) {
                validator(isValid);
            }
        }
    });
    computed.format = function () {
        if (isValid) {
            computed(formatJsonField(observable()));
        }
    };
    return computed;
};
export default Object.freeze(function ({client, config}) {
    const viewmodel = {};

    viewmodel.version = __version;

    viewmodel.client = client;
    viewmodel.config = config;

    viewmodel.newComputedJsonField = newComputedJsonField;
    viewmodel.sortByName = ext.simpleSort.bind(undefined, metaModel.META_NAME);

    viewmodel.activeComponent = ko.observable();

    viewmodel.COMPONENTS = Object.freeze({
        CLIENTS: "clients",
        EDITOR: "editor",
        FILES: "files",
        INDEX: "index",
        LOGGING: "logging",
        NOTIFICATIONS: "notifications",
        USERS: "users",
        WATCHDOG: "watchdog"
    });

    viewmodel.EDITOR_TABS = Object.freeze({
        ACTIONS: "actions",
        LOGS: "logs",
        OBJECTS: "objects",
        TYPES: "types"
    });

    viewmodel.LOGGING_TABS = Object.freeze({
        ENTRIES: "entries",
        METRICS: "metrics"
    });

    viewmodel.NOTIFICATIONS_TABS = Object.freeze({
        CLIENT: "client",
        META: "meta"
    });

    viewmodel.USERS_TABS = Object.freeze({
        CONFIGS: "configs",
        GROUPS: "groups",
        USERS: "users"
    });

    viewmodel.navigateToComponent = function (component, param = "") {
        if (viewmodel.activeComponent() === component) {
            return Promise.resolve(undefined);
        }
        LOG.debug(`navigateToComponent, component=${component}`);
        const params = nav.newParams();
        params.set(viewmodel.COMPONENTS.INDEX, component);
        params.set(component, param);
        return nav.pushState(params);
    };

    viewmodel.navigateToIndex = () => viewmodel.navigateToComponent(viewmodel.COMPONENTS.INDEX);
    viewmodel.navigateToClients = () => viewmodel.navigateToComponent(viewmodel.COMPONENTS.CLIENTS);
    viewmodel.navigateToEditor = () => viewmodel.navigateToComponent(viewmodel.COMPONENTS.EDITOR);
    viewmodel.navigateToFiles = () => viewmodel.navigateToComponent(viewmodel.COMPONENTS.FILES);
    viewmodel.navigateToLogging = () => viewmodel.navigateToComponent(viewmodel.COMPONENTS.LOGGING);
    viewmodel.navigateToNotifications = () => viewmodel.navigateToComponent(viewmodel.COMPONENTS.NOTIFICATIONS);
    viewmodel.navigateToUsers = () => viewmodel.navigateToComponent(viewmodel.COMPONENTS.USERS);
    viewmodel.navigateToWatchdog = () => viewmodel.navigateToComponent(viewmodel.COMPONENTS.WATCHDOG);

    viewmodel.navigateToTab = function (component, tab) {
        const params = nav.currentParams();
        params.set(component, tab);
        return nav.pushState(params);
    };

    viewmodel.metaToVm = function (meta) {
        const metaType = meta[metaModel.META_TYPE_PROPERTY];
        const metaForVm = Object.assign({}, meta);
        switch (metaType) {
        case metaModel.META_TYPE_ACTION:
            metaForVm.name = ko.observable(meta.name);
            metaForVm.paramSchema = ko.observable(meta.paramSchema);
            metaForVm.resultSchema = ko.observable(meta.resultSchema);
            break;
        case metaModel.META_TYPE_LOG:
            metaForVm.data = ko.observable(meta.data);
            metaForVm.message = ko.observable(meta.message);
            metaForVm.metaName = ko.observable(meta.metaName);
            break;
        case metaModel.META_TYPE_OBJECT:
            metaForVm.name = ko.observable(meta.name);
            metaForVm.position = ko.observable(meta.position);
            metaForVm.value = ko.observable(meta.value);
            break;
        case metaModel.META_TYPE_TYPE:
            metaForVm.name = ko.observable(meta.name);
            metaForVm.schema = ko.observable(meta.schema);
            break;
        }
        const vm = ko.mapping.fromJS(metaForVm, META_KO_MAPPING);
        switch (metaType) {
        case metaModel.META_TYPE_ACTION:
            vm.isParamSchemaValid = ko.observable(true);
            vm.paramSchemaString = newComputedJsonField(vm.paramSchema, undefined, vm.isParamSchemaValid);
            vm.isResultSchemaValid = ko.observable(true);
            vm.resultSchemaString = newComputedJsonField(vm.resultSchema, undefined, vm.isResultSchemaValid);
            break;
        case metaModel.META_TYPE_LOG:
            vm.isDataValid = ko.observable(true);
            vm.dataString = newComputedJsonField(vm.data, {}, vm.isDataValid);
            break;
        case metaModel.META_TYPE_OBJECT:
            vm.isValueValid = ko.observable(true);
            vm.valueString = newComputedJsonField(vm.value, {}, vm.isValueValid);
            break;
        case metaModel.META_TYPE_TYPE:
            vm.isSchemaValid = ko.observable(true);
            vm.schemaString = newComputedJsonField(vm.schema, {}, vm.isSchemaValid);
            break;
        }
        vm.deleted = ko.observable(false);
        return vm;
    };

    viewmodel.vmToMeta = (vm) => ko.mapping.toJS(vm);

    viewmodel.userToVm = function (user) {
        const vm = ko.mapping.fromJS(Object.assign({}, user, {
            grants: ko.observable(user.grants),
            otp: ko.observable(user.otp),
            webAuthn: ko.observable(user.webAuthn)
        }), USER_KO_MAPPING);
        vm.isGrantsValid = ko.observable(true);
        vm.grantsString = newComputedJsonField(vm.grants, {}, vm.isGrantsValid);
        vm.isDisabled = ko.pureComputed(function () {
            const grants = (
                vm.grantsString()
                ? JSON.parse(vm.grantsString())
                : undefined
            );
            if (_.isUndefined(grants) || vm.isGrantsValid() === false) {
                return true;
            }
            const loginGrant = _.get(grants, metaModel.metaGrantKeys.LOGIN, 0);
            return loginGrant < metaModel.metaGrantPermission.ALLOW;
        });
        vm.isNew = ko.observable(false);
        vm.deleted = ko.observable(false);
        return vm;
    };

    viewmodel.vmToUser = (vm) => ko.mapping.toJS(vm);

    viewmodel.groupToVm = function (group) {
        const vm = ko.mapping.fromJS(Object.assign({}, group, {
            grants: ko.observable(group.grants)
        }), GROUP_KO_MAPPING);
        vm.isUsersValid = ko.observable(true);
        vm.usersString = newComputedJsonField(vm.users, [], vm.isUsersValid);
        vm.isGrantsValid = ko.observable(true);
        vm.grantsString = newComputedJsonField(vm.grants, {}, vm.isGrantsValid);
        vm.isNew = ko.observable(false);
        vm.deleted = ko.observable(false);
        return vm;
    };

    viewmodel.vmToGroup = (vm) => ko.mapping.toJS(vm);

    viewmodel.userConfigToVm = function (userConfig) {
        const vm = ko.mapping.fromJS(Object.assign({}, userConfig, {
            config: ko.observable(userConfig.config),
            group: ko.observable(userConfig.group),
            user: ko.observable(userConfig.user)
        }), CONFIG_KO_MAPPING);
        vm.id = metaModel.forgeConfigId(userConfig);
        vm.isConfigValid = ko.observable(true);
        vm.configString = newComputedJsonField(vm.config, {}, vm.isConfigValid);
        vm.isNew = ko.observable(false);
        vm.deleted = ko.observable(false);
        return vm;
    };

    viewmodel.vmToUserConfig = (vm) => ko.mapping.toJS(vm);

    viewmodel.clientListEntryVm = function (clientListEntry) {
        return ko.mapping.fromJS(clientListEntry, CLIENT_LIST_ENTRY_KO_MAPPING);
    };

    viewmodel.loggingEntryVm = function (entry, colorProvider) {
        const vm = ko.mapping.fromJS(entry, LOGGING_ENTRY_KO_MAPPING);
        const clientId = _.get(entry, ["properties", "clientId"]);
        const properties = _.get(entry, "properties");
        vm.clientId = (
            "null" !== clientId
            ? clientId
            : undefined
        );
        vm.clientColor = colorProvider(vm.clientId);
        vm.isEngine = 0 < entry.loggerName.indexOf("engine");
        vm.propertyList = Object.keys(
            _.isObject(properties)
            ? properties
            : {}
        ).map(function (key) {
            return {key, value: entry.properties[key]};
        });
        vm.showDetails = ko.observable(false);
        vm.hide = ko.observable(false);
        return vm;
    };

    viewmodel.toLoggingEntryVm = function (entry, colorProvider) {
        return viewmodel.loggingEntryVm(entry, colorProvider);
    };

    viewmodel.fileInfoVm = function (fileInfo) {
        const vm = ko.mapping.fromJS(fileInfo);
        vm.sizeFormatted = ext.formatBytes(fileInfo.size);
        vm.isBackup = (
            _.isArray(fileInfo.tags)
            ? fileInfo.tags
            : []
        ).includes(metaModel.BACKUP_TAG);
        return vm;
    };

    viewmodel.vmToFileInfo = (vm) => ko.mapping.toJS(vm);

    viewmodel.watchdogStatusVm = function (id, status, position) {
        const vm = ko.mapping.fromJS(Object.assign({}, status, {
            updateStatus: ko.observable(status.updateStatus)
        }), WATCHDOG_STATUS_KO_MAPPING);
        vm.id = id;
        vm.position = ko.observable(position);
        const updateStatus = vm.updateStatus();
        if (updateStatus && _.has(updateStatus, "log") === false) {
            updateStatus.log = ["-"];
        }
        vm.hasAlerts = ko.pureComputed(() => _.isEmpty(vm.alerts()) === false);
        vm.icon = ko.computed(function () {
            if (vm.healthy() === false || vm.hasAlerts() === false) {
                return "WARN";
            }
            return "BLUE";
        });
        vm.rowspan = function () {
            let rowspan = 1;
            if (vm.hasAlerts()) {
                rowspan = rowspan + 1;
            }
            if (vm.updateStatus()) {
                rowspan = rowspan + 1;
            }
            return rowspan;
        };
        return vm;
    };

    viewmodel.toWatchdogStatusVm = function (meta) {
        const status = metaModel.watchdogStatus(meta.value);
        return viewmodel.watchdogStatusVm(meta.id, status, meta.position);
    };

    viewmodel.types = ko.observableArray();
    viewmodel.actions = ko.observableArray();

    viewmodel.pushTypesAndActions = function (types, actions) {
        viewmodel.types(types.sort(viewmodel.sortByName).map(viewmodel.metaToVm));
        LOG.debug(`pushTypesAndActions, types=${viewmodel.types().length}`);
        viewmodel.actions(actions.sort(viewmodel.sortByName).map(viewmodel.metaToVm));
        LOG.debug(`pushTypesAndActions, actions=${viewmodel.actions().length}`);
    };

    viewmodel.mapApiProvider = ko.observable();

    viewmodel.registerMapApiProvider = function (mapApi) {
        const mapPickPosition = mapApi.pickPosition;
        const pickPosition = function () {
            const activeComponent = viewmodel.activeComponent();
            let position;
            LOG.debug(`pickPosition, activeComponent=${activeComponent}`);
            viewmodel.navigateToIndex();
            return mapPickPosition().then(function (pickedPosition) {
                position = pickedPosition;
            }, function (exc) {
                LOG.error("pickPosition failed", exc);
            }).then(function () {
                return viewmodel.navigateToComponent(activeComponent);
            }).then(function () {
                return position;
            });
        };
        mapApi.editedObjectMarker.subscribe(function (metaId) {
            LOG.debug(`registerMapApiProvider, editedObjectMarker=${metaId}`);
            if (metaId) {
                viewmodel.navigateToEditor();
            }
        });
        viewmodel.mapApiProvider(Object.assign({pickPosition}, mapApi));
    };

    viewmodel.edited = ko.observable();

    viewmodel.metaCreateHook = ko.observable();
    viewmodel.newLog = () => viewmodel.metaCreateHook(metaModel.META_TYPE_LOG);
    viewmodel.newObject = (type) => viewmodel.metaCreateHook(metaModel.META_TYPE_OBJECT + ";" + type.id);

    viewmodel.userCreateHook = ko.observable();
    viewmodel.newUser = () => viewmodel.userCreateHook("user");
    viewmodel.newGroup = () => viewmodel.userCreateHook("group");
    viewmodel.newConfig = () => viewmodel.userCreateHook("userConfig");

    return Object.freeze(viewmodel);
});