mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-12 23:37:00 +08:00
Merge branch 'devel' into CB-2878-versions-load-error-message
This commit is contained in:
@@ -38,6 +38,7 @@
|
||||
anonymousAccessEnabled: true,
|
||||
anonymousUserRole: "user",
|
||||
supportsCustomConnections: false,
|
||||
showReadOnlyConnectionInfo: false,
|
||||
|
||||
forwardProxy: false,
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
anonymousAccessEnabled: true,
|
||||
anonymousUserRole: "user",
|
||||
supportsCustomConnections: false,
|
||||
showReadOnlyConnectionInfo: false,
|
||||
|
||||
forwardProxy: false,
|
||||
|
||||
|
||||
+34
-10
@@ -17,10 +17,12 @@
|
||||
package io.cloudbeaver.model;
|
||||
|
||||
import io.cloudbeaver.WebProjectImpl;
|
||||
import io.cloudbeaver.model.app.BaseWebAppConfiguration;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.service.security.SMUtils;
|
||||
import io.cloudbeaver.service.sql.WebDataFormat;
|
||||
import io.cloudbeaver.utils.CBModelConstants;
|
||||
import io.cloudbeaver.utils.WebAppUtils;
|
||||
import io.cloudbeaver.utils.WebCommonUtils;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.model.DBPDataSource;
|
||||
@@ -39,10 +41,7 @@ import org.jkiss.dbeaver.model.rm.RMProjectPermission;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -50,6 +49,7 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public class WebConnectionInfo {
|
||||
|
||||
private static final String SECURED_VALUE = "********";
|
||||
private final WebSession session;
|
||||
private final DBPDataSourceContainer dataSourceContainer;
|
||||
private WebServerError connectError;
|
||||
@@ -100,12 +100,18 @@ public class WebConnectionInfo {
|
||||
|
||||
@Property
|
||||
public String getHost() {
|
||||
return dataSourceContainer.getConnectionConfiguration().getHostName();
|
||||
if (canViewReadOnlyConnections()) {
|
||||
return dataSourceContainer.getConnectionConfiguration().getHostName();
|
||||
}
|
||||
return SECURED_VALUE;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getPort() {
|
||||
return dataSourceContainer.getConnectionConfiguration().getHostPort();
|
||||
if (canViewReadOnlyConnections()) {
|
||||
return dataSourceContainer.getConnectionConfiguration().getHostPort();
|
||||
}
|
||||
return SECURED_VALUE;
|
||||
}
|
||||
|
||||
@Property
|
||||
@@ -115,17 +121,26 @@ public class WebConnectionInfo {
|
||||
|
||||
@Property
|
||||
public String getDatabaseName() {
|
||||
return dataSourceContainer.getConnectionConfiguration().getDatabaseName();
|
||||
if (canViewReadOnlyConnections()) {
|
||||
return dataSourceContainer.getConnectionConfiguration().getDatabaseName();
|
||||
}
|
||||
return SECURED_VALUE;
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getUrl() {
|
||||
return dataSourceContainer.getConnectionConfiguration().getUrl();
|
||||
if (canViewReadOnlyConnections()) {
|
||||
return dataSourceContainer.getConnectionConfiguration().getUrl();
|
||||
}
|
||||
return SECURED_VALUE;
|
||||
}
|
||||
|
||||
@Property
|
||||
public Map<String, String> getProperties() {
|
||||
return dataSourceContainer.getConnectionConfiguration().getProperties();
|
||||
if (canViewReadOnlyConnections()) {
|
||||
return dataSourceContainer.getConnectionConfiguration().getProperties();
|
||||
}
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Property
|
||||
@@ -352,7 +367,7 @@ public class WebConnectionInfo {
|
||||
|
||||
@Property
|
||||
public boolean isCanViewSettings() {
|
||||
return true;
|
||||
return canViewReadOnlyConnections();
|
||||
}
|
||||
|
||||
@Property
|
||||
@@ -382,4 +397,13 @@ public class WebConnectionInfo {
|
||||
}
|
||||
return SMUtils.hasProjectPermission(session, ((WebProjectImpl) project).getRmProject(), projectPermission);
|
||||
}
|
||||
|
||||
private boolean canViewReadOnlyConnections() {
|
||||
if (isCanEdit()) {
|
||||
return true;
|
||||
}
|
||||
BaseWebAppConfiguration appConfig = (BaseWebAppConfiguration) WebAppUtils.getWebApplication().getAppConfiguration();
|
||||
return appConfig.isShowReadOnlyConnectionInfo();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+7
@@ -31,6 +31,7 @@ public abstract class BaseWebAppConfiguration implements WebAppConfiguration {
|
||||
protected final Map<String, Object> plugins;
|
||||
protected String defaultUserTeam;
|
||||
protected boolean resourceManagerEnabled;
|
||||
protected boolean showReadOnlyConnectionInfo;
|
||||
protected String[] enabledFeatures;
|
||||
|
||||
public BaseWebAppConfiguration() {
|
||||
@@ -38,6 +39,7 @@ public abstract class BaseWebAppConfiguration implements WebAppConfiguration {
|
||||
this.defaultUserTeam = DEFAULT_APP_ANONYMOUS_TEAM_NAME;
|
||||
this.resourceManagerEnabled = true;
|
||||
this.enabledFeatures = null;
|
||||
this.showReadOnlyConnectionInfo = false;
|
||||
}
|
||||
|
||||
public BaseWebAppConfiguration(BaseWebAppConfiguration src) {
|
||||
@@ -45,6 +47,7 @@ public abstract class BaseWebAppConfiguration implements WebAppConfiguration {
|
||||
this.defaultUserTeam = src.defaultUserTeam;
|
||||
this.resourceManagerEnabled = src.resourceManagerEnabled;
|
||||
this.enabledFeatures = src.enabledFeatures;
|
||||
this.showReadOnlyConnectionInfo = src.showReadOnlyConnectionInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,4 +101,8 @@ public abstract class BaseWebAppConfiguration implements WebAppConfiguration {
|
||||
public void setEnabledFeatures(String[] enabledFeatures) {
|
||||
this.enabledFeatures = enabledFeatures;
|
||||
}
|
||||
|
||||
public boolean isShowReadOnlyConnectionInfo() {
|
||||
return showReadOnlyConnectionInfo;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -155,6 +155,7 @@ public class LocalResourceController implements RMController {
|
||||
if (userProject != null) {
|
||||
projects.add(0, userProject);
|
||||
}
|
||||
projects.sort(Comparator.comparing(RMProject::getDisplayName));
|
||||
return projects.toArray(new RMProject[0]);
|
||||
}
|
||||
|
||||
@@ -172,7 +173,6 @@ public class LocalResourceController implements RMController {
|
||||
RMProjectType.SHARED, true)
|
||||
)
|
||||
.filter(Objects::nonNull)
|
||||
.sorted(Comparator.comparing(RMProject::getDisplayName))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
+2
-5
@@ -64,6 +64,7 @@ import org.jkiss.dbeaver.model.runtime.ProxyProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.security.*;
|
||||
import org.jkiss.dbeaver.model.security.user.SMObjectPermissions;
|
||||
import org.jkiss.dbeaver.model.sql.DBQuotaException;
|
||||
import org.jkiss.dbeaver.registry.BaseProjectImpl;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
@@ -373,11 +374,7 @@ public class WebSession extends AbstractSessionPersistent
|
||||
RMController controller = getRmController();
|
||||
RMProject[] rmProjects = controller.listAccessibleProjects();
|
||||
for (RMProject project : rmProjects) {
|
||||
WebProjectImpl virtualProject = createWebProject(project);
|
||||
if (!virtualProject.getRmProject().getProjectPermissions().contains(RMProjectPermission.DATA_SOURCES_EDIT.getPermissionId())) {
|
||||
// Projects for which user don't have edit permission can't be saved. So mark the as in memory
|
||||
virtualProject.setInMemory(true);
|
||||
}
|
||||
createWebProject(project);
|
||||
}
|
||||
if (user == null) {
|
||||
WebProjectImpl anonymousProject = createWebProject(RMUtils.createAnonymousProject());
|
||||
|
||||
+11
-6
@@ -59,7 +59,7 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
public WebUserContext(WebApplication application) throws DBException {
|
||||
this.application = application;
|
||||
this.securityController = application.createSecurityController(this);
|
||||
this.userPermissions = getDefaultPermissions();
|
||||
setUserPermissions(getDefaultPermissions());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +87,7 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
authPermissions.getPermissions()
|
||||
);
|
||||
this.refreshToken = smAuthInfo.getSmRefreshToken();
|
||||
this.userPermissions = authPermissions.getPermissions();
|
||||
setUserPermissions(authPermissions.getPermissions());
|
||||
this.securityController = application.createSecurityController(this);
|
||||
this.adminSecurityController = application.getAdminSecurityController(this);
|
||||
this.secretController = application.getSecretController(this);
|
||||
@@ -122,7 +122,7 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
} catch (Exception e) {
|
||||
log.error("Error logging out user", e);
|
||||
}
|
||||
this.userPermissions = getDefaultPermissions();
|
||||
setUserPermissions(getDefaultPermissions());
|
||||
this.smCredentials = null;
|
||||
this.user = null;
|
||||
this.securityController = application.createSecurityController(this);
|
||||
@@ -171,11 +171,16 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
*/
|
||||
public synchronized void refreshPermissions() throws DBException {
|
||||
if (isAuthorizedInSecurityManager()) {
|
||||
this.userPermissions = securityController.getTokenPermissions().getPermissions();
|
||||
setUserPermissions(securityController.getTokenPermissions().getPermissions());
|
||||
} else {
|
||||
this.userPermissions = getDefaultPermissions();
|
||||
setUserPermissions(getDefaultPermissions());
|
||||
}
|
||||
if (userPermissions != null && !userPermissions.isEmpty()) {
|
||||
}
|
||||
|
||||
private void setUserPermissions(Set<String> permissions) {
|
||||
this.userPermissions = permissions;
|
||||
// FIXME: automatically assign public permission in sm controller˚
|
||||
if (!CommonUtils.isEmpty(userPermissions)) {
|
||||
userPermissions.add(DBWConstants.PERMISSION_PUBLIC);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-3
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
package io.cloudbeaver.service;
|
||||
|
||||
import io.cloudbeaver.model.app.WebAppConfiguration;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.server.CBAppConfig;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
@@ -28,8 +28,12 @@ import org.jkiss.dbeaver.model.app.DBPApplication;
|
||||
*/
|
||||
public interface DBWServiceServerConfigurator extends DBWServiceBinding {
|
||||
|
||||
void configureServer(@NotNull DBPApplication application, @Nullable WebSession session, @NotNull CBAppConfig appConfig) throws DBException;
|
||||
void configureServer(
|
||||
@NotNull DBPApplication application,
|
||||
@Nullable WebSession session,
|
||||
@NotNull WebAppConfiguration appConfig
|
||||
) throws DBException;
|
||||
|
||||
void reloadConfiguration(@NotNull CBAppConfig appConfig) throws DBException;
|
||||
void reloadConfiguration(@NotNull WebAppConfiguration appConfig) throws DBException;
|
||||
|
||||
}
|
||||
@@ -930,23 +930,25 @@ public class CBApplication extends BaseWebApplication implements WebAuthApplicat
|
||||
rootConfig.put("app", appConfigProperties);
|
||||
|
||||
copyConfigValue(
|
||||
oldAppConfig, appConfigProperties, "anonymousAccessEnabled", appConfig.isAnonymousAccessEnabled());
|
||||
oldAppConfig, appConfigProperties, "anonymousAccessEnabled", appConfig.isAnonymousAccessEnabled());
|
||||
copyConfigValue(
|
||||
oldAppConfig, appConfigProperties, "supportsCustomConnections", appConfig.isSupportsCustomConnections());
|
||||
oldAppConfig, appConfigProperties, "supportsCustomConnections", appConfig.isSupportsCustomConnections());
|
||||
copyConfigValue(
|
||||
oldAppConfig, appConfigProperties, "publicCredentialsSaveEnabled", appConfig.isPublicCredentialsSaveEnabled());
|
||||
oldAppConfig, appConfigProperties, "publicCredentialsSaveEnabled", appConfig.isPublicCredentialsSaveEnabled());
|
||||
copyConfigValue(
|
||||
oldAppConfig, appConfigProperties, "adminCredentialsSaveEnabled", appConfig.isAdminCredentialsSaveEnabled());
|
||||
oldAppConfig, appConfigProperties, "adminCredentialsSaveEnabled", appConfig.isAdminCredentialsSaveEnabled());
|
||||
copyConfigValue(
|
||||
oldAppConfig, appConfigProperties, "enableReverseProxyAuth", appConfig.isEnabledReverseProxyAuth());
|
||||
oldAppConfig, appConfigProperties, "enableReverseProxyAuth", appConfig.isEnabledReverseProxyAuth());
|
||||
copyConfigValue(
|
||||
oldAppConfig, appConfigProperties, "forwardProxy", appConfig.isEnabledForwardProxy());
|
||||
oldAppConfig, appConfigProperties, "forwardProxy", appConfig.isEnabledForwardProxy());
|
||||
copyConfigValue(
|
||||
oldAppConfig, appConfigProperties, "linkExternalCredentialsWithUser", appConfig.isLinkExternalCredentialsWithUser());
|
||||
oldAppConfig, appConfigProperties, "linkExternalCredentialsWithUser", appConfig.isLinkExternalCredentialsWithUser());
|
||||
copyConfigValue(
|
||||
oldAppConfig, appConfigProperties, "redirectOnFederatedAuth", appConfig.isRedirectOnFederatedAuth());
|
||||
oldAppConfig, appConfigProperties, "redirectOnFederatedAuth", appConfig.isRedirectOnFederatedAuth());
|
||||
copyConfigValue(
|
||||
oldAppConfig, appConfigProperties, CBConstants.PARAM_RESOURCE_MANAGER_ENABLED, appConfig.isResourceManagerEnabled());
|
||||
oldAppConfig, appConfigProperties, CBConstants.PARAM_RESOURCE_MANAGER_ENABLED, appConfig.isResourceManagerEnabled());
|
||||
copyConfigValue(
|
||||
oldAppConfig, appConfigProperties, CBConstants.PARAM_SHOW_READ_ONLY_CONN_INFO, appConfig.isShowReadOnlyConnectionInfo());
|
||||
|
||||
Map<String, Object> resourceQuotas = new LinkedHashMap<>();
|
||||
Map<String, Object> originResourceQuotas = JSONUtils.getObject(oldAppConfig, CBConstants.PARAM_RESOURCE_QUOTAS);
|
||||
|
||||
@@ -39,6 +39,7 @@ public class CBConstants {
|
||||
public static final String PARAM_STATIC_CONTENT = "staticContent";
|
||||
public static final String PARAM_RESOURCE_QUOTAS = "resourceQuotas";
|
||||
public static final String PARAM_RESOURCE_MANAGER_ENABLED = "resourceManagerEnabled";
|
||||
public static final String PARAM_SHOW_READ_ONLY_CONN_INFO = "showReadOnlyConnectionInfo";
|
||||
public static final String PARAM_AUTH_PROVIDERS = "authConfiguration";
|
||||
public static final String PARAM_DB_CONFIGURATION = "database";
|
||||
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ public class CBDataSourceUpdatedEventHandlerImpl extends CBProjectUpdatedEventHa
|
||||
CBEventConstants.EventType eventType =
|
||||
CBEventConstants.EventType.valueOf(JSONUtils.getString(event.getEventData(), "eventType"));
|
||||
WebProjectImpl project = activeUserSession.getProjectById(projectId);
|
||||
if (project == null || !project.getRmProject().isShared()) {
|
||||
if (project == null) {
|
||||
return;
|
||||
}
|
||||
activeUserSession.updateProjectConnection(project, dataSourceIds, eventType);
|
||||
|
||||
+3
@@ -161,6 +161,9 @@ public class WebServiceNavigator implements DBWServiceNavigator {
|
||||
|
||||
List<WebNavigatorNodeInfo> nodeParents = new ArrayList<>();
|
||||
for (DBNNode parent = node.getParentNode(); parent != null && !(parent instanceof DBNRoot); parent = parent.getParentNode()) {
|
||||
if (parent instanceof DBNProjectDatabases) {
|
||||
continue;
|
||||
}
|
||||
nodeParents.add(new WebNavigatorNodeInfo(session, parent));
|
||||
}
|
||||
|
||||
|
||||
+23
-5
@@ -44,11 +44,11 @@ import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;
|
||||
import org.jkiss.dbeaver.model.impl.jdbc.exec.JDBCTransaction;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.runtime.LoggingProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.security.*;
|
||||
import org.jkiss.dbeaver.model.security.exception.SMAccessTokenExpiredException;
|
||||
import org.jkiss.dbeaver.model.security.exception.SMException;
|
||||
import org.jkiss.dbeaver.model.security.exception.SMRefreshTokenExpiredException;
|
||||
import org.jkiss.dbeaver.model.security.user.*;
|
||||
import org.jkiss.dbeaver.model.security.*;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.utils.ArrayUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
@@ -757,8 +757,11 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
dbStat.execute();
|
||||
}
|
||||
|
||||
insertPermissions(dbCon, teamId,
|
||||
new String[] {DBWConstants.PERMISSION_PUBLIC} , grantor);
|
||||
insertPermissions(dbCon,
|
||||
teamId,
|
||||
getDefaultTeamPermissions(),
|
||||
grantor
|
||||
);
|
||||
|
||||
txn.commit();
|
||||
}
|
||||
@@ -767,6 +770,10 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
}
|
||||
}
|
||||
|
||||
protected String[] getDefaultTeamPermissions() {
|
||||
return new String[]{DBWConstants.PERMISSION_PUBLIC};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(String teamId, String name, String description) throws DBCException {
|
||||
if (!isSubjectExists(teamId)) {
|
||||
@@ -1803,7 +1810,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
|
||||
@Override
|
||||
public void updateSession(@NotNull String sessionId, @NotNull Map<String, Object> parameters) throws DBCException {
|
||||
String userId = getUserIdOrThrow();
|
||||
String userId = getUserIdOrNull();
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
try (PreparedStatement dbStat = dbCon.prepareStatement(
|
||||
"UPDATE CB_SESSION SET USER_ID=?,LAST_ACCESS_TIME=?,LAST_ACCESS_REMOTE_ADDRESS=?,LAST_ACCESS_USER_AGENT=?,LAST_ACCESS_INSTANCE_ID=? WHERE SESSION_ID=?")) {
|
||||
@@ -2156,10 +2163,21 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
return originalLink + "?authId=" + authId;
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
private String getUserIdOrThrow() throws SMException {
|
||||
String userId = getUserIdOrNull();
|
||||
if (userId == null) {
|
||||
throw new SMException("User not authenticated");
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getUserIdOrNull() {
|
||||
SMCredentials activeUserCredentials = credentialsProvider.getActiveUserCredentials();
|
||||
if (activeUserCredentials == null || activeUserCredentials.getUserId() == null) {
|
||||
throw new SMException("User not authenticated");
|
||||
return null;
|
||||
}
|
||||
return activeUserCredentials.getUserId();
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ export class NavNodeManagerService extends Bootstrap {
|
||||
}
|
||||
|
||||
async canOpen(nodeId: string, parentId: string, folderId?: string): Promise<boolean> {
|
||||
if (!this.navTree.has(nodeId)) {
|
||||
if (!this.navNodeInfoResource.has(nodeId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,30 @@ describe('Is array equals', () => {
|
||||
});
|
||||
|
||||
test('should use isEqual argument if passed', () => {
|
||||
expect(isArraysEqual([{ a:1 }], [{ a: 1 }], (a, b) => a.a === b.a)).toBe(true);
|
||||
expect(isArraysEqual([{ a: 1 }], [{ a: 1 }], (a, b) => a.a === b.a)).toBe(true);
|
||||
});
|
||||
|
||||
test('should return "true" when arrays are the same and "order" argument is "true"', () => {
|
||||
expect(isArraysEqual([1, '1', true], [1, '1', true], undefined, true)).toBe(true);
|
||||
});
|
||||
|
||||
test('should return "false" when arrays are not the same and "order" argument is "true"', () => {
|
||||
expect(isArraysEqual([1, '2', true], [2, '1', true], undefined, true)).toBe(false);
|
||||
});
|
||||
|
||||
test('should return "false" when arrays have the same elements but elements order is different and "order" argument is "true"', () => {
|
||||
expect(isArraysEqual(['1', 1, true], [1, '1', true], undefined, true)).toBe(false);
|
||||
});
|
||||
|
||||
test('should return "false" when arrays length is different and the first array is larger and "order" argument is "true"', () => {
|
||||
expect(isArraysEqual([1, true, '1'], [1, true], undefined, true)).toBe(false);
|
||||
});
|
||||
|
||||
test('should return "false" when arrays length is different and the second array is larger and "order" argument is "true"', () => {
|
||||
expect(isArraysEqual([1, true], [1, true, '1'], undefined, true)).toBe(false);
|
||||
});
|
||||
|
||||
test('should use isEqual argument if passed and "order" argument is "true"', () => {
|
||||
expect(isArraysEqual([{ a: 1 }], [{ a: 1 }], (a, b) => a.a === b.a, true)).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -11,11 +11,19 @@ const isObjectEqual = <T>(a: T, b: T) => a === b;
|
||||
export function isArraysEqual<T>(
|
||||
first: T[],
|
||||
second: T[],
|
||||
isEqual: (a: T, b: T) => boolean = isObjectEqual
|
||||
isEqual: (a: T, b: T) => boolean = isObjectEqual,
|
||||
order?: boolean
|
||||
): boolean {
|
||||
if (first.length !== second.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !first.some(b => !second.some(a => isEqual(a, b)));
|
||||
|
||||
return !first.some((b, index) => {
|
||||
if (order) {
|
||||
return !isEqual(second[index], b);
|
||||
}
|
||||
|
||||
return !second.some(a => isEqual(a, b));
|
||||
});
|
||||
}
|
||||
|
||||
+1
@@ -62,6 +62,7 @@ const topBarStyles = css`
|
||||
position: relative;
|
||||
display: flex;
|
||||
padding-top: 16px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ export const UserInfo: TabContainerPanelComponent<IUserFormProps> = observer(fun
|
||||
);
|
||||
|
||||
return styled(style)(
|
||||
<ColoredContainer parent gap overflow>
|
||||
<ColoredContainer gap overflow>
|
||||
<Group small gap vertical overflow>
|
||||
<Container gap vertical>
|
||||
<GroupTitle keepSize>{translate('authentication_user_credentials')}</GroupTitle>
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useState } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { AuthRolesResource } from '@cloudbeaver/core-authentication';
|
||||
import { BASE_CONTAINERS_STYLES, Combobox, Filter, Group, IconOrImage, useDataResource, useStyles, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import { IUserFilters, USER_ROLE_ALL, USER_STATUSES } from './useUsersTableFilters';
|
||||
|
||||
const styles = css`
|
||||
filter-container {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
Filter {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
actions {
|
||||
composes: theme-form-element-radius theme-background-surface theme-text-on-surface theme theme-border-color-background from global;
|
||||
box-sizing: border-box;
|
||||
border: 2px solid;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
button {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
background: inherit;
|
||||
cursor: pointer;
|
||||
width: 28px;
|
||||
height: 100%;
|
||||
padding: 4px;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&[|active] {
|
||||
background: var(--theme-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
IconOrImage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
filters: IUserFilters;
|
||||
}
|
||||
|
||||
export const UsersTableFilters = observer<Props>(function UsersTableFilters({ filters }) {
|
||||
const translate = useTranslate();
|
||||
const style = useStyles(BASE_CONTAINERS_STYLES, styles);
|
||||
const authRolesResource = useDataResource(UsersTableFilters, AuthRolesResource, undefined);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return styled(style)(
|
||||
<Group parent compact gap>
|
||||
<filter-container>
|
||||
<Filter
|
||||
placeholder={translate('authentication_administration_users_filters_search_placeholder')}
|
||||
value={filters.search}
|
||||
max
|
||||
onFilter={filters.setSearch}
|
||||
/>
|
||||
<actions>
|
||||
<button
|
||||
{...use({ active: open })}
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
<IconOrImage icon='filter' />
|
||||
</button>
|
||||
</actions>
|
||||
</filter-container>
|
||||
|
||||
{open && (
|
||||
<Group box gap>
|
||||
<Combobox
|
||||
value={filters.status}
|
||||
items={USER_STATUSES}
|
||||
valueSelector={value => translate(value.label)}
|
||||
keySelector={value => value.value}
|
||||
keepSize
|
||||
onSelect={filters.setStatus}
|
||||
>
|
||||
{translate('authentication_user_status')}
|
||||
</Combobox>
|
||||
{!!authRolesResource.data.length && (
|
||||
<Combobox
|
||||
items={[...authRolesResource.data, USER_ROLE_ALL]}
|
||||
value={filters.role}
|
||||
keepSize
|
||||
onSelect={filters.setRole}
|
||||
>
|
||||
{translate('authentication_user_role')}
|
||||
</Combobox>
|
||||
)}
|
||||
</Group>
|
||||
)}
|
||||
</Group>
|
||||
);
|
||||
});
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { action, computed, observable } from 'mobx';
|
||||
|
||||
import { useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
import type { TLocalizationToken } from '@cloudbeaver/core-localization';
|
||||
import type { AdminUser } from '@cloudbeaver/core-authentication';
|
||||
import { isArraysEqual } from '@cloudbeaver/core-utils';
|
||||
|
||||
export enum EUserStatus {
|
||||
ENABLED = 'ENABLED',
|
||||
DISABLED = 'DISABLED',
|
||||
ALL = 'ALL'
|
||||
}
|
||||
|
||||
interface IStatus {
|
||||
label: TLocalizationToken;
|
||||
value: EUserStatus;
|
||||
}
|
||||
|
||||
export const USER_ROLE_ALL = 'ALL';
|
||||
|
||||
export const USER_STATUSES: IStatus[] = [
|
||||
{
|
||||
label: 'authentication_administration_users_filters_status_enabled',
|
||||
value: EUserStatus.ENABLED,
|
||||
},
|
||||
{
|
||||
label: 'authentication_administration_users_filters_status_disabled',
|
||||
value: EUserStatus.DISABLED,
|
||||
},
|
||||
{
|
||||
label: 'authentication_administration_users_filters_status_all',
|
||||
value: EUserStatus.ALL,
|
||||
},
|
||||
];
|
||||
|
||||
export interface IUserFilters {
|
||||
search: string;
|
||||
role: string;
|
||||
status: EUserStatus;
|
||||
filteredUsers: AdminUser[];
|
||||
setSearch: (value: string) => void;
|
||||
setRole: (role: string) => void;
|
||||
setStatus: (status: EUserStatus) => void;
|
||||
}
|
||||
|
||||
export function useUsersTableFilters(users: AdminUser[]) {
|
||||
const filters: IUserFilters = useObservableRef(() => ({
|
||||
search: '',
|
||||
role: USER_ROLE_ALL,
|
||||
status: EUserStatus.ENABLED,
|
||||
get filteredUsers() {
|
||||
return this.users.filter(user => {
|
||||
const matchSearch = user.userId.toLowerCase().includes(this.search.trim().toLowerCase());
|
||||
const matchStatus = this.status === EUserStatus.ALL
|
||||
|| (this.status === EUserStatus.ENABLED ? user.enabled : !user.enabled);
|
||||
const matchRole = this.role === USER_ROLE_ALL || this.role === user.authRole;
|
||||
|
||||
return matchSearch && matchStatus && matchRole;
|
||||
})
|
||||
},
|
||||
setSearch(value: string) {
|
||||
this.search = value;
|
||||
},
|
||||
setRole(role: string) {
|
||||
this.role = role;
|
||||
},
|
||||
setStatus(status: EUserStatus) {
|
||||
this.status = status;
|
||||
},
|
||||
}), {
|
||||
search: observable.ref,
|
||||
role: observable.ref,
|
||||
status: observable.ref,
|
||||
users: observable.ref,
|
||||
filteredUsers: computed<AdminUser[]>({ equals: (first, second) => isArraysEqual(first, second, undefined, true) }),
|
||||
setSearch: action.bound,
|
||||
setRole: action.bound,
|
||||
setStatus: action.bound,
|
||||
}, { users });
|
||||
|
||||
return filters;
|
||||
}
|
||||
+116
-108
@@ -7,36 +7,24 @@
|
||||
*/
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { ADMINISTRATION_TOOLS_PANEL_STYLES, IAdministrationItemSubItem } from '@cloudbeaver/core-administration';
|
||||
import { AdminUser, AuthRolesResource, UsersResource } from '@cloudbeaver/core-authentication';
|
||||
import { Table, TableHeader, TableColumnHeader, TableBody, TableSelect, useMapResource, ToolsAction, ToolsPanel, Loader, BASE_LAYOUT_GRID_STYLES, useTranslate, useStyles, useDataResource } from '@cloudbeaver/core-blocks';
|
||||
import { useController, useService } from '@cloudbeaver/core-di';
|
||||
import { AuthProvidersResource, AuthRolesResource, AUTH_PROVIDER_LOCAL_ID, UsersResource } from '@cloudbeaver/core-authentication';
|
||||
import {
|
||||
Table, TableHeader, TableColumnHeader, TableBody, useMapResource, ToolsAction,
|
||||
ToolsPanel, Loader, useTranslate, useStyles, useDataResource,
|
||||
BASE_CONTAINERS_STYLES, ColoredContainer, Container, Group
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { CachedMapAllKey } from '@cloudbeaver/core-sdk';
|
||||
import { filterUndefined } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { CreateUser } from './CreateUser';
|
||||
import { CreateUserService } from './CreateUserService';
|
||||
import { UsersTableFilters } from './Filters/UsersTableFilters';
|
||||
import { useUsersTableFilters } from './Filters/useUsersTableFilters';
|
||||
import { User } from './User';
|
||||
import { UsersTableController } from './UsersTableController';
|
||||
|
||||
const layoutStyles = css`
|
||||
layout-grid {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
layout-grid-inner {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
layout-grid-cell {
|
||||
composes: theme-background-surface theme-text-on-surface theme-border-color-background from global;
|
||||
position: relative;
|
||||
border: solid 1px;
|
||||
}
|
||||
`;
|
||||
import { useUsersTable } from './useUsersTable';
|
||||
|
||||
const loaderStyle = css`
|
||||
ExceptionMessage {
|
||||
@@ -45,16 +33,36 @@ const loaderStyle = css`
|
||||
`;
|
||||
|
||||
const styles = css`
|
||||
ToolsPanel {
|
||||
border-bottom: none;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
Group {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
Table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
[|table] {
|
||||
min-height: 140px; /* loader overlay size */
|
||||
TableHeader {
|
||||
background: var(--theme-surface);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
ToolsPanel {
|
||||
border-bottom: none;
|
||||
|
||||
table-container {
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -65,53 +73,48 @@ interface Props {
|
||||
|
||||
export const UsersTable = observer<Props>(function UsersTable({ sub, param }) {
|
||||
const translate = useTranslate();
|
||||
const style = useStyles(BASE_LAYOUT_GRID_STYLES, styles, ADMINISTRATION_TOOLS_PANEL_STYLES, layoutStyles);
|
||||
const createUserService = useService(CreateUserService);
|
||||
const controller = useController(UsersTableController);
|
||||
const usersResource = useMapResource(UsersTable, UsersResource, CachedMapAllKey);
|
||||
const authRoles = useDataResource(UsersTable, AuthRolesResource, undefined);
|
||||
const isLocalProviderAvailable = controller.isLocalProviderAvailable;
|
||||
const users = usersResource.resource.values
|
||||
.filter<AdminUser>(filterUndefined)
|
||||
.sort((a, b) => {
|
||||
if (usersResource.resource.isNew(a.userId) === usersResource.resource.isNew(b.userId)) {
|
||||
return a.userId.localeCompare(b.userId);
|
||||
}
|
||||
if (usersResource.resource.isNew(a.userId)) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
const style = useStyles(styles, ADMINISTRATION_TOOLS_PANEL_STYLES, BASE_CONTAINERS_STYLES);
|
||||
|
||||
const createUserService = useService(CreateUserService);
|
||||
|
||||
const usersResource = useMapResource(UsersTable, UsersResource, CachedMapAllKey);
|
||||
const authProvidersResource = useMapResource(UsersTable, AuthProvidersResource, CachedMapAllKey);
|
||||
const authRolesResource = useDataResource(UsersTable, AuthRolesResource, undefined);
|
||||
|
||||
const table = useUsersTable(usersResource.resource);
|
||||
const filters = useUsersTableFilters(table.users);
|
||||
|
||||
const isLocalProviderAvailable = authProvidersResource.resource.has(AUTH_PROVIDER_LOCAL_ID);
|
||||
const create = param === 'create';
|
||||
const keys = users.map(user => user.userId);
|
||||
const displayAuthRole = authRoles.data.length > 0;
|
||||
const displayAuthRole = authRolesResource.data.length > 0;
|
||||
const keys = filters.filteredUsers.map(user => user.userId);
|
||||
|
||||
return styled(style)(
|
||||
<layout-grid>
|
||||
<layout-grid-inner>
|
||||
<layout-grid-cell data-span='12'>
|
||||
<ToolsPanel>
|
||||
{isLocalProviderAvailable && (
|
||||
<ColoredContainer wrap gap parent overflow vertical>
|
||||
<Container gap vertical>
|
||||
<Container gap keepSize>
|
||||
<Group>
|
||||
<ToolsPanel>
|
||||
{isLocalProviderAvailable && (
|
||||
<ToolsAction
|
||||
title={translate('authentication_administration_tools_add_tooltip')}
|
||||
icon='add'
|
||||
viewBox="0 0 24 24"
|
||||
disabled={create && !!createUserService.user}
|
||||
onClick={createUserService.create}
|
||||
>
|
||||
{translate('ui_add')}
|
||||
</ToolsAction>
|
||||
)}
|
||||
<ToolsAction
|
||||
title={translate('authentication_administration_tools_add_tooltip')}
|
||||
icon="add"
|
||||
title={translate('authentication_administration_tools_refresh_tooltip')}
|
||||
icon='refresh'
|
||||
viewBox="0 0 24 24"
|
||||
disabled={create && !!createUserService.user}
|
||||
onClick={createUserService.create}
|
||||
onClick={table.update}
|
||||
>
|
||||
{translate('ui_add')}
|
||||
{translate('ui_refresh')}
|
||||
</ToolsAction>
|
||||
)}
|
||||
<ToolsAction
|
||||
title={translate('authentication_administration_tools_refresh_tooltip')}
|
||||
icon="refresh"
|
||||
viewBox="0 0 24 24"
|
||||
onClick={controller.update}
|
||||
>
|
||||
{translate('ui_refresh')}
|
||||
</ToolsAction>
|
||||
{/* {isLocalProviderAvailable && (
|
||||
{/* {isLocalProviderAvailable && (
|
||||
<ToolsAction
|
||||
title={translate('authentication_administration_tools_delete_tooltip')}
|
||||
icon="trash"
|
||||
@@ -122,51 +125,56 @@ export const UsersTable = observer<Props>(function UsersTable({ sub, param }) {
|
||||
{translate('ui_delete')}
|
||||
</ToolsAction>
|
||||
)} */}
|
||||
</ToolsPanel>
|
||||
</layout-grid-cell>
|
||||
</ToolsPanel>
|
||||
</Group>
|
||||
<UsersTableFilters filters={filters} />
|
||||
</Container>
|
||||
|
||||
{create && createUserService.user && (
|
||||
<layout-grid-cell data-span='12'>
|
||||
<CreateUser user={createUserService.user} onCancel={createUserService.cancelCreate} />
|
||||
</layout-grid-cell>
|
||||
)}
|
||||
<layout-grid-cell data-span='12' {...use({ table: true })}>
|
||||
<Loader style={loaderStyle} state={[usersResource, authRoles]} overlay>
|
||||
<Table
|
||||
keys={keys}
|
||||
selectedItems={controller.selectedItems}
|
||||
expandedItems={controller.expandedItems}
|
||||
size='big'
|
||||
>
|
||||
<TableHeader>
|
||||
{/* {isLocalProviderAvailable && (
|
||||
<content>
|
||||
{create && createUserService.user && (
|
||||
<Group>
|
||||
<CreateUser user={createUserService.user} onCancel={createUserService.cancelCreate} />
|
||||
</Group>
|
||||
)}
|
||||
|
||||
<Group>
|
||||
<Loader style={loaderStyle} state={[usersResource, authRolesResource]} overlay>
|
||||
<Table
|
||||
keys={keys}
|
||||
selectedItems={table.state.selected}
|
||||
expandedItems={table.state.expanded}
|
||||
size='big'
|
||||
>
|
||||
<TableHeader>
|
||||
{/* {isLocalProviderAvailable && (
|
||||
<TableColumnHeader min flex centerContent>
|
||||
<TableSelect />
|
||||
</TableColumnHeader>
|
||||
)} */}
|
||||
<TableColumnHeader min />
|
||||
<TableColumnHeader>{translate('authentication_user_name')}</TableColumnHeader>
|
||||
{displayAuthRole && (
|
||||
<TableColumnHeader>{translate('authentication_user_role')}</TableColumnHeader>
|
||||
)}
|
||||
<TableColumnHeader>{translate('authentication_user_team')}</TableColumnHeader>
|
||||
<TableColumnHeader>{translate('authentication_user_enabled')}</TableColumnHeader>
|
||||
<TableColumnHeader />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{users.map(user => (
|
||||
<User
|
||||
key={user.userId}
|
||||
user={user}
|
||||
displayAuthRole={displayAuthRole}
|
||||
// selectable={isLocalProviderAvailable}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Loader>
|
||||
</layout-grid-cell>
|
||||
</layout-grid-inner>
|
||||
</layout-grid>
|
||||
<TableColumnHeader min />
|
||||
<TableColumnHeader>{translate('authentication_user_name')}</TableColumnHeader>
|
||||
{displayAuthRole && (
|
||||
<TableColumnHeader>{translate('authentication_user_role')}</TableColumnHeader>
|
||||
)}
|
||||
<TableColumnHeader>{translate('authentication_user_team')}</TableColumnHeader>
|
||||
<TableColumnHeader>{translate('authentication_user_enabled')}</TableColumnHeader>
|
||||
<TableColumnHeader />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filters.filteredUsers.map(user => (
|
||||
<User
|
||||
key={user.userId}
|
||||
user={user}
|
||||
displayAuthRole={displayAuthRole}
|
||||
// selectable={isLocalProviderAvailable}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Loader>
|
||||
</Group>
|
||||
</content>
|
||||
</Container>
|
||||
</ColoredContainer>
|
||||
);
|
||||
});
|
||||
|
||||
-133
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observable, computed, makeObservable } from 'mobx';
|
||||
|
||||
import { AdminUser, AuthProvidersResource, AUTH_PROVIDER_LOCAL_ID, UsersResource } from '@cloudbeaver/core-authentication';
|
||||
import { injectable, IInitializableController } from '@cloudbeaver/core-di';
|
||||
import { CommonDialogService, ConfirmationDialogDelete, DialogueStateResult } from '@cloudbeaver/core-dialogs';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { LocalizationService } from '@cloudbeaver/core-localization';
|
||||
import { ErrorDetailsDialog } from '@cloudbeaver/core-notifications';
|
||||
import { GQLErrorCatcher, resourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
|
||||
@injectable()
|
||||
export class UsersTableController implements IInitializableController {
|
||||
isDeleting = false;
|
||||
readonly selectedItems = observable<string, boolean>(new Map());
|
||||
readonly expandedItems = observable<string, boolean>(new Map());
|
||||
readonly error = new GQLErrorCatcher();
|
||||
|
||||
get users(): AdminUser[] {
|
||||
return Array.from(this.usersResource.data.values())
|
||||
.sort((a, b) => {
|
||||
if (this.usersResource.isNew(a.userId) === this.usersResource.isNew(b.userId)) {
|
||||
return a.userId.localeCompare(b.userId);
|
||||
}
|
||||
if (this.usersResource.isNew(a.userId)) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
}
|
||||
|
||||
get isProvidersLoading(): boolean {
|
||||
return this.authProvidersResource.isLoading();
|
||||
}
|
||||
|
||||
get isLocalProviderAvailable(): boolean {
|
||||
return this.authProvidersResource.has(AUTH_PROVIDER_LOCAL_ID);
|
||||
}
|
||||
|
||||
get isLoading(): boolean {
|
||||
return this.usersResource.isLoading() || this.isDeleting;
|
||||
}
|
||||
|
||||
get itemsSelected(): boolean {
|
||||
return Array.from(this.selectedItems.values()).some(v => v);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private notificationService: NotificationService,
|
||||
private authProvidersResource: AuthProvidersResource,
|
||||
private usersResource: UsersResource,
|
||||
private commonDialogService: CommonDialogService,
|
||||
private localizationService: LocalizationService
|
||||
) {
|
||||
makeObservable(this, {
|
||||
isDeleting: observable,
|
||||
users: computed,
|
||||
itemsSelected: computed,
|
||||
});
|
||||
}
|
||||
|
||||
init(): void {
|
||||
this.authProvidersResource.loadAll();
|
||||
}
|
||||
|
||||
update = async () => {
|
||||
try {
|
||||
await this.usersResource.refreshAll();
|
||||
this.notificationService.logSuccess({ title: 'authentication_administration_tools_refresh_success' });
|
||||
} catch (exception: any) {
|
||||
if (!this.error.catch(exception)) {
|
||||
this.notificationService.logException(exception, 'Users update failed');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
delete = async () => {
|
||||
if (this.isDeleting) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deletionList = Array
|
||||
.from(this.selectedItems)
|
||||
.filter(([_, value]) => value)
|
||||
.map(([userId]) => userId);
|
||||
if (deletionList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userNames = deletionList.map(name => `"${name}"`).join(', ');
|
||||
const message = `${this.localizationService.translate('authentication_administration_users_delete_confirmation')}${userNames}. ${this.localizationService.translate('ui_are_you_sure')}`;
|
||||
|
||||
const result = await this.commonDialogService.open(ConfirmationDialogDelete, {
|
||||
title: 'ui_data_delete_confirmation',
|
||||
message,
|
||||
confirmActionText: 'ui_delete',
|
||||
});
|
||||
|
||||
if (result === DialogueStateResult.Rejected) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDeleting = true;
|
||||
|
||||
try {
|
||||
await this.usersResource.delete(resourceKeyList(deletionList));
|
||||
this.selectedItems.clear();
|
||||
|
||||
for (const id of deletionList) {
|
||||
this.expandedItems.delete(id);
|
||||
}
|
||||
} catch (exception: any) {
|
||||
if (!this.error.catch(exception)) {
|
||||
this.notificationService.logException(exception, 'User delete failed');
|
||||
}
|
||||
} finally {
|
||||
this.isDeleting = false;
|
||||
}
|
||||
};
|
||||
|
||||
showDetails = () => {
|
||||
if (this.error.exception) {
|
||||
this.commonDialogService.open(ErrorDetailsDialog, this.error.exception);
|
||||
}
|
||||
};
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { action, computed, observable } from 'mobx';
|
||||
|
||||
import type { AdminUser, UsersResource } from '@cloudbeaver/core-authentication';
|
||||
import { TableState, useObservableRef, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { CommonDialogService, ConfirmationDialogDelete, DialogueStateResult } from '@cloudbeaver/core-dialogs';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { resourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
import { isArraysEqual } from '@cloudbeaver/core-utils';
|
||||
|
||||
interface State {
|
||||
loading: boolean;
|
||||
state: TableState;
|
||||
users: AdminUser[];
|
||||
update: () => Promise<void>;
|
||||
delete: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function useUsersTable(usersResource: UsersResource) {
|
||||
const translate = useTranslate();
|
||||
const notificationService = useService(NotificationService);
|
||||
const commonDialogService = useService(CommonDialogService);
|
||||
|
||||
const state: State = useObservableRef(() => ({
|
||||
loading: false,
|
||||
state: new TableState(),
|
||||
get users() {
|
||||
return this.usersResource.values
|
||||
.slice()
|
||||
.sort((a, b) => {
|
||||
if (this.usersResource.isNew(a.userId) === this.usersResource.isNew(b.userId)) {
|
||||
return a.userId.localeCompare(b.userId);
|
||||
}
|
||||
if (this.usersResource.isNew(a.userId)) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
},
|
||||
async update() {
|
||||
try {
|
||||
await this.usersResource.refreshAll();
|
||||
notificationService.logSuccess({ title: 'authentication_administration_tools_refresh_success' });
|
||||
} catch (exception: any) {
|
||||
notificationService.logException(exception, 'authentication_administration_tools_refresh_fail');
|
||||
}
|
||||
},
|
||||
async delete() {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deletionList = this.state.selectedList.filter(([_, value]) => value).map(([userId]) => userId);
|
||||
if (deletionList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const userNames = deletionList.map(name => `"${name}"`).join(', ');
|
||||
const message = `${translate('authentication_administration_users_delete_confirmation')}${userNames}. ${translate('ui_are_you_sure')}`;
|
||||
|
||||
const result = await commonDialogService.open(ConfirmationDialogDelete, {
|
||||
title: 'ui_data_delete_confirmation',
|
||||
message,
|
||||
confirmActionText: 'ui_delete',
|
||||
});
|
||||
|
||||
if (result === DialogueStateResult.Rejected) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
await this.usersResource.delete(resourceKeyList(deletionList));
|
||||
this.state.unselect();
|
||||
|
||||
for (const id of deletionList) {
|
||||
this.state.unexpand(id);
|
||||
}
|
||||
} catch (exception: any) {
|
||||
notificationService.logException(exception, 'authentication_administration_user_delete_fail');
|
||||
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
}), {
|
||||
loading: observable.ref,
|
||||
users: computed<AdminUser[]>({ equals: (first, second) => isArraysEqual(first, second, undefined, true) }),
|
||||
update: action.bound,
|
||||
delete: action.bound,
|
||||
}, { usersResource });
|
||||
|
||||
return state;
|
||||
}
|
||||
@@ -18,11 +18,17 @@ export default [
|
||||
['authentication_administration_tools_refresh_tooltip', 'Refresh user list'],
|
||||
['authentication_administration_tools_delete_tooltip', 'Delete selected users'],
|
||||
['authentication_administration_tools_refresh_success', 'User list was refreshed'],
|
||||
['authentication_administration_tools_refresh_fail', 'Users update failed'],
|
||||
['authentication_administration_user_delete_fail', 'Error deleting user'],
|
||||
['authentication_administration_user_update_failed', 'Error saving user'],
|
||||
['authentication_administration_user_updated', 'User updated'],
|
||||
['authentication_administration_user_created', 'User was created successfully'],
|
||||
['authentication_administration_user_create_failed', 'Error creating new user'],
|
||||
['authentication_administration_users_delete_confirmation', "You're going to delete these users: "],
|
||||
['authentication_administration_users_filters_search_placeholder', 'Search for the user name...'],
|
||||
['authentication_administration_users_filters_status_enabled', 'ENABLED'],
|
||||
['authentication_administration_users_filters_status_disabled', 'DISABLED'],
|
||||
['authentication_administration_users_filters_status_all', 'ALL'],
|
||||
|
||||
['administration_configuration_wizard_configuration_admin', 'Administrator Credentials'],
|
||||
['administration_configuration_wizard_configuration_admin_name', 'Login'],
|
||||
|
||||
@@ -15,10 +15,16 @@ export default [
|
||||
['authentication_administration_tools_refresh_tooltip', 'Aggiorna la lista utenti'],
|
||||
['authentication_administration_tools_delete_tooltip', 'Elimina gli utenti selezionati'],
|
||||
['authentication_administration_tools_refresh_success', 'Lista degli utenti ricaricata'],
|
||||
['authentication_administration_tools_refresh_fail', 'Users update failed'],
|
||||
['authentication_administration_user_delete_fail', 'Error deleting user'],
|
||||
['authentication_administration_user_update_failed', 'Errore al salvataggio utente'],
|
||||
['authentication_administration_user_updated', 'Utente aggiornato'],
|
||||
['authentication_administration_user_created', 'Utente creato con successo'],
|
||||
['authentication_administration_user_create_failed', 'Errore di creazione nuovo utente'],
|
||||
['authentication_administration_users_filters_search_placeholder', 'Search for the user name...'],
|
||||
['authentication_administration_users_filters_status_enabled', 'ENABLED'],
|
||||
['authentication_administration_users_filters_status_disabled', 'DISABLED'],
|
||||
['authentication_administration_users_filters_status_all', 'ALL'],
|
||||
|
||||
['administration_teams_team_info_created', 'Team created'],
|
||||
['administration_teams_team_info_updated', 'Team updated'],
|
||||
|
||||
@@ -18,11 +18,17 @@ export default [
|
||||
['authentication_administration_tools_refresh_tooltip', 'Обновить список пользователей'],
|
||||
['authentication_administration_tools_delete_tooltip', 'Удалить выбранных пользователей'],
|
||||
['authentication_administration_tools_refresh_success', 'Список пользователей обновлен'],
|
||||
['authentication_administration_tools_refresh_fail', 'Не удалось обновить список пользователей'],
|
||||
['authentication_administration_user_delete_fail', 'Не удалось удалить пользователя'],
|
||||
['authentication_administration_user_update_failed', 'Не удалось обновить пользователя'],
|
||||
['authentication_administration_user_updated', 'Пользователь обновлен'],
|
||||
['authentication_administration_user_created', 'Пользователь создан успешно'],
|
||||
['authentication_administration_user_create_failed', 'Не удалось создать пользователя'],
|
||||
['authentication_administration_users_delete_confirmation', 'Вы собираетесь удалить следующих пользователей: '],
|
||||
['authentication_administration_users_filters_search_placeholder', 'Поиск по имени пользователя...'],
|
||||
['authentication_administration_users_filters_status_enabled', 'ВКЛЮЧЕН'],
|
||||
['authentication_administration_users_filters_status_disabled', 'ВЫКЛЮЧЕН'],
|
||||
['authentication_administration_users_filters_status_all', 'ВСЕ'],
|
||||
|
||||
['administration_configuration_wizard_configuration_admin', 'Учетные данные администратора'],
|
||||
['administration_configuration_wizard_configuration_admin_name', 'Логин'],
|
||||
|
||||
@@ -17,11 +17,17 @@ export default [
|
||||
['authentication_administration_tools_refresh_tooltip', '刷新用户列表'],
|
||||
['authentication_administration_tools_delete_tooltip', '删除选中的用户'],
|
||||
['authentication_administration_tools_refresh_success', '用户列表已刷新'],
|
||||
['authentication_administration_tools_refresh_fail', 'Users update failed'],
|
||||
['authentication_administration_user_delete_fail', 'Error deleting user'],
|
||||
['authentication_administration_user_update_failed', '保存用户出错'],
|
||||
['authentication_administration_user_updated', '用户信息已更新'],
|
||||
['authentication_administration_user_created', '创建用户成功'],
|
||||
['authentication_administration_user_create_failed', '创建新用户出错'],
|
||||
['authentication_administration_users_delete_confirmation', '您将删除这些用户:'],
|
||||
['authentication_administration_users_filters_search_placeholder', 'Search for the user name...'],
|
||||
['authentication_administration_users_filters_status_enabled', 'ENABLED'],
|
||||
['authentication_administration_users_filters_status_disabled', 'DISABLED'],
|
||||
['authentication_administration_users_filters_status_all', 'ALL'],
|
||||
|
||||
['administration_configuration_wizard_configuration_admin', '管理员凭据'],
|
||||
['administration_configuration_wizard_configuration_admin_name', '登录'],
|
||||
|
||||
+1
@@ -26,6 +26,7 @@ export const NetworkHandlerAuthForm = observer<Props>(function NetworkHandlerAut
|
||||
const translate = useTranslate();
|
||||
const handler = useMapResource(NetworkHandlerAuthForm, NetworkHandlerResource, id);
|
||||
|
||||
//@TODO Do not mutate state in component body
|
||||
if (!networkHandlersConfig.some(state => state.id === id)) {
|
||||
networkHandlersConfig.push({
|
||||
id,
|
||||
|
||||
@@ -49,7 +49,6 @@ export const DBAuthDialogFooter = observer<React.PropsWithChildren<Props>>(funct
|
||||
type="button"
|
||||
mod={['unelevated']}
|
||||
disabled={isAuthenticating}
|
||||
loading={isAuthenticating}
|
||||
onClick={onLogin}
|
||||
>
|
||||
{translate('authentication_login')}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
useFocus,
|
||||
ErrorMessage,
|
||||
useStyles,
|
||||
Loader,
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { IConnectionInfoParams, useConnectionInfo, useDBDriver } from '@cloudbeaver/core-connections';
|
||||
import { useController } from '@cloudbeaver/core-di';
|
||||
@@ -86,20 +87,20 @@ export const DatabaseAuthDialog: DialogComponent<Payload> = observer(function Da
|
||||
)}
|
||||
onReject={options?.persistent ? undefined : rejectDialog}
|
||||
>
|
||||
<SubmittingForm
|
||||
ref={focusedRef}
|
||||
disabled={!connection.isLoaded() || connection.isLoading() || !controller.configured}
|
||||
onSubmit={controller.login}
|
||||
>
|
||||
<ConnectionAuthenticationForm
|
||||
config={controller.config}
|
||||
authModelId={authModelId}
|
||||
authProperties={connection.connectionInfo?.authProperties}
|
||||
networkHandlers={payload.networkHandlers}
|
||||
formId={`${payload.connection.projectId}:${payload.connection.connectionId}`}
|
||||
allowSaveCredentials={credentialsSavingEnabled}
|
||||
disabled={controller.isAuthenticating}
|
||||
/>
|
||||
<SubmittingForm ref={focusedRef} onSubmit={controller.login}>
|
||||
{!connection.isLoaded() || connection.isLoading() || !controller.configured
|
||||
? <Loader />
|
||||
: (
|
||||
<ConnectionAuthenticationForm
|
||||
config={controller.config}
|
||||
authModelId={authModelId}
|
||||
authProperties={connection.connectionInfo?.authProperties}
|
||||
networkHandlers={payload.networkHandlers}
|
||||
formId={`${payload.connection.projectId}:${payload.connection.connectionId}`}
|
||||
allowSaveCredentials={credentialsSavingEnabled}
|
||||
disabled={controller.isAuthenticating}
|
||||
/>
|
||||
)}
|
||||
</SubmittingForm>
|
||||
</CommonDialogWrapper>
|
||||
);
|
||||
|
||||
@@ -191,9 +191,9 @@ export class PluginBootstrap extends Bootstrap {
|
||||
|
||||
try {
|
||||
const nodeInfo = contexts.getContext(this.navNodeManagerService.navigationNavNodeContext);
|
||||
const node = await this.navNodeInfoResource.load(data.nodeId);
|
||||
const node = this.navNodeInfoResource.get(data.nodeId);
|
||||
|
||||
if (node.nodeType !== RESOURCE_NODE_TYPE || !isScript(node.id)) {
|
||||
if (!node || node.nodeType !== RESOURCE_NODE_TYPE || !isScript(node.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ export const topMenuStyles = css`
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
MenuButton, MenuBarElement {
|
||||
MenuBarElement {
|
||||
&:hover, &:global([aria-expanded="true"]) {
|
||||
background: #338ecc;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@cloudbeaver/product-default",
|
||||
"sideEffects": false,
|
||||
"version": "22.2.4",
|
||||
"version": "22.3.0",
|
||||
"description": "",
|
||||
"license": "Apache-2.0",
|
||||
"main": "lib/index.js",
|
||||
@@ -22,8 +22,6 @@
|
||||
"@cloudbeaver/core-bootstrap": "0.1.0",
|
||||
"@cloudbeaver/core-di": "0.1.0",
|
||||
"@cloudbeaver/core-product": "0.1.0",
|
||||
"@cloudbeaver/plugin-user-profile": "0.1.0",
|
||||
"@cloudbeaver/plugin-help": "0.1.0",
|
||||
"@cloudbeaver/plugin-administration": "0.1.0",
|
||||
"@cloudbeaver/plugin-authentication": "0.1.0",
|
||||
"@cloudbeaver/plugin-authentication-administration": "0.1.0",
|
||||
@@ -36,6 +34,7 @@
|
||||
"@cloudbeaver/plugin-data-viewer": "0.1.0",
|
||||
"@cloudbeaver/plugin-datasource-context-switch": "0.1.0",
|
||||
"@cloudbeaver/plugin-ddl-viewer": "0.1.0",
|
||||
"@cloudbeaver/plugin-help": "0.1.0",
|
||||
"@cloudbeaver/plugin-localization": "0.1.0",
|
||||
"@cloudbeaver/plugin-log-viewer": "0.1.0",
|
||||
"@cloudbeaver/plugin-navigation-tree": "0.1.0",
|
||||
@@ -48,6 +47,7 @@
|
||||
"@cloudbeaver/plugin-sql-editor-screen": "0.1.0",
|
||||
"@cloudbeaver/plugin-theme": "0.1.0",
|
||||
"@cloudbeaver/plugin-top-app-bar": "0.1.0",
|
||||
"@cloudbeaver/plugin-user-profile": "0.1.0",
|
||||
"@cloudbeaver/plugin-version": "0.1.0",
|
||||
"@cloudbeaver/plugin-version-update-administration": "0.1.0"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user