Source: component/users.js

/*! meta-admin/modules/users */
/*jslint
    browser, long
*/
/*global
    Blob, confirm
*/
/**
 * users component.
 *
 * @module meta-admin/modules/component/users
 */
import ko from "knockout";
import Logger from "js-logger";
import objects from "meta-client/modules/objects";
import protocol from "meta-core/modules/protocol";
import model from "meta-core/modules/model";
import template from "./users.html";
import _ from "underscore";
import dates from "util-web/modules/dates";
import i18next from "util-web/modules/i18next";
import nav from "util-web/modules/nav";
import ui from "util-web/modules/ui";

const LOG = Logger.get("meta-admin/users");
const DEFAULT_GRANTS = Object.freeze(function () {
    const grants = {};
    Object.keys(model.metaGrantKeys).forEach(function (key) {
        grants[model.metaGrantKeys[key]] = 0;
    });
    return grants;
}());
const GRANT_LIST = Object.keys(model.metaGrantKeys).map(function (grantKey) {
    return {
        key: grantKey,
        value: model.metaGrantKeys[grantKey]
    };
});
const PERMISSION_LIST = Object.keys(model.metaGrantPermission).map(function (grantPermission) {
    return grantPermission + " = " + model.metaGrantPermission[grantPermission];
}).join("\n");

export default Object.freeze({
    template,
    viewModel: {
        createViewModel: function ({viewmodel}) {
            const vm = {};

            vm.metaGrants = GRANT_LIST;
            vm.metaGrantsList = GRANT_LIST.map((metaGrant) => metaGrant.key + " = " + metaGrant.value).join("\n");
            vm.permissionList = PERMISSION_LIST;
            vm.viewmodel = viewmodel;

            vm.activeTab = ko.observable();

            vm.userSearcher = objects({
                searchReplyObjectsPath: "users",
                sort: viewmodel.sortByName,
                toVm: (user) => vm.viewmodel.userToVm(model.metaUser(user))
            });
            vm.groupSearcher = objects({
                searchReplyObjectsPath: "groups",
                sort: viewmodel.sortByName,
                toVm: (group) => vm.viewmodel.groupToVm(model.metaUserGroup(group))
            });
            vm.configSearcher = objects({
                searchReplyObjectsPath: "configs",
                toVm: (userConfig) => vm.viewmodel.userConfigToVm(model.metaUserConfig(userConfig))
            });
            vm.editedUser = ko.observable();
            vm.removeOtp = ko.observable(false);
            vm.removeWebauthn = ko.observable(false);
            vm.isSavingUser = ko.observable(false);
            vm.editedGroup = ko.observable();
            vm.isSavingGroup = ko.observable(false);
            vm.editedConfig = ko.observable();
            vm.isSavingConfig = ko.observable(false);
            vm.saveSuccess = ko.observable();
            vm.errorReply = ko.observable();

            vm.cancel = function () {
                vm.editedUser(undefined);
                vm.editedGroup(undefined);
                vm.editedConfig(undefined);
            };

            vm.newUser = function (optionalUser) {
                const user = optionalUser || model.metaUser({
                    grants: DEFAULT_GRANTS,
                    name: "user" + dates.now().toMillis()
                });
                const userVm = vm.viewmodel.userToVm(user);
                LOG.debug("newUser");
                userVm.isNew(true);
                vm.editUser(userVm);
                vm.navigateToTab(vm.viewmodel.USERS_TABS.USERS);
            };

            vm.cloneUser = function () {
                const userVm = vm.editedUser();
                const user = vm.viewmodel.vmToUser(userVm);
                user.name = user.name + "clone";
                vm.newUser(user);
            };

            vm.userQuery = ko.observable();

            vm.searchUsersInternal = function (limit, clearSearch) {
                LOG.debug("searchUsersInternal");
                if (clearSearch) {
                    vm.userSearcher.clear();
                }
                return vm.userSearcher.processSearch(vm.viewmodel.client.userSearch(protocol.userSearchRequest({
                    cursor: vm.userSearcher.searchCursor(),
                    limit,
                    query: vm.userQuery()
                }))).then(function () {
                    vm.errorReply(undefined);
                }, function (exc) {
                    LOG.warn("searchUsersInternal failed", exc);
                    vm.errorReply(JSON.stringify(exc, undefined, 4));
                });
            };

            vm.searchUsers = function (form) {
                if (ui.validateForm(form) === false) {
                    return;
                }
                LOG.debug("searchUsers");
                return vm.searchUsersInternal(100, true).then(function () {
                    ui.resetForm(form);
                });
            };
            vm.loadMoreUsers = function () {
                LOG.debug("loadMoreUsers");
                return vm.searchUsersInternal(100, false);
            };
            vm.loadAllUsers = function () {
                LOG.debug("loadAllUsers");
                return vm.searchUsersInternal(0, false);
            };

            vm.editUser = function (user) {
                LOG.debug(`editUser, user=${user.name()}`);
                vm.cancel();
                vm.editedUser(user);
            };

            vm.insertGrant = function (id, value) {
                const textAreaId = (
                    vm.editedUser()
                    ? "userGrants"
                    : "groupGrants"
                );
                ui.insertText("#" + textAreaId, `\n    "${id}": ${value},`);
            };

            vm.selectedMetaGrant = ko.observable();
            vm.selectedMetaGrant.subscribe(function (metaGrant) {
                if (Boolean(metaGrant) === false) {
                    return;
                }
                LOG.debug(`selectedMetaGrant, metaGrant=${metaGrant}`);
                vm.insertGrant(metaGrant, String(model.metaGrantPermission.ALLOW));
                vm.selectedMetaGrant(undefined);
            });
            vm.selectedTypeGrant = ko.observable();
            vm.selectedTypeGrant.subscribe(function (type) {
                if (Boolean(type) === false) {
                    return;
                }
                LOG.debug(`selectedTypeGrant, type=${type}`);
                vm.insertGrant(type, String(model.metaGrantPermission.ALLOW));
                vm.selectedTypeGrant(undefined);
            });
            vm.selectedActionGrant = ko.observable();
            vm.selectedActionGrant.subscribe(function (action) {
                if (Boolean(action) === false) {
                    return;
                }
                LOG.debug(`selectedActionGrant, action=${action}`);
                vm.insertGrant(action, String(model.metaGrantPermission.ALLOW));
                vm.selectedActionGrant(undefined);
            });

            vm.saveUser = function (form) {
                const userVm = vm.editedUser();
                if (ui.validateForm(form) === false) {
                    return;
                }
                LOG.debug(`saveUser, user=${userVm.name()}`);
                vm.isSavingUser(true);
                if (vm.removeOtp()) {
                    delete userVm.otp;
                }
                if (vm.removeWebauthn()) {
                    delete userVm.webAuthn;
                }
                const user = vm.viewmodel.vmToUser(userVm);
                return vm.viewmodel.client.userWrite(protocol.userWriteRequest({
                    action: (
                        userVm.isNew()
                        ? protocol.WRITE_ACTION.CREATE
                        : protocol.WRITE_ACTION.UPDATE
                    ),
                    user
                })).then(function (reply) {
                    vm.errorReply(undefined);
                    vm.removeOtp(false);
                    vm.removeWebauthn(false);
                    userVm.isNew(false);
                    vm.editedUser(ko.mapping.fromJS(reply.user, userVm));
                    vm.saveSuccess("user_save_success");
                    ui.resetForm(form);
                }, function (exc) {
                    LOG.warn("saveUser failed", exc);
                    vm.errorReply(JSON.stringify(exc, undefined, 4));
                }).then(function () {
                    vm.isSavingUser(false);
                });
            };

            vm.isDeletingUser = ko.observable(false);

            vm.deleteUser = function () {
                const userVm = vm.editedUser();
                const user = vm.viewmodel.vmToUser(userVm);
                LOG.debug(`deleteUser, user=${userVm.name()}`);
                if (confirm(i18next.t("delete_user_confirmation")) === false) {
                    return;
                }
                vm.isDeletingUser(true);
                return vm.viewmodel.client.userWrite(protocol.userWriteRequest({
                    action: protocol.WRITE_ACTION.DELETE,
                    user
                })).then(function () {
                    vm.errorReply(undefined);
                    userVm.deleted(true);
                    vm.editedUser(undefined);
                }, function (exc) {
                    LOG.warn("deleteUser failed", exc);
                    vm.errorReply(JSON.stringify(exc, undefined, 4));
                }).then(function () {
                    vm.isDeletingUser(false);
                });
            };

            vm.newGroup = function (optionalGroup) {
                const group = optionalGroup || model.metaUserGroup({
                    grants: {},
                    name: "group" + dates.now().toMillis(),
                    users: []
                });
                const groupVm = vm.viewmodel.groupToVm(group);
                LOG.debug("newGroup");
                groupVm.isNew(true);
                vm.editGroup(groupVm);
                vm.navigateToTab(vm.viewmodel.USERS_TABS.GROUPS);
            };

            vm.cloneGroup = function () {
                const groupVm = vm.editedGroup();
                const group = vm.viewmodel.vmToGroup(groupVm);
                group.name = group.name + "clone";
                vm.newGroup(group);
            };

            vm.groupQuery = ko.observable();

            vm.searchGroupsInternal = function (limit, clearSearch) {
                if (clearSearch) {
                    vm.groupSearcher.clear();
                }
                return vm.groupSearcher.processSearch(vm.viewmodel.client.groupSearch(protocol.userGroupSearchRequest({
                    cursor: vm.groupSearcher.searchCursor(),
                    limit,
                    query: vm.groupQuery()
                }))).then(function () {
                    vm.errorReply(undefined);
                }, function (exc) {
                    LOG.warn("searchGroupsInternal failed", exc);
                    vm.errorReply(JSON.stringify(exc, undefined, 4));
                });
            };

            vm.searchGroups = function (form) {
                if (ui.validateForm(form) === false) {
                    return;
                }
                LOG.debug("searchGroups");
                return vm.searchGroupsInternal(100, true).then(function () {
                    ui.resetForm(form);
                });
            };
            vm.loadMoreGroups = function () {
                LOG.debug("loadMoreGroups");
                return vm.searchGroupsInternal(100, false);
            };
            vm.loadAllGroups = function () {
                LOG.debug("loadAllGroups");
                return vm.searchGroupsInternal(0, false);
            };

            vm.editGroup = function (group) {
                LOG.debug(`editGroup, group=${group.name()}`);
                vm.cancel();
                vm.editedGroup(group);
            };

            vm.saveGroup = function (form) {
                const groupVm = vm.editedGroup();
                if (ui.validateForm(form) === false) {
                    return;
                }
                LOG.debug(`saveGroup, group=${groupVm.name()}`);
                vm.isSavingGroup(true);
                const group = vm.viewmodel.vmToGroup(groupVm);
                return vm.viewmodel.client.groupWrite(protocol.userGroupWriteRequest({
                    action: (
                        groupVm.isNew()
                        ? protocol.WRITE_ACTION.CREATE
                        : protocol.WRITE_ACTION.UPDATE
                    ),
                    group
                })).then(function (reply) {
                    vm.errorReply(undefined);
                    vm.editedGroup(ko.mapping.fromJS(reply.group, groupVm));
                    vm.saveSuccess("group_save_success");
                    ui.resetForm(form);
                }, function (exc) {
                    LOG.warn("saveGroup failed", exc);
                    vm.errorReply(JSON.stringify(exc, undefined, 4));
                }).then(function () {
                    vm.isSavingGroup(false);
                });
            };

            vm.isDeletingGroup = ko.observable(false);

            vm.deleteGroup = function () {
                const groupVm = vm.editedGroup();
                const group = vm.viewmodel.vmToGroup(groupVm);
                LOG.debug(`deleteGroup, group=${groupVm.name()}`);
                if (confirm(i18next.t("delete_user_confirmation")) === false) {
                    return;
                }
                vm.isDeletingGroup(true);
                return vm.viewmodel.client.userWrite(protocol.userGroupWriteRequest({
                    action: protocol.WRITE_ACTION.DELETE,
                    group
                })).then(function () {
                    vm.errorReply(undefined);
                    groupVm.deleted(true);
                    vm.editedGroup(undefined);
                }, function (exc) {
                    LOG.warn("deleteGroup failed", exc);
                    vm.errorReply(JSON.stringify(exc, undefined, 4));
                }).then(function () {
                    vm.isDeletingGroup(false);
                });
            };

            vm.isUpdateActive = ko.observable(false);
            vm.isUpdating = ko.observable(false);

            vm.updateUsersAdd = ko.observable();
            vm.isUpdateUsersAddValid = ko.observable(true);
            vm.updateUsersAddString = viewmodel.newComputedJsonField(vm.updateUsersAdd, undefined, vm.isUpdateUsersAddValid);
            vm.updateUsersRemove = ko.observable();
            vm.isUpdateUsersRemoveValid = ko.observable(true);
            vm.updateUsersRemoveString = viewmodel.newComputedJsonField(vm.updateUsersRemove, undefined, vm.isUpdateUsersRemoveValid);

            vm.updateGrantsAdd = ko.observable();
            vm.isUpdateGrantsAddValid = ko.observable(true);
            vm.updateGrantsAddString = viewmodel.newComputedJsonField(vm.updateGrantsAdd, undefined, vm.isUpdateGrantsAddValid);
            vm.updateGrantsRemove = ko.observable();
            vm.isUpdateGrantsRemoveValid = ko.observable(true);
            vm.updateGrantsRemoveString = viewmodel.newComputedJsonField(vm.updateGrantsRemove, undefined, vm.isUpdateGrantsRemoveValid);

            vm.activateUpdate = function () {
                vm.updateUsersAdd(undefined);
                vm.updateUsersAddString.format();
                vm.updateUsersRemove(undefined);
                vm.updateUsersRemoveString.format();
                vm.updateGrantsAdd(undefined);
                vm.updateGrantsAddString.format();
                vm.updateGrantsRemove(undefined);
                vm.updateGrantsRemoveString.format();
                vm.isUpdateActive(true);
            };

            vm.update = function (form) {
                if (ui.validateForm(form) === false) {
                    return;
                }
                LOG.debug("update");
                vm.isUpdating(true);
                const isGroup = vm.editedGroup();
                const updatePromise = (
                    isGroup
                    ? vm.viewmodel.client.groupWriteUpdate(protocol.userGroupWriteUpdateRequest({
                        addGrants: vm.updateGrantsAdd(),
                        addUsers: vm.updateUsersAdd(),
                        group: vm.editedGroup().name(),
                        removeGrants: vm.updateGrantsRemove(),
                        removeUsers: vm.updateUsersRemove()
                    }))
                    : vm.viewmodel.client.userWriteUpdate(protocol.userWriteUpdateRequest({
                        addGrants: vm.updateGrantsAdd(),
                        removeGrants: vm.updateGrantsRemove(),
                        user: vm.editedUser().name()
                    }))
                );
                return updatePromise.then(function () {
                    // TODO reload edited user/group
                    vm.errorReply(undefined);
                    vm.isUpdateActive(false);
                    vm.saveSuccess(
                        isGroup
                        ? "group_save_success"
                        : "user_save_success"
                    );
                    ui.resetForm(form);
                }, function (exc) {
                    LOG.warn("update failed", exc);
                    vm.errorReply(JSON.stringify(exc, undefined, 4));
                }).then(function () {
                    vm.isUpdating(false);
                });
            };

            vm.newConfig = function (optionalUserConfig) {
                const userConfig = optionalUserConfig || model.metaUserConfig({config: {}});
                const configVm = vm.viewmodel.userConfigToVm(userConfig);
                LOG.debug("newConfig");
                configVm.isNew(true);
                vm.editConfig(configVm);
                vm.navigateToTab(vm.viewmodel.USERS_TABS.CONFIGS);
            };

            vm.cloneConfig = function () {
                const configVm = vm.editedConfig();
                const userConfig = vm.viewmodel.vmToUserConfig(configVm);
                vm.newConfig(userConfig);
            };

            vm.configQuery = ko.observable();

            vm.searchConfigsInternal = function (limit, clearSearch) {
                if (clearSearch) {
                    vm.configSearcher.clear();
                }
                return vm.configSearcher.processSearch(vm.viewmodel.client.configSearch(protocol.userConfigSearchRequest({
                    cursor: vm.configSearcher.searchCursor(),
                    limit,
                    query: vm.configQuery()
                }))).then(function () {
                    vm.errorReply(undefined);
                }, function (exc) {
                    LOG.warn("searchConfigsInternal failed", exc);
                    vm.errorReply(JSON.stringify(exc, undefined, 4));
                });
            };

            vm.searchConfigs = function (form) {
                if (ui.validateForm(form) === false) {
                    return;
                }
                LOG.debug("searchConfigs");
                return vm.searchConfigsInternal(100, true).then(function () {
                    ui.resetForm(form);
                });
            };
            vm.loadMoreConfigs = function () {
                LOG.debug("loadMoreConfigs");
                return vm.searchConfigsInternal(100, false);
            };
            vm.loadAllConfigs = function () {
                LOG.debug("loadAllConfigs");
                return vm.searchConfigsInternal(0, false);
            };

            vm.editConfig = function (userConfig) {
                LOG.debug(`editConfig, id=${userConfig.id}`);
                vm.cancel();
                vm.editedConfig(userConfig);
            };

            vm.saveConfig = function (form) {
                const configVm = vm.editedConfig();
                if (ui.validateForm(form) === false) {
                    return;
                }
                LOG.debug(`saveConfig, id=${configVm.id}`);
                vm.isSavingConfig(true);
                const userConfig = vm.viewmodel.vmToUserConfig(configVm);
                return vm.viewmodel.client.configWrite(protocol.userConfigWriteRequest({
                    action: (
                        configVm.isNew()
                        ? protocol.WRITE_ACTION.CREATE
                        : protocol.WRITE_ACTION.UPDATE
                    ),
                    config: userConfig
                })).then(function (reply) {
                    vm.errorReply(undefined);
                    vm.editedConfig(ko.mapping.fromJS(reply.config, configVm));
                    vm.saveSuccess("config_save_success");
                    ui.resetForm(form);
                }, function (exc) {
                    LOG.warn("saveConfig failed", exc);
                    vm.errorReply(JSON.stringify(exc, undefined, 4));
                }).then(function () {
                    vm.isSavingConfig(false);
                });
            };

            vm.isDeletingConfig = ko.observable(false);

            vm.deleteConfig = function () {
                const configVm = vm.editedConfig();
                const userConfig = vm.viewmodel.vmToUserConfig(configVm);
                LOG.debug(`deleteConfig, id=${configVm.id}`);
                if (confirm(i18next.t("delete_user_confirmation")) === false) {
                    return;
                }
                vm.isDeletingConfig(true);
                return vm.viewmodel.client.configWrite(protocol.userConfigWriteRequest({
                    action: protocol.WRITE_ACTION.DELETE,
                    config: userConfig
                })).then(function () {
                    vm.errorReply(undefined);
                    configVm.deleted(true);
                    vm.editedConfig(undefined);
                }, function (exc) {
                    LOG.warn("deleteConfig failed", exc);
                    vm.errorReply(JSON.stringify(exc, undefined, 4));
                }).then(function () {
                    vm.isDeletingConfig(false);
                });
            };

            vm.viewmodel.userCreateHook.subscribe(function (create) {
                if (_.isEmpty(create)) {
                    return;
                }
                return vm.viewmodel.navigateToUsers().then(function () {
                    if ("user" === create) {
                        vm.newUser();
                    }
                    if ("group" === create) {
                        vm.newGroup();
                    }
                    if ("userConfig" === create) {
                        vm.newConfig();
                    }
                    vm.viewmodel.userCreateHook(undefined);
                });
            });

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

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

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

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