mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge branch 'devel' into feat/execution-plan
# Conflicts: # webapp/packages/core-app/public/icons/icons.svg
This commit is contained in:
@@ -92,6 +92,8 @@ type ServerConfig {
|
||||
|
||||
serverURL: String!
|
||||
|
||||
rootURI: String!
|
||||
|
||||
anonymousAccessEnabled: Boolean
|
||||
authenticationEnabled: Boolean @deprecated
|
||||
supportsCustomConnections: Boolean
|
||||
|
||||
@@ -64,6 +64,11 @@ public class WebServerConfig {
|
||||
return CommonUtils.notEmpty(application.getServerURL());
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getRootURI() {
|
||||
return CommonUtils.notEmpty(application.getRootURI());
|
||||
}
|
||||
|
||||
@Property
|
||||
public boolean isAnonymousAccessEnabled() {
|
||||
return application.getAppConfiguration().isAnonymousAccessEnabled();
|
||||
|
||||
+29
-1
@@ -1,5 +1,7 @@
|
||||
package io.cloudbeaver.server.jetty;
|
||||
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
import org.eclipse.jetty.http.HttpContent;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.server.ResourceService;
|
||||
@@ -9,7 +11,10 @@ import javax.servlet.ServletException;
|
||||
import javax.servlet.annotation.WebServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Enumeration;
|
||||
|
||||
@WebServlet(urlPatterns = "/")
|
||||
public class CBStaticServlet extends DefaultServlet {
|
||||
@@ -26,10 +31,33 @@ public class CBStaticServlet extends DefaultServlet {
|
||||
}
|
||||
|
||||
private static ResourceService makeResourceService() {
|
||||
ResourceService resourceService = new ResourceService();
|
||||
ResourceService resourceService = new ProxyResourceService();
|
||||
resourceService.setCacheControl(new HttpField(HttpHeader.CACHE_CONTROL, "public, max-age=" + STATIC_CACHE_SECONDS));
|
||||
return resourceService;
|
||||
}
|
||||
|
||||
|
||||
private static class ProxyResourceService extends ResourceService {
|
||||
@Override
|
||||
protected boolean sendData(HttpServletRequest request, HttpServletResponse response, boolean include, HttpContent content, Enumeration<String> reqRanges) throws IOException {
|
||||
if (content.getResource().getName().endsWith("index.html")) {
|
||||
return patchIndexHtml(response, content);
|
||||
}
|
||||
return super.sendData(request, response, include, content, reqRanges);
|
||||
}
|
||||
|
||||
private boolean patchIndexHtml(HttpServletResponse response, HttpContent content) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
content.getResource().writeTo(baos, 0, content.getContentLengthValue());
|
||||
String indexContents = new String(baos.toByteArray(), StandardCharsets.UTF_8);
|
||||
indexContents = indexContents.replace("{ROOT_URI}", CBApplication.getInstance().getRootURI());
|
||||
byte[] indexBytes = indexContents.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
putHeaders(response, content, indexBytes.length);
|
||||
response.getOutputStream().write(indexBytes);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,6 +39,7 @@
|
||||
"mobx": "^6.3.0",
|
||||
"mobx-react-lite": "^3.2.0",
|
||||
"node-sass": "^5.0.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"preact": "^10.5.13",
|
||||
"preact-jsx-runtime": "^1.2.0",
|
||||
"preact-render-to-string": "^5.1.19",
|
||||
|
||||
+4
-4
@@ -13,7 +13,7 @@ import { IExecutor, Executor } from '@cloudbeaver/core-executor';
|
||||
import { PermissionsResource, PermissionsService, ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { ScreenService, RouterState } from '@cloudbeaver/core-routing';
|
||||
import { LocalStorageSaveService } from '@cloudbeaver/core-settings';
|
||||
import { BuildVersion } from '@cloudbeaver/core-utils';
|
||||
import { GlobalConstants } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { AdministrationItemService } from '../AdministrationItem/AdministrationItemService';
|
||||
import type { IAdministrationItemRoute } from '../AdministrationItem/IAdministrationItemRoute';
|
||||
@@ -76,7 +76,7 @@ export class AdministrationScreenService {
|
||||
|
||||
this.info = {
|
||||
workspaceId: '',
|
||||
version: BuildVersion.version || '',
|
||||
version: GlobalConstants.version || '',
|
||||
serverVersion: '',
|
||||
configurationMode: false,
|
||||
};
|
||||
@@ -154,13 +154,13 @@ export class AdministrationScreenService {
|
||||
this.info.workspaceId !== this.serverConfigResource.workspaceId
|
||||
|| this.info.configurationMode !== this.isConfigurationMode
|
||||
|| this.info.serverVersion !== this.serverConfigResource.serverVersion
|
||||
|| this.info.version !== BuildVersion.version
|
||||
|| this.info.version !== GlobalConstants.version
|
||||
) {
|
||||
this.clearItemsState();
|
||||
this.info.workspaceId = this.serverConfigResource.workspaceId;
|
||||
this.info.configurationMode = this.isConfigurationMode;
|
||||
this.info.serverVersion = this.serverConfigResource.serverVersion;
|
||||
this.info.version = BuildVersion.version || '';
|
||||
this.info.version = GlobalConstants.version || '';
|
||||
}
|
||||
|
||||
if (defaultState) {
|
||||
|
||||
@@ -186,18 +186,10 @@
|
||||
<symbol id="order-arrow-unknown" viewBox="0 0 16 16">
|
||||
<path fill="currentColor" fill-rule="evenodd" d="M11.5 2L8 6h2.333v8h2.334V6H15l-3.5-4zm-7 12L8 10H5.667V2H3.333v8H1l3.5 4z" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
<symbol id="execution-plan" viewBox="0 0 32 32">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M31 2.5C31 1.11929 29.8807 0 28.5 0H8.39735C6.74049 0 5.39735 1.34315 5.39735 3L5.39734 5L5.4 4.99684V23H7.6V4.5V4.49997C7.59999 3.9996 7.63686 3.49608 7.82593 3.13249C7.90761 2.97541 8.0105 2.86064 8.15272 2.77735C8.29873 2.69185 8.55425 2.6 9 2.6H9.01415H9.02847H9.04296H9.05762H9.07246H9.08746H9.10263H9.11797H9.13348H9.14915H9.16499H9.181H9.19717H9.21351H9.23001H9.24668H9.26351H9.2805H9.29766H9.31497H9.33245H9.35009H9.36788H9.38584H9.40396H9.42223H9.44066H9.45925H9.47799H9.49689H9.51595H9.53516H9.55452H9.57404H9.59371H9.61353H9.63351H9.65363H9.67391H9.69434H9.71491H9.73564H9.75651H9.77753H9.7987H9.82002H9.84148H9.86309H9.88484H9.90673H9.92877H9.95096H9.97328H9.99575H10.0184H10.0411H10.064H10.087H10.1102H10.1335H10.157H10.1805H10.2043H10.2281H10.2521H10.2762H10.3005H10.3249H10.3494H10.3741H10.3989H10.4238H10.4489H10.4741H10.4994H10.5248H10.5504H10.5761H10.602H10.6279H10.654H10.6802H10.7066H10.733H10.7596H10.7863H10.8132H10.8401H10.8672H10.8944H10.9217H10.9492H10.9768H11.0044H11.0322H11.0602H11.0882H11.1163H11.1446H11.173H11.2015H11.2301H11.2589H11.2877H11.3167H11.3457H11.3749H11.4042H11.4336H11.4631H11.4927H11.5225H11.5523H11.5822H11.6123H11.6425H11.6727H11.7031H11.7336H11.7642H11.7948H11.8256H11.8565H11.8875H11.9186H11.9498H11.9811H12.0125H12.044H12.0756H12.1073H12.1391H12.171H12.2029H12.235H12.2672H12.2995H12.3318H12.3643H12.3968H12.4295H12.4622H12.495H12.5279H12.5609H12.594H12.6272H12.6605H12.6938H12.7273H12.7608H12.7944H12.8281H12.8619H12.8958H12.9298H12.9638H12.9979H13.0321H13.0664H13.1008H13.1352H13.1698H13.2044H13.239H13.2738H13.3086H13.3436H13.3786H13.4136H13.4488H13.484H13.5193H13.5546H13.5901H13.6256H13.6612H13.6968H13.7326H13.7683H13.8042H13.8401H13.8761H13.9122H13.9483H13.9846H14.0208H14.0572H14.0936H14.13H14.1665H14.2031H14.2398H14.2765H14.3133H14.3501H14.387H14.424H14.461H14.4981H14.5352H14.5724H14.6096H14.6469H14.6843H14.7217H14.7592H14.7967H14.8343H14.8719H14.9096H14.9473H14.9851H15.0229H15.0608H15.0987H15.1367H15.1747H15.2128H15.251H15.2891H15.3273H15.3656H15.4039H15.4423H15.4807H15.5191H15.5576H15.5961H15.6347H15.6733H15.7119H15.7506H15.7893H15.8281H15.8669H15.9057H15.9446H15.9835H16.0225H16.0615H16.1005H16.1395H16.1786H16.2177H16.2569H16.2961H16.3353H16.3745H16.4138H16.4531H16.4924H16.5318H16.5712H16.6106H16.65H16.6895H16.729H16.7685H16.808H16.8476H16.8872H16.9268H16.9664H17.0061H17.0457H17.0854H17.1251H17.1649H17.2046H17.2444H17.2842H17.324H17.3638H17.4036H17.4435H17.4834H17.5232H17.5631H17.603H17.643H17.6829H17.7228H17.7628H17.8027H17.8427H17.8827H17.9227H17.9627H18.0027H18.0427H18.0827H18.1227H18.1627H18.2028H18.2428H18.2828H18.3229H18.3629H18.4029H18.443H18.483H18.5231H18.5631H18.6031H18.6432H18.6832H18.7232H18.7633H18.8033H18.8433H18.8833H18.9233H18.9633H19.0033H19.0433H19.0833H19.1232H19.1632H19.2031H19.2431H19.283H19.3229H19.3628H19.4027H19.4426H19.4824H19.5223H19.5621H19.6019H19.6417H19.6815H19.7213H19.761H19.8007H19.8404H19.8801H19.9198H19.9594H19.9991H20.0387H20.0782H20.1178H20.1573H20.1968H20.2363H20.2758H20.3152H20.3546H20.394H20.4333H20.4727H20.512H20.5512H20.5905H20.6297H20.6688H20.708H20.7471H20.7861H20.8252H20.8642H20.9032H20.9421H20.981H21.0199H21.0587H21.0975H21.1362H21.1749H21.2136H21.2522H21.2908H21.3294H21.3679H21.4064H21.4448H21.4832H21.5215H21.5598H21.598H21.6362H21.6744H21.7125H21.7506H21.7886H21.8266H21.8645H21.9023H21.9402H21.9779H22.0156H22.0533H22.0909H22.1285H22.166H22.2034H22.2408H22.2782H22.3154H22.3527H22.3898H22.427H22.464H22.501H22.5379H22.5748H22.6116H22.6484H22.6851H22.7217H22.7583H22.7948H22.8312H22.8676H22.9L22.9 28V28C22.9 28.5039 22.8677 29.0333 22.6921 29.4181C22.6146 29.5879 22.5253 29.6909 22.4266 29.7567C22.3313 29.8202 22.1516 29.9 21.8049 29.9H20V26H0V32H6V32.1H14.6488H21.8049C22.5118 32.1 23.1333 31.9298 23.6472 31.587C24.1577 31.2466 24.4855 30.7871 24.6933 30.3319C25.0884 29.4667 25.1 28.4962 25.1 28V28L25.1 5H31V2.5ZM10 7H14V9H10V7ZM14 13H10V15H14V13ZM10 19H14V21H10V19ZM20 7H16V9H20V7ZM16 13H20V15H16V13ZM20 19H16V21H20V19Z" fill="#338ECC"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="32" height="32" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<symbol id="password-hide" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 9C4.13401 9 1.93333 6.3 1 4.5H2.43914C3.39058 6.0143 5.17528 7.75 8 7.75C10.8247 7.75 12.6094 6.0143 13.5609 4.5H15C14.0667 6.3 11.866 9 8 9ZM14.9999 8.77009L13.7144 7.23804L12.9484 7.88081L14.2339 9.41286L14.9999 8.77009ZM8.5 12.2499V10.25H7.50003V12.2499H8.5ZM1 8.77009L2.28554 7.23805L3.05156 7.88082L1.76602 9.41286L1 8.77009ZM4.50498 9.23861L3.75578 11.0929L4.68294 11.4675L5.43213 9.61321L4.50498 9.23861ZM11.4323 9.23861L12.1815 11.0929L11.2543 11.4675L10.5051 9.61321L11.4323 9.23861Z" fill="currentColor"/>
|
||||
</symbol>
|
||||
<symbol id="execution-plan-tab" viewBox="0 0 32 32">
|
||||
<path d="M5 2H19.5858L27 9.41421V30H5V2Z" stroke="#338ECC" stroke-width="2" fill="transparent"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19 1L28 10H19V1ZM8 8H13V12H8V8ZM24 22H19V26H24V22ZM19 15H24V19H19V15ZM11.5 13H9.5V25H10H11.5H18V23H11.5V18H18V16H11.5V13Z" fill="#338ECC"/>
|
||||
<symbol id="password-show" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 11.25C10.8247 11.25 12.6094 9.5143 13.5609 8C12.6094 6.4857 10.8247 4.75 8 4.75C5.17528 4.75 3.39058 6.4857 2.43914 8C3.39058 9.5143 5.17528 11.25 8 11.25ZM15 8C14.0667 9.8 11.866 12.5 8 12.5C4.13401 12.5 1.93333 9.8 1 8C1.93333 6.2 4.13401 3.5 8 3.5C11.866 3.5 14.0667 6.2 15 8ZM7.99997 9.88333C9.0309 9.88333 9.86663 9.0476 9.86663 8.01666C9.86663 6.98573 9.0309 6.15 7.99997 6.15C6.96904 6.15 6.1333 6.98573 6.1333 8.01666C6.1333 9.0476 6.96904 9.88333 7.99997 9.88333Z" fill="currentColor"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 20 KiB |
@@ -79,7 +79,7 @@ export class NavigationTreeService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
await this.navNodeManagerService.loadTree(id);
|
||||
await this.navTreeResource.load(id);
|
||||
return true;
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception);
|
||||
|
||||
@@ -240,13 +240,13 @@ export function useElementsTree(options: IOptions): IElementsTree {
|
||||
|
||||
useExecutor({
|
||||
executor: navNodeInfoResource.onDataOutdated,
|
||||
handlers: [function refreshRoot() {
|
||||
postHandlers: [function refreshRoot() {
|
||||
loadTree(options.root);
|
||||
}],
|
||||
});
|
||||
useExecutor({
|
||||
executor: navTreeResource.onNodeRefresh,
|
||||
handlers: [loadTree],
|
||||
postHandlers: [loadTree],
|
||||
});
|
||||
|
||||
useExecutor({
|
||||
|
||||
@@ -226,7 +226,7 @@ export class NavTreeResource extends CachedMapResource<string, string[]> {
|
||||
private async connectionUpdateHandler(key: ResourceKey<string>) {
|
||||
await this.markOutdated(ROOT_NODE_PATH);
|
||||
|
||||
ResourceKeyUtils.forEach(key, async key => {
|
||||
await ResourceKeyUtils.forEachAsync(key, async key => {
|
||||
const nodeId = NodeManagerUtils.connectionIdToConnectionNodeId(key);
|
||||
|
||||
if (this.has(nodeId)) {
|
||||
@@ -235,14 +235,14 @@ export class NavTreeResource extends CachedMapResource<string, string[]> {
|
||||
if (!connectionInfo?.connected) {
|
||||
this.delete(nodeId);
|
||||
} else {
|
||||
this.markTreeOutdated(nodeId);
|
||||
await this.markTreeOutdated(nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
const node = this.navNodeInfoResource.get(nodeId);
|
||||
|
||||
if (node) {
|
||||
this.markOutdated(node.parentId);
|
||||
await this.markOutdated(node.parentId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { ServerService } from '@cloudbeaver/core-root';
|
||||
import { BuildVersion } from '@cloudbeaver/core-utils';
|
||||
import { GlobalConstants } from '@cloudbeaver/core-utils';
|
||||
|
||||
interface IAppVersion {
|
||||
backendVersion: string;
|
||||
@@ -20,7 +20,7 @@ const VERSION_REGEX = /(\d+\.\d+\.\d+)/;
|
||||
export function useAppVersion(short = false): IAppVersion {
|
||||
const serverService = useService(ServerService);
|
||||
let backendVersion = serverService.config.data?.version || '';
|
||||
let frontendVersion = BuildVersion.version || '';
|
||||
let frontendVersion = GlobalConstants.version || '';
|
||||
|
||||
if (short) {
|
||||
backendVersion = VERSION_REGEX.exec(backendVersion)?.[1] ?? backendVersion;
|
||||
|
||||
@@ -59,16 +59,15 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void>
|
||||
}
|
||||
|
||||
async login(provider: string, credentials: Record<string, string>, link?: boolean): Promise<UserInfo | null> {
|
||||
const processedCredentials = await this.authProviderService.processCredentials(provider, credentials);
|
||||
|
||||
const { authToken } = await this.graphQLService.sdk.authLogin({
|
||||
provider,
|
||||
credentials: processedCredentials,
|
||||
linkUser: link,
|
||||
customIncludeOriginDetails: true,
|
||||
});
|
||||
|
||||
await this.performUpdate(undefined, undefined, async () => {
|
||||
const processedCredentials = await this.authProviderService.processCredentials(provider, credentials);
|
||||
|
||||
const { authToken } = await this.graphQLService.sdk.authLogin({
|
||||
provider,
|
||||
credentials: processedCredentials,
|
||||
linkUser: link,
|
||||
customIncludeOriginDetails: true,
|
||||
});
|
||||
if (this.data === null || link) {
|
||||
this.data = await this.loader();
|
||||
} else {
|
||||
|
||||
@@ -58,6 +58,10 @@ const buttonStyles = composes(
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&[href] {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
@@ -30,6 +30,9 @@ const fieldCheckboxStyles = css`
|
||||
padding-left: 10px;
|
||||
line-height: 16px;
|
||||
}
|
||||
Checkbox[disabled] + field-label {
|
||||
cursor: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
export const FieldCheckboxNew: CheckboxType = function FieldCheckboxNew({
|
||||
@@ -44,9 +47,15 @@ export const FieldCheckboxNew: CheckboxType = function FieldCheckboxNew({
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<field className={className} as="div">
|
||||
<field className={className}>
|
||||
<Checkbox {...(rest as CheckboxBaseProps & ICheckboxControlledProps)} />
|
||||
<field-label htmlFor={rest.id || rest.name} title={rest.title} as="label">{children}</field-label>
|
||||
<field-label
|
||||
htmlFor={rest.id || rest.name}
|
||||
title={rest.title}
|
||||
as="label"
|
||||
>
|
||||
{children}
|
||||
</field-label>
|
||||
</field>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,26 +7,54 @@
|
||||
*/
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useContext } from 'react';
|
||||
import { useCallback, useContext, useState } from 'react';
|
||||
import styled, { use, css } from 'reshadow';
|
||||
|
||||
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { ComponentStyle, composes, useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps';
|
||||
import { Icon } from '../Icon';
|
||||
import { baseFormControlStylesNew } from './baseFormControlStylesNew';
|
||||
import { FormContext } from './FormContext';
|
||||
import { isControlPresented } from './isControlPresented';
|
||||
|
||||
const INPUT_FIELD_STYLES = css`
|
||||
field-label {
|
||||
display: block;
|
||||
composes: theme-typography--body1 from global;
|
||||
font-weight: 500;
|
||||
}
|
||||
field-label:not(:empty) {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
`;
|
||||
const INPUT_FIELD_STYLES = composes(
|
||||
css`
|
||||
Icon {
|
||||
composes: theme-text-on-secondary from global;
|
||||
}
|
||||
`,
|
||||
css`
|
||||
field-label {
|
||||
display: block;
|
||||
composes: theme-typography--body1 from global;
|
||||
font-weight: 500;
|
||||
}
|
||||
field-label:not(:empty) {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
input-container {
|
||||
position: relative;
|
||||
}
|
||||
icon-container {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[disabled] + Icon {
|
||||
cursor: auto;
|
||||
opacity: 0.8;
|
||||
}
|
||||
input:not(:only-child) {
|
||||
padding-right: 32px !important;
|
||||
}
|
||||
`);
|
||||
|
||||
type BaseProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'name' | 'value'> & ILayoutSizeProps & {
|
||||
description?: string;
|
||||
@@ -80,9 +108,19 @@ export const InputFieldNew: InputFieldType = observer(function InputFieldNew({
|
||||
onChange,
|
||||
...rest
|
||||
}: ControlledProps | ObjectProps<any, any>, ref: React.Ref<HTMLInputElement>) {
|
||||
const [passwordRevealed, setPasswordRevealed] = useState(false);
|
||||
const translate = useTranslate();
|
||||
const styles = useStyles(baseFormControlStylesNew, INPUT_FIELD_STYLES, style);
|
||||
const context = useContext(FormContext);
|
||||
|
||||
const revealPassword = useCallback(() => {
|
||||
if (rest.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
setPasswordRevealed(prev => !prev);
|
||||
}, [rest.disabled]);
|
||||
|
||||
const handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = mapValue?.(event.target.value) ?? event.target.value;
|
||||
|
||||
@@ -111,21 +149,37 @@ export const InputFieldNew: InputFieldType = observer(function InputFieldNew({
|
||||
value = mapState(value);
|
||||
}
|
||||
|
||||
const showRevealPasswordButton = rest.type === 'password' && !rest.readOnly;
|
||||
|
||||
return styled(styles)(
|
||||
<field as="div" className={className} {...use({ small, medium, large })}>
|
||||
<field-label as='label' title={rest.title}>{children}{required && ' *'}</field-label>
|
||||
<input
|
||||
ref={ref}
|
||||
role='new'
|
||||
{...rest}
|
||||
name={name}
|
||||
value={value ?? ''}
|
||||
onChange={handleChange}
|
||||
{...use({ mod })}
|
||||
required={required}
|
||||
/>
|
||||
<field className={className} {...use({ small, medium, large })}>
|
||||
<field-label title={rest.title}>{children}{required && ' *'}</field-label>
|
||||
<input-container>
|
||||
<input
|
||||
ref={ref}
|
||||
role='new'
|
||||
{...rest}
|
||||
type={passwordRevealed ? 'text' : rest.type}
|
||||
name={name}
|
||||
value={value ?? ''}
|
||||
onChange={handleChange}
|
||||
{...use({ mod })}
|
||||
required={required}
|
||||
/>
|
||||
{showRevealPasswordButton && (
|
||||
<icon-container
|
||||
title={translate('ui_reveal_password')}
|
||||
onClick={revealPassword}
|
||||
>
|
||||
<Icon
|
||||
name={passwordRevealed ? 'password-hide' : 'password-show'}
|
||||
viewBox='0 0 16 16'
|
||||
/>
|
||||
</icon-container>
|
||||
)}
|
||||
</input-container>
|
||||
{description && (
|
||||
<field-description as='div'>
|
||||
<field-description>
|
||||
{description}
|
||||
</field-description>
|
||||
)}
|
||||
|
||||
@@ -6,4 +6,7 @@
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="preload" as="font"
|
||||
crossorigin="anonymous" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" rel="stylesheet" crossorigin="anonymous" />
|
||||
<link rel="prefetch" href="icons/icons.svg" as="image" type="image/svg+xml" />
|
||||
<link rel="prefetch" href="icons/icons.svg" as="image" type="image/svg+xml" />
|
||||
<script type="text/javascript">
|
||||
var _ROOT_URI_ = '{ROOT_URI}';
|
||||
</script>
|
||||
@@ -6,12 +6,15 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import type { SVGAttributes } from 'react';
|
||||
|
||||
import { GlobalConstants } from '@cloudbeaver/core-utils';
|
||||
|
||||
export const Icon: React.FC<SVGAttributes<any>> = function Icon(props) {
|
||||
const { name = '', className, ...rest } = props;
|
||||
const cn = `icon ${className || ''}`;
|
||||
const url = name.startsWith('/') ? name : `icons/icons.svg#${name}`;
|
||||
const url = name.startsWith('/') ? path.join(GlobalConstants.rootURI, name) : `icons/icons.svg#${name}`;
|
||||
return (
|
||||
<svg {...rest} className={cn} aria-hidden="true" focusable="false">
|
||||
<use href={url} />
|
||||
|
||||
@@ -110,6 +110,9 @@ module.exports = (env, argv) => {
|
||||
'react-dom': 'preact/compat',
|
||||
'react-dom$': 'preact/compat',
|
||||
},
|
||||
fallback: {
|
||||
path: require.resolve("path-browserify"),
|
||||
}
|
||||
},
|
||||
resolveLoader: {
|
||||
modules: nodeModules,
|
||||
|
||||
@@ -31,6 +31,7 @@ export const defaultENLocale = [
|
||||
['ui_copy_to_clipboard_failed_to_copy', 'Failed to copy'],
|
||||
['ui_clipboard_access_denied_title', 'Clipboard access denied'],
|
||||
['ui_clipboard_access_denied_message', 'You need to give access to clipboard to use some of the app features'],
|
||||
['ui_reveal_password', 'Show or hide password'],
|
||||
['root_permission_denied', 'You have no permissions'],
|
||||
['app_root_session_expired_title', 'Session expired'],
|
||||
['app_root_session_expired_message', 'Session has been expired. Do you want to reload?'],
|
||||
|
||||
@@ -29,6 +29,7 @@ export const defaultRULocale = [
|
||||
['ui_copy_to_clipboard_failed_to_copy', 'Не удалось скопировать'],
|
||||
['ui_clipboard_access_denied_title', 'Доступ к буферу обмена запрещен'],
|
||||
['ui_clipboard_access_denied_message', 'Чтобы использовать некоторые функции приложения, необходим доступ к буферу обмена'],
|
||||
['ui_reveal_password', 'Показать или скрыть пароль'],
|
||||
['root_permission_denied', 'Отказано в доступе'],
|
||||
['app_root_session_expired_title', 'Сессия истекла'],
|
||||
['app_root_session_expired_message', 'Сессия истекла. Хотите перезагрузить страницу?'],
|
||||
|
||||
@@ -84,15 +84,16 @@ export class ServerConfigResource extends CachedDataResource<ServerConfig | null
|
||||
|
||||
return (
|
||||
this.update.serverName !== this.data.name
|
||||
|| this.update.sessionExpireTime !== this.data.sessionExpireTime
|
||||
|| this.update.serverURL !== this.data.serverURL
|
||||
|| this.update.sessionExpireTime !== this.data.sessionExpireTime
|
||||
|
||||
|| this.update.anonymousAccessEnabled !== this.data.anonymousAccessEnabled
|
||||
|| this.update.anonymousAccessEnabled !== this.data.anonymousAccessEnabled
|
||||
|
||||
|| this.update.adminCredentialsSaveEnabled !== this.data.adminCredentialsSaveEnabled
|
||||
|| this.update.publicCredentialsSaveEnabled !== this.data.publicCredentialsSaveEnabled
|
||||
|| this.update.adminCredentialsSaveEnabled !== this.data.adminCredentialsSaveEnabled
|
||||
|| this.update.publicCredentialsSaveEnabled !== this.data.publicCredentialsSaveEnabled
|
||||
|
||||
|| this.update.customConnectionsEnabled !== this.data.supportsCustomConnections
|
||||
|| !isArraysEqual(this.update.enabledAuthProviders || [], this.data.enabledAuthProviders)
|
||||
|| this.update.customConnectionsEnabled !== this.data.supportsCustomConnections
|
||||
|| !isArraysEqual(this.update.enabledAuthProviders || [], this.data.enabledAuthProviders)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,33 +131,36 @@ export class ServerConfigResource extends CachedDataResource<ServerConfig | null
|
||||
}
|
||||
}
|
||||
|
||||
async saveDefaultNavigatorSettings(): Promise<void> {
|
||||
async save(skipConfigUpdate = false): Promise<void> {
|
||||
await this.performUpdate(undefined, undefined, async () => {
|
||||
await this.graphQLService.sdk.setDefaultNavigatorSettings({ settings: this.navigatorSettingsUpdate });
|
||||
if (this.isNavigatorSettingsChanged()) {
|
||||
await this.graphQLService.sdk.setDefaultNavigatorSettings({ settings: this.navigatorSettingsUpdate });
|
||||
|
||||
if (this.data) {
|
||||
this.data.defaultNavigatorSettings = { ...this.navigatorSettingsUpdate };
|
||||
} else {
|
||||
if (this.data) {
|
||||
this.data.defaultNavigatorSettings = { ...this.navigatorSettingsUpdate };
|
||||
} else {
|
||||
this.data = await this.loader();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isChanged() && !skipConfigUpdate) {
|
||||
await this.graphQLService.sdk.configureServer({
|
||||
configuration: this.update,
|
||||
});
|
||||
this.data = await this.loader();
|
||||
}
|
||||
}, () => !this.isNavigatorSettingsChanged());
|
||||
}, () => !this.isNavigatorSettingsChanged() && (!this.isChanged() || skipConfigUpdate));
|
||||
}
|
||||
|
||||
async save(onlyRestart = false): Promise<void> {
|
||||
async finishConfiguration(onlyRestart = false): Promise<void> {
|
||||
await this.performUpdate(undefined, undefined, async () => {
|
||||
await this.graphQLService.sdk.configureServer({
|
||||
configuration: (onlyRestart && !this.isChanged()) ? {} : this.update,
|
||||
configuration: !this.isChanged() && onlyRestart ? {} : this.update,
|
||||
});
|
||||
|
||||
this.data = await this.loader();
|
||||
}, () => !this.isChanged() && !onlyRestart);
|
||||
}
|
||||
|
||||
async saveAllData(): Promise<void> {
|
||||
await this.saveDefaultNavigatorSettings();
|
||||
await this.save();
|
||||
}
|
||||
|
||||
protected async loader(): Promise<ServerConfig> {
|
||||
const { serverConfig } = await this.graphQLService.sdk.serverConfig();
|
||||
|
||||
@@ -169,6 +173,7 @@ export class ServerConfigResource extends CachedDataResource<ServerConfig | null
|
||||
Object.assign(this.navigatorSettingsUpdate, serverConfig.defaultNavigatorSettings);
|
||||
|
||||
this.update.serverName = serverConfig.name;
|
||||
this.update.serverURL = serverConfig.serverURL;
|
||||
this.update.sessionExpireTime = serverConfig.sessionExpireTime;
|
||||
|
||||
this.update.adminName = undefined;
|
||||
|
||||
@@ -15,6 +15,7 @@ import type { DoneFn } from 'router5/dist/types/base';
|
||||
|
||||
import { injectable, Bootstrap } from '@cloudbeaver/core-di';
|
||||
import { Executor, ExecutorInterrupter, IExecutor } from '@cloudbeaver/core-executor';
|
||||
import { GlobalConstants } from '@cloudbeaver/core-utils';
|
||||
|
||||
export type RouterState = State;
|
||||
|
||||
@@ -77,6 +78,7 @@ export class RouterService extends Bootstrap {
|
||||
private configure() {
|
||||
this.router.usePlugin(browserPlugin({
|
||||
useHash: true,
|
||||
base: GlobalConstants.rootURI,
|
||||
}));
|
||||
|
||||
this.router.subscribe(this.onRouteChange.bind(this));
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { GlobalConstants } from '@cloudbeaver/core-utils';
|
||||
|
||||
@injectable()
|
||||
export class EnvironmentService {
|
||||
readonly gqlEndpoint = '/api/gql';
|
||||
readonly staticEndpoint = '/api';
|
||||
readonly gqlEndpoint = path.join(GlobalConstants.serviceURI, 'gql');
|
||||
readonly staticEndpoint = GlobalConstants.serviceURI;
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ export abstract class CachedMapResource<
|
||||
markOutdated(key: ResourceKey<TKey>): Promise<void>
|
||||
async markOutdated(key?: ResourceKey<TKey>): Promise<void> {
|
||||
if (!key) {
|
||||
key = resourceKeyList(Array.from(this.data.keys()));
|
||||
key = resourceKeyList(this.keys);
|
||||
} else {
|
||||
key = this.transformParam(key);
|
||||
}
|
||||
@@ -164,7 +164,7 @@ export abstract class CachedMapResource<
|
||||
markUpdated(key: ResourceKey<TKey>): void
|
||||
markUpdated(key?: ResourceKey<TKey>): void {
|
||||
if (!key) {
|
||||
key = resourceKeyList(Array.from(this.data.keys()));
|
||||
key = resourceKeyList(this.keys);
|
||||
} else {
|
||||
key = this.transformParam(key);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { computed, observable } from 'mobx';
|
||||
|
||||
import { AuthProviderService, UserInfoResource } from '@cloudbeaver/core-authentication';
|
||||
import { useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
@@ -18,11 +20,12 @@ interface IAuthenticationAction {
|
||||
auth: () => Promise<void>;
|
||||
}
|
||||
interface IAuthenticationData extends IAuthenticationAction {
|
||||
onAuthenticate?: () => void;
|
||||
authenticating: boolean;
|
||||
onAuthenticate?: () => Promise<any> | void;
|
||||
}
|
||||
|
||||
export type Options = {
|
||||
onAuthenticate?: () => void;
|
||||
onAuthenticate?: () => Promise<any> | void;
|
||||
} & ({
|
||||
origin: ObjectOrigin;
|
||||
} | {
|
||||
@@ -45,19 +48,28 @@ export function useAuthenticationAction(options: Options): IAuthenticationAction
|
||||
}
|
||||
|
||||
return useObjectRef<IAuthenticationData>({
|
||||
authenticating: false,
|
||||
type,
|
||||
subType,
|
||||
onAuthenticate: options.onAuthenticate,
|
||||
get authorized() {
|
||||
return userInfoService.hasToken(this.type, this.subType);
|
||||
return !this.authenticating && userInfoService.hasToken(this.type, this.subType);
|
||||
},
|
||||
async auth() {
|
||||
await authProviderService.requireProvider(this.type, this.subType);
|
||||
this.onAuthenticate?.();
|
||||
this.authenticating = true;
|
||||
try {
|
||||
await authProviderService.requireProvider(this.type, this.subType);
|
||||
await this.onAuthenticate?.();
|
||||
} finally {
|
||||
this.authenticating = false;
|
||||
}
|
||||
},
|
||||
}, {
|
||||
type,
|
||||
subType,
|
||||
onAuthenticate: options.onAuthenticate,
|
||||
}, undefined, ['auth']);
|
||||
}, {
|
||||
authorized: computed,
|
||||
authenticating: observable,
|
||||
}, ['auth']);
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
declare const _VERSION_: string | undefined;
|
||||
|
||||
export const BuildVersion = {
|
||||
get version(): string | undefined {
|
||||
return _VERSION_;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 path from 'path';
|
||||
|
||||
declare const _VERSION_: string | undefined;
|
||||
declare const _ROOT_URI_: string | undefined;
|
||||
|
||||
export const GlobalConstants = {
|
||||
get version(): string | undefined {
|
||||
return _VERSION_;
|
||||
},
|
||||
|
||||
get rootURI(): string {
|
||||
const defaultURI = '/';
|
||||
|
||||
if (_ROOT_URI_ === '{ROOT_URI}') {
|
||||
return defaultURI;
|
||||
}
|
||||
return _ROOT_URI_ ?? defaultURI;
|
||||
},
|
||||
|
||||
get serviceURI(): string {
|
||||
return path.join(this.rootURI, 'api');
|
||||
},
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './BuildVersion';
|
||||
export * from './flat';
|
||||
export * from './MetadataMap';
|
||||
export * from './OrderedMap';
|
||||
@@ -6,6 +5,7 @@ export * from './parseJSONFlat';
|
||||
export * from './uuid_';
|
||||
export * from './generateFileName';
|
||||
export * from './getMIME';
|
||||
export * from './GlobalConstants';
|
||||
export * from './isArraysEqual';
|
||||
export * from './isPropertiesEqual';
|
||||
export * from './isValuesEqual';
|
||||
|
||||
+2
-2
@@ -33,7 +33,7 @@ export const ServerConfigurationInfoForm: React.FC<Props> = observer(function Se
|
||||
state={state.serverConfig}
|
||||
mod='surface'
|
||||
required
|
||||
tiny
|
||||
medium
|
||||
>
|
||||
{translate('administration_configuration_wizard_configuration_server_name')}
|
||||
</InputFieldNew>
|
||||
@@ -44,7 +44,7 @@ export const ServerConfigurationInfoForm: React.FC<Props> = observer(function Se
|
||||
state={state.serverConfig}
|
||||
mod='surface'
|
||||
required
|
||||
tiny
|
||||
medium
|
||||
>
|
||||
{translate('administration_configuration_wizard_configuration_server_url')}
|
||||
</InputFieldNew>
|
||||
|
||||
+3
-4
@@ -196,11 +196,10 @@ export class ServerConfigurationService {
|
||||
}
|
||||
|
||||
try {
|
||||
await this.serverConfigResource.saveDefaultNavigatorSettings();
|
||||
if (!data.configurationWizard || data.finish) {
|
||||
await this.serverConfigResource.save();
|
||||
}
|
||||
await this.serverConfigResource.save(data.configurationWizard);
|
||||
|
||||
if (data.configurationWizard && data.finish) {
|
||||
await this.serverConfigResource.finishConfiguration();
|
||||
await this.sessionDataResource.refresh();
|
||||
}
|
||||
} catch (exception) {
|
||||
|
||||
+43
-24
@@ -31,8 +31,9 @@ export const AuthenticationProviders: PlaceholderComponent<IConfigurationPlaceho
|
||||
throw new Error('Form state should be provided');
|
||||
}
|
||||
|
||||
const localExists = providers.resource.has(AUTH_PROVIDER_LOCAL_ID);
|
||||
const externalAuthentication = providers.data.length === 1 && !localExists;
|
||||
const localProvider = providers.resource.get(AUTH_PROVIDER_LOCAL_ID);
|
||||
const services = providers.resource.values.filter(provider => provider.id !== AUTH_PROVIDER_LOCAL_ID);
|
||||
const externalAuthentication = providers.data.length === 1 && localProvider === undefined;
|
||||
const authenticationDisabled = serverConfig.enabledAuthProviders?.length === 0;
|
||||
|
||||
useExecutor({
|
||||
@@ -63,34 +64,52 @@ export const AuthenticationProviders: PlaceholderComponent<IConfigurationPlaceho
|
||||
>
|
||||
{translate('administration_configuration_wizard_configuration_anonymous_access')}
|
||||
</SwitchNew>
|
||||
<GroupTitle>{translate('administration_configuration_wizard_configuration_authentication_provider')}</GroupTitle>
|
||||
<Loader state={providers}>
|
||||
{() => styled(styles)(
|
||||
<>
|
||||
{providers.data.map(provider => provider && (
|
||||
<SwitchNew
|
||||
key={provider.id}
|
||||
value={provider.id}
|
||||
name="enabledAuthProviders"
|
||||
state={serverConfig}
|
||||
description={provider.description}
|
||||
mod={['primary']}
|
||||
small
|
||||
autoHide
|
||||
>
|
||||
{provider.label}
|
||||
</SwitchNew>
|
||||
))}
|
||||
</>
|
||||
{() => localProvider && styled(styles)(
|
||||
<SwitchNew
|
||||
key={localProvider.id}
|
||||
value={localProvider.id}
|
||||
name="enabledAuthProviders"
|
||||
state={serverConfig}
|
||||
description={localProvider.description}
|
||||
mod={['primary']}
|
||||
small
|
||||
autoHide
|
||||
>
|
||||
{localProvider.label}
|
||||
</SwitchNew>
|
||||
)}
|
||||
</Loader>
|
||||
</Group>
|
||||
{configurationWizard && localExists ? (
|
||||
{configurationWizard && localProvider && (
|
||||
<ServerConfigurationAdminForm serverConfig={serverConfig} />
|
||||
) : (
|
||||
<Container medium />
|
||||
)}
|
||||
<Container medium />
|
||||
{services.length > 0 ? (
|
||||
<Group key='services' form gap medium>
|
||||
<GroupTitle>{translate('administration_configuration_wizard_configuration_services')}</GroupTitle>
|
||||
<Loader state={providers}>
|
||||
{() => styled(styles)(
|
||||
<>
|
||||
{services.map(provider => (
|
||||
<SwitchNew
|
||||
key={provider.id}
|
||||
value={provider.id}
|
||||
name="enabledAuthProviders"
|
||||
state={serverConfig}
|
||||
description={provider.description}
|
||||
mod={['primary']}
|
||||
small
|
||||
autoHide
|
||||
>
|
||||
{provider.label}
|
||||
</SwitchNew>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</Loader>
|
||||
</Group>
|
||||
) : <Container medium />}
|
||||
{!(configurationWizard && localProvider) && <Container medium />}
|
||||
</Container>
|
||||
);
|
||||
});
|
||||
|
||||
+3
-4
@@ -11,6 +11,7 @@ import { observer } from 'mobx-react-lite';
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { UsersResource } from '@cloudbeaver/core-authentication';
|
||||
import { BASE_CONTAINERS_STYLES, Container, ErrorMessage, Group, InputFieldNew, SubmittingForm, useFocus } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { CommonDialogService, CommonDialogWrapper, DialogComponent, DialogComponentProps } from '@cloudbeaver/core-dialogs';
|
||||
@@ -21,7 +22,6 @@ import { GQLError, GQLErrorCatcher } from '@cloudbeaver/core-sdk';
|
||||
import { composes, useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { ChangeUserPasswordDialogFooter } from './ChangeUserPasswordDialogFooter';
|
||||
import { UsersResource } from '@cloudbeaver/core-authentication';
|
||||
|
||||
const styles = composes(
|
||||
css`
|
||||
@@ -38,7 +38,6 @@ const styles = composes(
|
||||
}
|
||||
`);
|
||||
|
||||
|
||||
interface IState {
|
||||
oldPassword: string;
|
||||
newPassword: string;
|
||||
@@ -55,7 +54,7 @@ export const ChangeUserPasswordDialog: DialogComponent<null, null> = observer(
|
||||
rejectDialog,
|
||||
}: DialogComponentProps<null, null>) {
|
||||
const [focusedRef] = useFocus<HTMLInputElement>({});
|
||||
const usersResource = useService(UsersResource)
|
||||
const usersResource = useService(UsersResource);
|
||||
const commonDialogService = useService(CommonDialogService);
|
||||
const notificationService = useService(NotificationService);
|
||||
const style = useStyles(styles, BASE_CONTAINERS_STYLES);
|
||||
@@ -131,7 +130,7 @@ export const ChangeUserPasswordDialog: DialogComponent<null, null> = observer(
|
||||
small
|
||||
required
|
||||
>
|
||||
{translate('authentication_user_old_password')}
|
||||
{translate('authentication_user_current_password')}
|
||||
</InputFieldNew>
|
||||
<InputFieldNew
|
||||
type='password'
|
||||
|
||||
@@ -27,7 +27,7 @@ export default [
|
||||
['authentication_administration_user_create_failed', 'Error creating new user'],
|
||||
['authentication_user_name', 'User name'],
|
||||
['authentication_user_credentials', 'Credentials'],
|
||||
['authentication_user_old_password', 'Old password'],
|
||||
['authentication_user_current_password', 'Current password'],
|
||||
['authentication_user_new_password', 'New password'],
|
||||
['authentication_user_password', 'User password'],
|
||||
['authentication_user_password_repeat', 'Repeat password'],
|
||||
@@ -48,7 +48,7 @@ export default [
|
||||
['administration_configuration_wizard_configuration_anonymous_access', 'Allow anonymous access'],
|
||||
['administration_configuration_wizard_configuration_anonymous_access_description', 'Allows to work with CloudBeaver without user authentication'],
|
||||
['administration_configuration_wizard_configuration_authentication_group', 'Authentication settings'],
|
||||
['administration_configuration_wizard_configuration_authentication_provider', 'Authentication Source'],
|
||||
['administration_configuration_wizard_configuration_services', 'Services'],
|
||||
['administration_configuration_wizard_configuration_authentication', 'Enable users authentication'],
|
||||
['administration_configuration_wizard_configuration_authentication_description', 'Allows users to authenticate. Otherwise only anonymous access is enabled'],
|
||||
];
|
||||
|
||||
@@ -25,7 +25,7 @@ export default [
|
||||
['authentication_administration_user_create_failed', 'Не удалось создать пользователя'],
|
||||
['authentication_user_name', 'Имя'],
|
||||
['authentication_user_credentials', 'Учетные данные'],
|
||||
['authentication_user_old_password', 'Старый пароль'],
|
||||
['authentication_user_current_password', 'Текущий пароль'],
|
||||
['authentication_user_new_password', 'Новый пароль'],
|
||||
['authentication_user_password', 'Пароль'],
|
||||
['authentication_user_password_repeat', 'Повторите пароль'],
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { GraphQLService, DataTransferParameters, EnvironmentService } from '@cloudbeaver/core-sdk';
|
||||
@@ -83,7 +85,7 @@ export class DataExportProcessService {
|
||||
if (!dataFileId) {
|
||||
return;
|
||||
}
|
||||
return `${this.environmentService.staticEndpoint}/data/${dataFileId}`;
|
||||
return path.join(this.environmentService.staticEndpoint, '/data/', dataFileId);
|
||||
}
|
||||
|
||||
async exportData(
|
||||
|
||||
+3
-2
@@ -26,7 +26,6 @@ export const CellRenderer: React.FC<CellRendererProps<any>> = observer(function
|
||||
const selectionContext = useContext(DataGridSelectionContext);
|
||||
const editingContext = useContext(EditingContext);
|
||||
const editor = dataGridContext?.model.source.getEditor(dataGridContext.resultIndex);
|
||||
const dataGridApi = dataGridContext?.getDataGridApi();
|
||||
const mouse = useMouse<HTMLDivElement>({});
|
||||
|
||||
const classes: string[] = [];
|
||||
@@ -45,6 +44,8 @@ export const CellRenderer: React.FC<CellRendererProps<any>> = observer(function
|
||||
}
|
||||
|
||||
const handleMouseDown = useCallback((event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
const dataGridApi = dataGridContext?.getDataGridApi();
|
||||
|
||||
if (dataGridApi) {
|
||||
dataGridApi.selectCell({ idx: column.idx, rowIdx });
|
||||
}
|
||||
@@ -58,7 +59,7 @@ export const CellRenderer: React.FC<CellRendererProps<any>> = observer(function
|
||||
event.shiftKey,
|
||||
true
|
||||
);
|
||||
}, [column, rowIdx, selectionContext]);
|
||||
}, [column, rowIdx, dataGridContext, selectionContext]);
|
||||
|
||||
const handleMouseUp = useCallback((event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
if (!dataGridContext?.isGridInFocus()) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { createContext } from 'react';
|
||||
import type { DataGridHandle } from 'react-data-grid';
|
||||
|
||||
import type { IExecutor } from '@cloudbeaver/core-executor';
|
||||
import type { IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer';
|
||||
import type { IDatabaseDataModel, IDataTableActions } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
export interface IColumnResizeInfo {
|
||||
column: number;
|
||||
@@ -19,6 +19,7 @@ export interface IColumnResizeInfo {
|
||||
|
||||
export interface IDataGridContext {
|
||||
model: IDatabaseDataModel<any>;
|
||||
actions: IDataTableActions;
|
||||
resultIndex: number;
|
||||
columnResize: IExecutor<IColumnResizeInfo>;
|
||||
isGridInFocus: () => boolean;
|
||||
|
||||
+20
-10
@@ -73,6 +73,7 @@ export class DataGridContextMenuFilterService {
|
||||
context: IMenuContext<IDataGridCellMenuContext>,
|
||||
value: any | (() => any),
|
||||
icon: string,
|
||||
isHidden?: (context: IMenuContext<IDataGridCellMenuContext>) => boolean,
|
||||
): Array<IContextMenuItem<IDataGridCellMenuContext>> {
|
||||
const { model, resultIndex, column } = context.data;
|
||||
const data = model.source.getAction(resultIndex, ResultSetDataAction);
|
||||
@@ -88,6 +89,9 @@ export class DataGridContextMenuFilterService {
|
||||
isDisabled(context) {
|
||||
return context.data.model.isLoading();
|
||||
},
|
||||
isHidden(context) {
|
||||
return isHidden?.(context) ?? false;
|
||||
},
|
||||
titleGetter() {
|
||||
const val = typeof value === 'function' ? value() : value;
|
||||
const stringifyValue = format.toDisplayString(val);
|
||||
@@ -115,7 +119,7 @@ export class DataGridContextMenuFilterService {
|
||||
return context.data.model.isDisabled(context.data.resultIndex)
|
||||
|| context.data.model.source.results.length > 1;
|
||||
},
|
||||
order: 1,
|
||||
order: 2,
|
||||
title: 'data_grid_table_filter',
|
||||
icon: '/icons/filter.png',
|
||||
isPanel: true,
|
||||
@@ -132,7 +136,7 @@ export class DataGridContextMenuFilterService {
|
||||
const constraints = context.data.model.source.getAction(context.data.resultIndex, ResultSetConstraintAction);
|
||||
return constraints.orderConstraints.length === 0 && constraints.filterConstraints.length === 0;
|
||||
},
|
||||
order: 2,
|
||||
order: 3,
|
||||
title: 'data_grid_table_delete_filters_and_orders',
|
||||
icon: '/icons/erase.png',
|
||||
onClick: async context => {
|
||||
@@ -171,10 +175,19 @@ export class DataGridContextMenuFilterService {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (this.clipboardService.state === 'prompt') {
|
||||
return [{
|
||||
const valueGetter = () => this.clipboardService.clipboardValue || '';
|
||||
const items = this.getGeneralizedMenuItems(
|
||||
context,
|
||||
valueGetter,
|
||||
'/icons/filter_clipboard.png',
|
||||
() => this.clipboardService.state === 'prompt'
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'permission',
|
||||
isPresent: () => true,
|
||||
isHidden: () => this.clipboardService.state !== 'prompt',
|
||||
isDisabled(context) {
|
||||
return context.data.model.isLoading();
|
||||
},
|
||||
@@ -183,12 +196,9 @@ export class DataGridContextMenuFilterService {
|
||||
onClick: async () => {
|
||||
await this.clipboardService.read();
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
||||
const valueGetter = () => this.clipboardService.clipboardValue || '';
|
||||
const items = this.getGeneralizedMenuItems(context, valueGetter, '/icons/filter_clipboard.png');
|
||||
return items;
|
||||
},
|
||||
...items,
|
||||
];
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ export class DataGridContextMenuOrderService {
|
||||
isPresent(context) {
|
||||
return context.contextType === DataGridContextMenuService.cellContext;
|
||||
},
|
||||
order: 0,
|
||||
order: 1,
|
||||
title: 'data_grid_table_order',
|
||||
icon: 'order-arrow-unknown',
|
||||
isPanel: true,
|
||||
|
||||
+6
-3
@@ -9,10 +9,11 @@
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { ContextMenuService, IContextMenuItem, IMenuPanel } from '@cloudbeaver/core-dialogs';
|
||||
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
|
||||
import type { IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer';
|
||||
import type { IDatabaseDataModel, IDataTableActions } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
export interface IDataGridCellMenuContext {
|
||||
model: IDatabaseDataModel<any>;
|
||||
actions: IDataTableActions;
|
||||
resultIndex: number;
|
||||
row: number;
|
||||
column: number;
|
||||
@@ -36,6 +37,7 @@ export class DataGridContextMenuService {
|
||||
|
||||
constructMenuWithContext(
|
||||
model: IDatabaseDataModel<any>,
|
||||
actions: IDataTableActions,
|
||||
resultIndex: number,
|
||||
row: number,
|
||||
column: number
|
||||
@@ -43,17 +45,18 @@ export class DataGridContextMenuService {
|
||||
return this.contextMenuService.createContextMenu<IDataGridCellMenuContext>({
|
||||
menuId: this.getMenuToken(),
|
||||
contextType: DataGridContextMenuService.cellContext,
|
||||
data: { model, resultIndex, row, column },
|
||||
data: { model, actions, resultIndex, row, column },
|
||||
}, this.getMenuToken());
|
||||
}
|
||||
|
||||
openMenu(
|
||||
model: IDatabaseDataModel<any>,
|
||||
actions: IDataTableActions,
|
||||
resultIndex: number,
|
||||
row: number,
|
||||
column: number
|
||||
): void {
|
||||
this.onRootMenuOpen.execute({ model, resultIndex, row, column });
|
||||
this.onRootMenuOpen.execute({ model, actions, resultIndex, row, column });
|
||||
}
|
||||
|
||||
add(panelId: string, menuItem: IContextMenuItem<IDataGridCellMenuContext>): void {
|
||||
|
||||
@@ -7,29 +7,21 @@
|
||||
*/
|
||||
|
||||
import { ComplexLoader, createComplexLoader, Loader } from '@cloudbeaver/core-blocks';
|
||||
import type { IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
interface Props {
|
||||
tableModel: IDatabaseDataModel<any, any>;
|
||||
resultIndex: number;
|
||||
className?: string;
|
||||
}
|
||||
import type { IDataPresentationProps } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
const loader = createComplexLoader(async function loader() {
|
||||
const { DataGridTable } = await import('./DataGridTable');
|
||||
return { DataGridTable };
|
||||
});
|
||||
|
||||
export const DataGridLoader: React.FC<Props> = function DataGridLoader({
|
||||
tableModel, resultIndex, className,
|
||||
}) {
|
||||
export const DataGridLoader: React.FC<IDataPresentationProps> = function DataGridLoader(props) {
|
||||
return (
|
||||
<ComplexLoader
|
||||
loader={loader}
|
||||
placeholder={<Loader />}
|
||||
>
|
||||
{({ DataGridTable }) => (
|
||||
<DataGridTable model={tableModel} resultIndex={resultIndex} className={className} />
|
||||
<DataGridTable {...props} />
|
||||
)}
|
||||
</ComplexLoader>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ import styled from 'reshadow';
|
||||
|
||||
import { Executor } from '@cloudbeaver/core-executor';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
import { IDatabaseDataEditorActionsData, IDatabaseDataModel, IDatabaseResultSet, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer';
|
||||
import { IDatabaseDataEditorActionsData, IDatabaseDataModel, IDatabaseResultSet, IDataPresentationProps, IDataTableActions, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import { CellPosition, EditingContext } from '../Editing/EditingContext';
|
||||
import { useEditing } from '../Editing/useEditing';
|
||||
@@ -30,18 +30,12 @@ import { useGridDragging } from './useGridDragging';
|
||||
import { useGridSelectedCellsCopy } from './useGridSelectedCellsCopy';
|
||||
import { useTableData } from './useTableData';
|
||||
|
||||
interface Props {
|
||||
model: IDatabaseDataModel<any, IDatabaseResultSet>;
|
||||
resultIndex: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function isAtBottom(event: React.UIEvent<HTMLDivElement>): boolean {
|
||||
const target = event.target as HTMLDivElement;
|
||||
return target.clientHeight + target.scrollTop + 100 > target.scrollHeight;
|
||||
}
|
||||
|
||||
export const DataGridTable: React.FC<Props> = observer(function DataGridTable({ model, resultIndex, className }) {
|
||||
export const DataGridTable: React.FC<IDataPresentationProps<any, IDatabaseResultSet>> = observer(function DataGridTable({ model, actions, resultIndex, className }) {
|
||||
const gridContainerRef = useRef<HTMLDivElement>(null);
|
||||
const dataGridRef = useRef<DataGridHandle>(null);
|
||||
const editorRef = useRef<HTMLDivElement>(null);
|
||||
@@ -143,12 +137,13 @@ export const DataGridTable: React.FC<Props> = observer(function DataGridTable({
|
||||
|
||||
const gridContext = useMemo<IDataGridContext>(() => ({
|
||||
model,
|
||||
actions,
|
||||
columnResize,
|
||||
resultIndex,
|
||||
isGridInFocus: () => gridContainerRef.current === document.activeElement,
|
||||
getEditorPortal: () => editorRef.current,
|
||||
getDataGridApi: () => dataGridRef.current,
|
||||
}), [model, resultIndex, editorRef, dataGridRef]);
|
||||
}), [model, actions, resultIndex, editorRef, dataGridRef]);
|
||||
|
||||
return styled(styles)(
|
||||
<DataGridContext.Provider value={gridContext}>
|
||||
@@ -157,7 +152,6 @@ export const DataGridTable: React.FC<Props> = observer(function DataGridTable({
|
||||
<TableDataContext.Provider value={tableData}>
|
||||
<grid-container
|
||||
ref={gridContainerRef}
|
||||
as='div'
|
||||
className="cb-react-grid-container"
|
||||
tabIndex={-1}
|
||||
onKeyDown={onKeydownHandler}
|
||||
|
||||
@@ -72,6 +72,7 @@ export const CellFormatter: React.FC<FormatterProps> = observer(function CellFor
|
||||
{(isCellSelected || cellContext?.mouse.state.mouseEnter || menuVisible) && context && (
|
||||
<CellMenu
|
||||
model={context.model}
|
||||
actions={context.actions}
|
||||
resultIndex={context.resultIndex}
|
||||
row={rowIdx}
|
||||
column={Number(column.key)}
|
||||
|
||||
+12
-3
@@ -15,24 +15,28 @@ import { Icon } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { MenuTrigger } from '@cloudbeaver/core-dialogs';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
import type { IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer';
|
||||
import type { IDatabaseDataModel, IDataTableActions } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import { DataGridContextMenuService } from '../../DataGridContextMenu/DataGridContextMenuService';
|
||||
import { cellMenuStyles } from './cellMenuStyles';
|
||||
|
||||
interface Props {
|
||||
model: IDatabaseDataModel<any>;
|
||||
actions: IDataTableActions;
|
||||
resultIndex: number;
|
||||
row: number;
|
||||
column: number;
|
||||
onClick?: () => void;
|
||||
onStateSwitch?: (state: boolean) => void;
|
||||
}
|
||||
|
||||
export const CellMenu: React.FC<Props> = observer(function TreeNodeMenu({
|
||||
model,
|
||||
actions,
|
||||
resultIndex,
|
||||
row,
|
||||
column,
|
||||
onClick,
|
||||
onStateSwitch,
|
||||
}) {
|
||||
const dataGridContextMenuService = useService(DataGridContextMenuService);
|
||||
@@ -40,7 +44,7 @@ export const CellMenu: React.FC<Props> = observer(function TreeNodeMenu({
|
||||
|
||||
const { panel, hidden } = useMemo(
|
||||
() => {
|
||||
const panel = dataGridContextMenuService.constructMenuWithContext(model, resultIndex, row, column);
|
||||
const panel = dataGridContextMenuService.constructMenuWithContext(model, actions, resultIndex, row, column);
|
||||
const hidden = computed(() => !panel.menuItems.length
|
||||
|| panel.menuItems.every(item => item.isHidden));
|
||||
|
||||
@@ -49,6 +53,11 @@ export const CellMenu: React.FC<Props> = observer(function TreeNodeMenu({
|
||||
[column, row]
|
||||
);
|
||||
|
||||
function handleClick() {
|
||||
dataGridContextMenuService.openMenu(model, actions, resultIndex, row, column);
|
||||
onClick?.();
|
||||
}
|
||||
|
||||
if (hidden.get()) {
|
||||
return null;
|
||||
}
|
||||
@@ -59,7 +68,7 @@ export const CellMenu: React.FC<Props> = observer(function TreeNodeMenu({
|
||||
panel={panel}
|
||||
style={[cellMenuStyles]}
|
||||
modal
|
||||
onClick={() => dataGridContextMenuService.openMenu(model, resultIndex, row, column)}
|
||||
onClick={handleClick}
|
||||
onVisibleSwitch={onStateSwitch}
|
||||
>
|
||||
<Icon name="snack" viewBox="0 0 16 10" />
|
||||
|
||||
@@ -13,6 +13,7 @@ import { DataPresentationService } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import { DataGridContextMenuFilterService } from './DataGrid/DataGridContextMenu/DataGridContextMenuFilter/DataGridContextMenuFilterService';
|
||||
import { DataGridContextMenuOrderService } from './DataGrid/DataGridContextMenu/DataGridContextMenuOrderService';
|
||||
import { DataGridContextMenuService } from './DataGrid/DataGridContextMenu/DataGridContextMenuService';
|
||||
import { DataGridSettingsService } from './DataGridSettingsService';
|
||||
import { SpreadsheetGrid } from './SpreadsheetGrid';
|
||||
|
||||
@@ -23,6 +24,7 @@ export class SpreadsheetBootstrap extends Bootstrap {
|
||||
private dataGridSettingsService: DataGridSettingsService,
|
||||
private dataGridContextMenuSortingService: DataGridContextMenuOrderService,
|
||||
private dataGridContextMenuFilterService: DataGridContextMenuFilterService,
|
||||
private dataGridContextMenuService: DataGridContextMenuService,
|
||||
exceptionsCatcherService: ExceptionsCatcherService
|
||||
) {
|
||||
super();
|
||||
@@ -40,6 +42,25 @@ export class SpreadsheetBootstrap extends Bootstrap {
|
||||
});
|
||||
this.dataGridContextMenuSortingService.register();
|
||||
this.dataGridContextMenuFilterService.register();
|
||||
|
||||
this.dataGridContextMenuService.add(
|
||||
this.dataGridContextMenuService.getMenuToken(),
|
||||
{
|
||||
id: 'view_value_panel',
|
||||
isPresent(context) {
|
||||
return context.contextType === DataGridContextMenuService.cellContext;
|
||||
},
|
||||
isHidden(context) {
|
||||
return typeof context.data.actions.valuePresentationId === 'string';
|
||||
},
|
||||
order: 0.5,
|
||||
title: 'data_grid_table_open_value_panel',
|
||||
icon: '/icons/text_value_presentation.png',
|
||||
onClick(context) {
|
||||
context.data.actions.setValuePresentation('');
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
load(): void | Promise<void> { }
|
||||
|
||||
@@ -10,10 +10,6 @@ import type { IDataPresentationProps } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import { DataGridLoader } from './DataGrid/DataGridLoader';
|
||||
|
||||
export const SpreadsheetGrid: React.FC<IDataPresentationProps> = function SpreadsheetGrid({
|
||||
model,
|
||||
className,
|
||||
resultIndex,
|
||||
}) {
|
||||
return <DataGridLoader tableModel={model} resultIndex={resultIndex} className={className} />;
|
||||
export const SpreadsheetGrid: React.FC<IDataPresentationProps> = function SpreadsheetGrid(props) {
|
||||
return <DataGridLoader {...props} />;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export default [
|
||||
['data_grid_table_order', 'Sorting'],
|
||||
['data_grid_table_open_value_panel', 'Show in value panel'],
|
||||
['data_grid_table_filter', 'Filters'],
|
||||
['data_grid_table_filter_cell_value', 'Cell value'],
|
||||
['data_grid_table_filter_custom_value', 'Custom'],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export default [
|
||||
['data_grid_table_order', 'Сортировка'],
|
||||
['data_grid_table_open_value_panel', 'Показать в панели значений'],
|
||||
['data_grid_table_filter', 'Фильтры'],
|
||||
['data_grid_table_filter_cell_value', 'Значение ячейки'],
|
||||
['data_grid_table_filter_custom_value', 'Произвольный'],
|
||||
|
||||
@@ -12,6 +12,7 @@ import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { IDatabaseDataModel } from './DatabaseDataModel/IDatabaseDataModel';
|
||||
import type { IDatabaseDataResult } from './DatabaseDataModel/IDatabaseDataResult';
|
||||
import type { IDataTableActions } from './TableViewer/IDataTableActions';
|
||||
|
||||
export interface IDataPresentationProps<
|
||||
TOptions = any,
|
||||
@@ -19,6 +20,7 @@ export interface IDataPresentationProps<
|
||||
> {
|
||||
dataFormat: ResultDataFormat;
|
||||
model: IDatabaseDataModel<TOptions, TResult>;
|
||||
actions: IDataTableActions;
|
||||
resultIndex: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 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 type { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel';
|
||||
|
||||
export interface IDataTableActions {
|
||||
presentationId: string | undefined;
|
||||
valuePresentationId: string | null | undefined;
|
||||
dataModel: IDatabaseDataModel<any, any> | undefined;
|
||||
|
||||
setPresentation: (id: string) => void;
|
||||
setValuePresentation: (id: string | null) => void;
|
||||
}
|
||||
|
||||
export interface IDataTableActionsPrivate extends IDataTableActions {
|
||||
resultIndex: number;
|
||||
dataFormat: ResultDataFormat;
|
||||
onPresentationChange: (id: string) => void;
|
||||
onValuePresentationChange: (id: string | null) => void;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { IconButton, SubmittingForm, ToolsPanel } from '@cloudbeaver/core-blocks';
|
||||
import { IconOrImage, SubmittingForm, ToolsPanel } from '@cloudbeaver/core-blocks';
|
||||
import { composes, useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel';
|
||||
@@ -40,7 +40,14 @@ const tableFooterStyles = composes(
|
||||
reload {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
|
||||
& IconOrImage {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
IconButton {
|
||||
position: relative;
|
||||
@@ -48,7 +55,6 @@ const tableFooterStyles = composes(
|
||||
width: 24px;
|
||||
display: block;
|
||||
}
|
||||
reload,
|
||||
count,
|
||||
TableFooterMenu {
|
||||
margin-left: 16px;
|
||||
@@ -86,16 +92,12 @@ export const TableFooter = observer(function TableFooter({
|
||||
[model]
|
||||
);
|
||||
|
||||
const disabled = model.isLoading() || model.isDisabled(resultIndex);
|
||||
|
||||
return styled(useStyles(tableFooterStyles))(
|
||||
<ToolsPanel>
|
||||
<reload>
|
||||
<IconButton
|
||||
type="button"
|
||||
name='reload'
|
||||
viewBox="0 0 16 16"
|
||||
disabled={model.isLoading() || model.isDisabled(resultIndex)}
|
||||
onClick={() => model.refresh()}
|
||||
/>
|
||||
<reload aria-disabled={disabled} onClick={() => model.refresh()}>
|
||||
<IconOrImage icon='reload' viewBox="0 0 16 16" />
|
||||
</reload>
|
||||
<count>
|
||||
<SubmittingForm onSubmit={handleChange}>
|
||||
@@ -103,7 +105,7 @@ export const TableFooter = observer(function TableFooter({
|
||||
ref={ref}
|
||||
type="number"
|
||||
value={model.countGain}
|
||||
disabled={model.isLoading() || model.isDisabled(resultIndex)}
|
||||
disabled={disabled}
|
||||
onBlur={handleChange}
|
||||
{...use({ mod: 'surface' })}
|
||||
/>
|
||||
|
||||
@@ -15,10 +15,12 @@ import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel';
|
||||
import type { IDataPresentationOptions } from '../DataPresentationService';
|
||||
import type { IDataTableActions } from './IDataTableActions';
|
||||
import { TableStatistics } from './TableStatistics';
|
||||
|
||||
type TableGridProps = PropsWithChildren<{
|
||||
model: IDatabaseDataModel<any>;
|
||||
actions: IDataTableActions;
|
||||
dataFormat: ResultDataFormat;
|
||||
presentation: IDataPresentationOptions;
|
||||
resultIndex: number;
|
||||
@@ -33,6 +35,7 @@ const styles = css`
|
||||
|
||||
export const TableGrid = observer(function TableGrid({
|
||||
model,
|
||||
actions,
|
||||
dataFormat,
|
||||
presentation,
|
||||
resultIndex,
|
||||
@@ -58,6 +61,6 @@ export const TableGrid = observer(function TableGrid({
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<Presentation dataFormat={dataFormat} model={model} resultIndex={resultIndex} />
|
||||
<Presentation dataFormat={dataFormat} model={model} actions={actions} resultIndex={resultIndex} />
|
||||
);
|
||||
});
|
||||
|
||||
@@ -16,9 +16,11 @@ import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel';
|
||||
import type { IDataPresentationOptions } from '../DataPresentationService';
|
||||
import type { IDataTableActions } from './IDataTableActions';
|
||||
|
||||
type TableGridProps = PropsWithChildren<{
|
||||
model: IDatabaseDataModel<any, any>;
|
||||
actions: IDataTableActions;
|
||||
dataFormat: ResultDataFormat;
|
||||
presentation: IDataPresentationOptions | null;
|
||||
resultIndex: number;
|
||||
@@ -33,6 +35,7 @@ const styles = css`
|
||||
|
||||
export const TableToolsPanel = observer(function TableToolsPanel({
|
||||
model,
|
||||
actions,
|
||||
dataFormat,
|
||||
presentation,
|
||||
resultIndex,
|
||||
@@ -57,6 +60,6 @@ export const TableToolsPanel = observer(function TableToolsPanel({
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<Presentation dataFormat={dataFormat} model={model} resultIndex={resultIndex} />
|
||||
<Presentation dataFormat={dataFormat} model={model} actions={actions} resultIndex={resultIndex} />
|
||||
);
|
||||
});
|
||||
|
||||
@@ -6,16 +6,18 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observable } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { Loader, Pane, ResizerControls, Split, splitStyles, TextPlaceholder } from '@cloudbeaver/core-blocks';
|
||||
import { Loader, Pane, ResizerControls, Split, splitStyles, TextPlaceholder, useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
import { composes, useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { DataPresentationService, DataPresentationType } from '../DataPresentationService';
|
||||
import type { IDataTableActionsPrivate } from './IDataTableActions';
|
||||
import { TableError } from './TableError';
|
||||
import { TableFooter } from './TableFooter/TableFooter';
|
||||
import { TableGrid } from './TableGrid';
|
||||
@@ -117,30 +119,66 @@ export const TableViewer: React.FC<Props> = observer(function TableViewer({
|
||||
const loading = dataModel?.isLoading() ?? true;
|
||||
const dataFormat = result?.dataFormat || ResultDataFormat.Resultset;
|
||||
|
||||
const handlePresentationChange = useCallback((id: string) => {
|
||||
const presentation = dataPresentationService.get(id);
|
||||
if (presentation) {
|
||||
if (
|
||||
presentation.dataFormat !== undefined
|
||||
&& presentation.dataFormat !== dataModel?.source.dataFormat
|
||||
) {
|
||||
dataModel?.setDataFormat(presentation.dataFormat)
|
||||
.reload();
|
||||
}
|
||||
onPresentationChange(id);
|
||||
}
|
||||
}, [onPresentationChange, dataModel]);
|
||||
const dataTableActions = useObjectRef<IDataTableActionsPrivate>({
|
||||
presentationId,
|
||||
valuePresentationId,
|
||||
dataModel,
|
||||
resultIndex,
|
||||
dataFormat,
|
||||
onPresentationChange,
|
||||
onValuePresentationChange,
|
||||
|
||||
function handleValuePresentationChange(id: string) {
|
||||
if (id === valuePresentationId) {
|
||||
onValuePresentationChange(null);
|
||||
return;
|
||||
}
|
||||
const presentation = dataPresentationService.get(id);
|
||||
if (presentation) {
|
||||
onValuePresentationChange(id);
|
||||
}
|
||||
}
|
||||
setPresentation(id: string) {
|
||||
const presentation = dataPresentationService.get(id);
|
||||
|
||||
if (presentation) {
|
||||
if (
|
||||
presentation.dataFormat !== undefined
|
||||
&& presentation.dataFormat !== this.dataModel?.source.dataFormat
|
||||
) {
|
||||
this.dataModel?.setDataFormat(presentation.dataFormat).reload();
|
||||
}
|
||||
this.onPresentationChange(id);
|
||||
}
|
||||
},
|
||||
|
||||
setValuePresentation(id: string | null) {
|
||||
if (id === this.valuePresentationId || id === null) {
|
||||
this.onValuePresentationChange(null);
|
||||
return;
|
||||
}
|
||||
|
||||
let presentation = dataPresentationService.get(id);
|
||||
|
||||
if (!presentation && this.dataModel) {
|
||||
presentation = dataPresentationService.getSupported(
|
||||
DataPresentationType.toolsPanel,
|
||||
this.dataFormat,
|
||||
undefined,
|
||||
this.dataModel,
|
||||
this.resultIndex
|
||||
) ?? undefined;
|
||||
}
|
||||
|
||||
if (presentation) {
|
||||
this.onValuePresentationChange(presentation.id);
|
||||
}
|
||||
},
|
||||
}, {
|
||||
presentationId,
|
||||
valuePresentationId,
|
||||
dataModel,
|
||||
resultIndex,
|
||||
dataFormat,
|
||||
onPresentationChange,
|
||||
onValuePresentationChange,
|
||||
}, {
|
||||
presentationId: observable,
|
||||
valuePresentationId: observable,
|
||||
dataFormat: observable,
|
||||
resultIndex: observable,
|
||||
dataModel: observable.ref,
|
||||
}, ['setPresentation', 'setValuePresentation']);
|
||||
|
||||
useEffect(() => {
|
||||
if (!presentationId || !dataModel) {
|
||||
@@ -189,9 +227,9 @@ export const TableViewer: React.FC<Props> = observer(function TableViewer({
|
||||
&& resultExist;
|
||||
|
||||
return styled(styles)(
|
||||
<table-viewer as="div" className={className}>
|
||||
<table-viewer className={className}>
|
||||
<TableHeader model={dataModel} resultIndex={resultIndex} />
|
||||
<table-content as='div'>
|
||||
<table-content>
|
||||
<TablePresentationBar
|
||||
type={DataPresentationType.main}
|
||||
presentationId={presentation.id}
|
||||
@@ -199,7 +237,7 @@ export const TableViewer: React.FC<Props> = observer(function TableViewer({
|
||||
supportedDataFormat={dataModel.supportedDataFormats}
|
||||
model={dataModel}
|
||||
resultIndex={resultIndex}
|
||||
onPresentationChange={handlePresentationChange}
|
||||
onPresentationChange={dataTableActions.setPresentation}
|
||||
/>
|
||||
<table-data>
|
||||
<Split sticky={30} mode={valuePanelDisplayed ? undefined : 'minimize'} keepRatio>
|
||||
@@ -207,6 +245,7 @@ export const TableViewer: React.FC<Props> = observer(function TableViewer({
|
||||
<pane-content>
|
||||
<TableGrid
|
||||
model={dataModel}
|
||||
actions={dataTableActions}
|
||||
dataFormat={dataFormat}
|
||||
presentation={presentation}
|
||||
resultIndex={resultIndex}
|
||||
@@ -219,6 +258,7 @@ export const TableViewer: React.FC<Props> = observer(function TableViewer({
|
||||
{resultExist && (
|
||||
<TableToolsPanel
|
||||
model={dataModel}
|
||||
actions={dataTableActions}
|
||||
dataFormat={dataFormat}
|
||||
presentation={valuePresentation}
|
||||
resultIndex={resultIndex}
|
||||
@@ -242,7 +282,7 @@ export const TableViewer: React.FC<Props> = observer(function TableViewer({
|
||||
supportedDataFormat={[dataFormat]}
|
||||
model={dataModel}
|
||||
resultIndex={resultIndex}
|
||||
onPresentationChange={handleValuePresentationChange}
|
||||
onPresentationChange={dataTableActions.setValuePresentation}
|
||||
/>
|
||||
</table-content>
|
||||
<TableFooter model={dataModel} resultIndex={resultIndex} />
|
||||
|
||||
@@ -37,6 +37,7 @@ export * from './DatabaseDataModel/Order';
|
||||
export * from './TableViewer/TableViewerStorageService';
|
||||
export * from './TableViewer/ValuePanel/DataValuePanelService';
|
||||
|
||||
export * from './TableViewer/IDataTableActions';
|
||||
export * from './TableViewer/TableViewer';
|
||||
export * from './TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService';
|
||||
export * from './TableViewer/DataModelWrapper';
|
||||
|
||||
@@ -9916,6 +9916,11 @@ pascalcase@^0.1.1:
|
||||
resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
|
||||
integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
|
||||
|
||||
path-browserify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
|
||||
integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
|
||||
|
||||
path-dirname@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
|
||||
|
||||
Reference in New Issue
Block a user