$(function () { function AccessViewModel(parameters) { var access = this; access.loginState = parameters[0]; var GROUP_ADMINS = "admins"; var GROUP_GUESTS = "guests"; access.users = (function () { var self = {}; self.listHelper = new ItemListHelper("users", { name: function (a, b) { if (a["name"].toLocaleLowerCase() < b["name"].toLocaleLowerCase()) return -1; if (a["name"].toLocaleLowerCase() > b["name"].toLocaleLowerCase()) return 1; return 0; } }, {}, "name", [], [], CONFIG_USERSPERPAGE); self.emptyUser = { name: "", active: false }; self.currentUser = ko.observable(self.emptyUser).extend({ notify: "always" }); self.editor = { name: ko.observable(undefined), groups: ko.observableArray([]), permissions: ko.observableArray([]), password: ko.observable(undefined), repeatedPassword: ko.observable(undefined), passwordMismatch: ko.pureComputed(function () { return self.editor.password() !== self.editor.repeatedPassword(); }), apikey: ko.observable(undefined), active: ko.observable(undefined), permissionSelectable: function (permission) { return true; }, permissionSelected: function (permission) { var index = self.editor.permissions().indexOf(permission); return index >= 0; }, togglePermission: function (permission) { var permissions = self.editor.permissions(); var index = permissions.indexOf(permission); if (index < 0) { permissions.push(permission); } else { permissions.splice(index, 1); } self.editor.permissions(permissions); }, groupSelected: function (group) { var index = self.editor.groups().indexOf(group); return index >= 0; }, toggleGroup: function (group) { var groups = self.editor.groups(); var index = groups.indexOf(group); if (index < 0) { groups.push(group); } else { groups.splice(index, 1); } self.editor.groups(groups); }, joinedGroupPermissions: function (group) { return access.permissionList(group); }, header: ko.observable(undefined), new: ko.observable(true), confirm: undefined, valid: ko.pureComputed(function () { return (self.editor.name() && self.editor.name().trim() && (!self.editor.new() || (self.editor.password() && self.editor.password().trim() && !self.editor.passwordMismatch()))); }), dangerRestricted: function () { return false; }, dangerRestrictedText: gettext("This user may not have dangerous permissions.") }; self.userEditorDialog = undefined; self.changePasswordDialog = undefined; self.currentUser.subscribe(function (newValue) { if (newValue === undefined) { self.editor.name(undefined); self.editor.groups(access.groups.defaults.slice(0)); self.editor.permissions([]); self.editor.active(true); self.editor.apikey(undefined); self.editor.header(gettext("Add user")); self.editor.new(true); self.editor.confirm = self.confirmAddUser; } else { self.editor.name(newValue.name); self.editor.groups(newValue.groups.slice(0)); self.editor.permissions(newValue.permissions.slice(0)); self.editor.active(newValue.active); self.editor.apikey(newValue.apikey); self.editor.header(_.sprintf(gettext('Edit user "%(name)s"'), { name: newValue.name })); self.editor.new(false); self.editor.confirm = self.confirmEditUser; } self.editor.password(undefined); self.editor.repeatedPassword(undefined); }); self.requestData = function () { if (!CONFIG_ACCESS_CONTROL) return; if (!access.loginState.hasPermissionKo(access.permissions.ADMIN)) return; return OctoPrint.access.users.list().done(self.fromResponse); } ; self.fromResponse = function (response) { self.listHelper.updateItems(response.users); } ; self.showAddUserDialog = function () { if (!CONFIG_ACCESS_CONTROL) return; self.currentUser(undefined); $('ul.nav-pills a[data-toggle="tab"]:first', self.userEditorDialog).tab("show"); self.userEditorDialog.modal({ minHeight: function () { return Math.max($.fn.modal.defaults.maxHeight() - 80, 250); } }).css({ "margin-left": function () { return -($(this).width() / 2); } }); } ; self.confirmAddUser = function () { if (!CONFIG_ACCESS_CONTROL) return; var user = { name: self.editor.name(), password: self.editor.password(), groups: self.editor.groups(), permissions: self.editor.permissions(), active: self.editor.active() }; self.addUser(user).done(function () { self.currentUser(undefined); self.userEditorDialog.modal("hide"); }); } ; self.showEditUserDialog = function (user) { if (!CONFIG_ACCESS_CONTROL) return; var process = function (user) { self.currentUser(user); $('ul.nav-pills a[data-toggle="tab"]:first', self.userEditorDialog).tab("show"); self.userEditorDialog.modal({ minHeight: function () { return Math.max($.fn.modal.defaults.maxHeight() - 80, 250); } }).css({ "margin-left": function () { return -($(this).width() / 2); } }); }; OctoPrint.users.get(user.name).done(function (data) { process(data); }).fail(function () { log.warn("Could not fetch current user data, proceeding with client side data copy"); process(user); }); } ; self.confirmEditUser = function () { if (!CONFIG_ACCESS_CONTROL) return; var user = self.currentUser(); user.active = self.editor.active(); user.groups = self.editor.groups(); user.permissions = self.editor.permissions(); self.updateUser(user).done(function () { self.currentUser(undefined); self.userEditorDialog.modal("hide"); }); } ; self.showChangePasswordDialog = function (user) { if (!CONFIG_ACCESS_CONTROL) return; self.currentUser(user); self.changePasswordDialog.modal("show"); } ; self.confirmChangePassword = function () { if (!CONFIG_ACCESS_CONTROL) return; self.updatePassword(self.currentUser().name, self.editor.password()).done(function () { self.currentUser(undefined); self.changePasswordDialog.modal("hide"); }); } ; self.confirmGenerateApikey = function () { if (!CONFIG_ACCESS_CONTROL) return; self.generateApikey(self.currentUser().name).done(function (response) { self._updateApikey(response.apikey); }); } ; self.copyApikey = function () { copyToClipboard(self.editor.apikey()); } ; self._updateApikey = function (apikey) { self.editor.apikey(apikey); self.requestData(); } ; self.confirmDeleteApikey = function () { if (!CONFIG_ACCESS_CONTROL) return; self.deleteApikey(self.currentUser().name).done(function () { self._updateApikey(undefined); }); } ; self.onStartup = function () { self.userEditorDialog = $("#settings-usersEditorDialog"); self.changePasswordDialog = $("#settings-usersDialogChangePassword"); } ; self.addUser = function (user) { if (!user) { throw OctoPrint.InvalidArgumentError("user must be set"); } if (!access.loginState.hasPermissionKo(access.permissions.ADMIN)) return $.Deferred().reject("You are not authorized to perform this action").promise(); return OctoPrint.access.users.add(user).done(self.fromResponse); } ; self.removeUser = function (user) { if (!user) { throw OctoPrint.InvalidArgumentError("user must be set"); } if (!access.loginState.hasPermissionKo(access.permissions.ADMIN)) return $.Deferred().reject("You are not authorized to perform this action").promise(); if (user.name === access.loginState.username()) { new PNotify({ title: gettext("Not possible"), text: gettext("You may not delete your own account."), type: "error" }); return $.Deferred().reject("You may not delete your own account").promise(); } showConfirmationDialog({ title: gettext("Are you sure?"), message: _.sprintf(gettext('You are about to delete the user "%(name)s".'), { name: user.name }), proceed: gettext("Delete"), onproceed: function () { OctoPrint.access.users.delete(user.name).done(self.fromResponse); } }); } ; self.updateUser = function (user) { if (!user) { throw OctoPrint.InvalidArgumentError("user must be set"); } return OctoPrint.access.users.update(user.name, user.active, user.admin, user.permissions, user.groups).done(self.fromResponse); } ; self.updatePassword = function (username, password) { return OctoPrint.access.users.changePassword(username, password); } ; self.generateApikey = function (username) { return OctoPrint.access.users.generateApiKey(username).done(function () { self.requestData(); }); } ; self.deleteApikey = function (username) { return OctoPrint.access.users.resetApiKey(username); } ; return self; } )(); access.groups = (function () { var self = {}; self.listHelper = new ItemListHelper("groups", { name: function (a, b) { if (a["name"].toLocaleLowerCase() < b["name"].toLocaleLowerCase()) return -1; if (a["name"].toLocaleLowerCase() > b["name"].toLocaleLowerCase()) return 1; return 0; } }, {}, "name", [], [], CONFIG_GROUPSPERPAGE); self.groupsList = self.listHelper.items; self.lookup = {}; self.defaults = []; self.emptyGroup = { name: "" }; self.currentGroup = ko.observable(self.emptyGroup); self.editor = { key: ko.observable(undefined), name: ko.observable(undefined), description: ko.observable(undefined), permissions: ko.observableArray([]), subgroups: ko.observableArray([]), default: ko.observable(false), permissionSelectable: function (permission) { return self.editor.key() !== GROUP_GUESTS || !permission.dangerous; }, permissionSelected: function (permission) { var index = self.editor.permissions().indexOf(permission); return index >= 0; }, togglePermission: function (permission) { var permissions = self.editor.permissions(); var index = permissions.indexOf(permission); if (index < 0) { permissions.push(permission); } else { permissions.splice(index, 1); } self.editor.permissions(permissions); }, subgroupSelectable: function (subgroup) { return (self.editor.key() !== subgroup.key && (self.editor.key() !== GROUP_GUESTS || !subgroup.dangerous)); }, subgroupSelected: function (subgroup) { var index = self.editor.subgroups().indexOf(subgroup); return index >= 0; }, toggleSubgroup: function (subgroup) { var subgroups = self.editor.subgroups(); var index = subgroups.indexOf(subgroup); if (index < 0) { subgroups.push(subgroup); } else { subgroups.splice(index, 1); } self.editor.subgroups(subgroups); }, joinedGroupPermissions: function (group) { return access.permissionList(group); }, header: ko.observable(undefined), new: ko.observable(true), confirm: undefined, valid: ko.pureComputed(function () { return self.editor.name() && self.editor.name().trim(); }), dangerRestricted: function () { return self.editor.key() === GROUP_GUESTS; }, dangerRestrictedText: gettext("This group may not have dangerous permissions or subgroups.") }; self.groupEditorDialog = undefined; self.groupsList.subscribe(function (oldValue) { if (oldValue === undefined || oldValue.length === 0) return; oldValue.forEach(function (p) { delete self[p.key.toUpperCase()]; }); }, null, "beforeChange"); self.groupsList.subscribe(function (newValue) { if (newValue === undefined) return; newValue.forEach(function (g) { var needs = []; g.permissions.forEach(function (p) { for (var key in p.needs) { p.needs[key].forEach(function (value) { needs.push(access.permissions.need(key, value)); }); } }); if (needs.length > 0) { self.registerGroup(g.key.toUpperCase(), needs); } }); }); self.registerGroup = function (name, group) { Object.defineProperty(self, name, { value: group, enumerable: true, configurable: true }); } ; self.currentGroup.subscribe(function (newValue) { if (newValue === undefined) { self.editor.key(undefined); self.editor.name(undefined); self.editor.description(undefined); self.editor.permissions([]); self.editor.subgroups([]); self.editor.default(false); self.editor.header(gettext("Add group")); self.editor.new(true); self.editor.confirm = self.confirmAddGroup; } else { self.editor.key(newValue.key); self.editor.name(newValue.name); self.editor.description(newValue.description); self.editor.permissions(newValue.permissions.slice(0)); self.editor.subgroups(newValue.subgroups.slice(0)); self.editor.default(newValue.default); self.editor.header(_.sprintf(gettext('Edit group "%(name)s"'), { name: newValue.name })); self.editor.new(false); self.editor.confirm = self.confirmEditGroup; } }); self.requestData = function () { return OctoPrint.access.groups.list().done(self.fromResponse); } ; self.fromResponse = function (response) { var lookup = {}; var defaults = []; _.each(response.groups, function (group) { lookup[group.key] = group; if (group.default) { defaults.push(group.key); } }); self.lookup = lookup; self.defaults = defaults; self.listHelper.updateItems(response.groups); } ; self.showAddGroupDialog = function () { self.currentGroup(undefined); $('ul.nav-pills a[data-toggle="tab"]:first', self.groupEditorDialog).tab("show"); self.groupEditorDialog.modal({ minHeight: function () { return Math.max($.fn.modal.defaults.maxHeight() - 80, 250); } }).css({ "margin-left": function () { return -($(this).width() / 2); } }); } ; self.confirmAddGroup = function () { var group = { key: self.editor.name().toLowerCase().replace(/[^a-z0-9_ ]/g, "").replace(/ /g, "_"), name: self.editor.name(), description: self.editor.description(), permissions: self.editor.permissions(), subgroups: self.editor.subgroups(), default: self.editor.default() }; self.addGroup(group).done(function () { self.currentGroup(undefined); self.groupEditorDialog.modal("hide"); }); } ; self.showEditGroupDialog = function (group) { if (!group.changeable) return; self.currentGroup(group); $('ul.nav-pills a[data-toggle="tab"]:first', self.groupEditorDialog).tab("show"); self.groupEditorDialog.modal({ minHeight: function () { return Math.max($.fn.modal.defaults.maxHeight() - 80, 250); } }).css({ "margin-left": function () { return -($(this).width() / 2); } }); } ; self.confirmEditGroup = function () { var group = self.currentGroup(); var data = { key: group.key, name: group.name, description: self.editor.description(), permissions: self.editor.permissions(), subgroups: self.editor.subgroups(), default: self.editor.default() }; self.updateGroup(data).done(function () { self.currentGroup(undefined); self.groupEditorDialog.modal("hide"); }); } ; self.onStartup = function () { self.groupEditorDialog = $("#settings-groupsEditorDialog"); } ; self.addGroup = function (group) { if (!group) { throw OctoPrint.InvalidArgumentError("group must be set"); } return OctoPrint.access.groups.add(group).done(self.fromResponse); } ; self.removeGroup = function (group) { if (!group) { throw OctoPrint.InvalidArgumentError("group must be set"); } if (!group.removable) return; showConfirmationDialog({ title: gettext("Are you sure?"), message: _.sprintf(gettext('You are about to delete the group "%(name)s".'), { name: group.name }), proceed: gettext("Delete"), onproceed: function () { OctoPrint.access.groups.delete(group.key).done(function (response) { self.fromResponse(response); access.users.requestData(); }); } }); } ; self.updateGroup = function (group) { if (!group) { throw OctoPrint.InvalidArgumentError("group must be set"); } return OctoPrint.access.groups.update(group).done(self.fromResponse); } ; return self; } )(); access.permissions = (function () { var self = {}; self.need = function (method, value) { return { method: method, value: value }; } ; self.roleNeed = function (value) { return self.need("role", value); } ; self.permissionList = ko.observableArray([]); self.lookup = {}; var registeredPermissions = []; var registerPermission = function (key, permission) { Object.defineProperty(self, key, { value: permission, enumerable: true, configurable: true }); registeredPermissions.push(key); }; var clearAllRegisteredPermissions = function () { _.each(registeredPermissions, function (key) { delete self[key]; }); registeredPermissions = []; }; self.initialize = function () { clearAllRegisteredPermissions(); var permissionList = []; var lookup = {}; _.each(PERMISSIONS, function (permission) { var needs = []; _.each(permission.needs, function (value, key) { needs.push(self.need(key, value)); }); if (needs.length > 0) { registerPermission(permission.key, needs); } if (!permission.combined) { permissionList.push(permission); } lookup[permission.key] = permission; }); permissionList.sort(access.permissionComparator); self.permissionList(permissionList); self.lookup = lookup; } ; return self; } )(); access.groupComparator = function (a, b) { var nameA = a.name ? a.name.toUpperCase() : ""; var nameB = b.name ? b.name.toUpperCase() : ""; if (nameA < nameB) { return -1; } else if (nameA > nameB) { return 1; } else { return 0; } } ; access.permissionComparator = function (a, b) { var nameA = a.name ? a.name.toUpperCase() : ""; var nameB = b.name ? b.name.toUpperCase() : ""; var pluginA = a.plugin || ""; var pluginB = b.plugin || ""; var compA = pluginA + ":" + nameA; var compB = pluginB + ":" + nameB; if (compA < compB) { return -1; } else if (compA > compB) { return 1; } else { return 0; } } ; access.groupList = function (data) { if (data.groups === undefined) return ""; var mappedGroups = _.filter(_.map(data.groups, function (g) { return access.groups.lookup[g]; }), function (g) { return g !== undefined; }); mappedGroups.sort(access.groupComparator); return _.map(mappedGroups, function (g) { return g.name; }).join(", "); } ; access.subgroupList = function (data) { if (data.subgroups === undefined) return ""; var mappedGroups = _.filter(_.map(data.subgroups, function (g) { return access.groups.lookup[g]; }), function (g) { return g !== undefined; }); mappedGroups.sort(access.groupComparator); return _.map(mappedGroups, function (g) { return g.name; }).join(", "); } ; access.permissionList = function (data) { if (!data || data.permissions === undefined) return ""; var mappedPermissions = _.filter(_.map(data.permissions, function (p) { return access.permissions.lookup[p]; }), function (p) { return p !== undefined; }); mappedPermissions.sort(access.permissionComparator); return _.map(mappedPermissions, function (p) { return p.name; }).join(", "); } ; access.onStartup = function () { access.groups.onStartup(); access.users.onStartup(); } ; access.onServerConnect = function () { access.permissions.initialize(); } ; access.onServerReconnect = function () { access.permissions.initialize(); } ; access.onUserPermissionsChanged = access.onUserLoggedIn = access.onUserLoggedOut = function (user) { if (access.loginState.hasPermission(access.permissions.SETTINGS)) { access.groups.requestData().done(function () { access.users.requestData(); }); } } ; } OCTOPRINT_VIEWMODELS.push([AccessViewModel, ["loginStateViewModel"], []]); });