mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c30ed49746 | ||
|
|
d0feeda0df | ||
|
|
5a2c657f4c | ||
|
|
cd1a4fe77b |
+2
-2
@@ -179,13 +179,13 @@ export class ConnectionSchemaManagerService {
|
||||
try {
|
||||
await this.connectionInfo.load(connectionId);
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Can\'t load connection info', '', true);
|
||||
this.notificationService.logException(exception, 'Can\'t load connection info', true);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.dbDriverResource.loadAll();
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Can\'t load database drivers', '', true);
|
||||
this.notificationService.logException(exception, 'Can\'t load database drivers', true);
|
||||
}
|
||||
|
||||
if (this.activeItem?.changeCatalogId || this.activeItem?.changeSchemaId) {
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ export class SessionExpiredDialogService extends Bootstrap {
|
||||
this.notificationService.customNotification(() => ActionSnackbar, {
|
||||
actionText: 'app_root_session_expired_reload',
|
||||
onAction: () => location.reload(),
|
||||
}, { title: 'app_root_session_expired_title', persistent: true, type: ENotificationType.Info });
|
||||
}, { title: 'app_root_session_expired_title', persistent: true, type: ENotificationType.Error });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" fill="none" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" r="20" fill="#DF303E" fill-opacity=".2"/>
|
||||
<path fill="#DF303E" fill-rule="evenodd" d="M20 33c7.18 0 13-5.82 13-13S27.18 7 20 7 7 12.82 7 20s5.82 13 13 13zm-.986-10.668l-.205-9.434h2.373l-.196 9.434h-1.972zm2.49 3.428c0 .869-.596 1.455-1.494 1.455-.908 0-1.514-.586-1.514-1.455 0-.88.606-1.465 1.514-1.465.898 0 1.494.586 1.494 1.465z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 492 B |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" fill="none" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" r="20" fill="#47A0DD" fill-opacity=".2"/>
|
||||
<path fill="#47A0DD" fill-rule="evenodd" d="M28.332 23.333h-.42c1.157 0 2.088.933 2.088 2.083v.418c0 .463-.373.833-.834.833H10.834c-.461 0-.834-.373-.834-.833v-.418c0-1.15.934-2.083 2.087-2.083h-.42c.459 0 .833-.373.833-.834v-5C12.5 13.357 15.858 10 20 10c4.143 0 7.5 3.358 7.5 7.5v5c0 .463.373.833.832.833zM17.084 27.5h5.834c0 1.61-1.306 2.917-2.917 2.917-1.61 0-2.917-1.306-2.917-2.917z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 589 B |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" fill="none" viewBox="0 0 40 40">
|
||||
<circle cx="20" cy="20" r="20" fill="#52C41A" fill-opacity=".2"/>
|
||||
<path fill="#52C41A" fill-rule="evenodd" d="M20 33c7.18 0 13-5.82 13-13S27.18 7 20 7 7 12.82 7 20s5.82 13 13 13zm-.379-7.175l6.34-9.4-1.659-1.119-5.132 7.612-4.175-3.304-1.241 1.568 5.867 4.643z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 395 B |
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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 { SVGAttributes } from 'react';
|
||||
|
||||
export function CheckCircle(props: SVGAttributes<any>) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="64 64 896 896"
|
||||
data-icon="check-circle"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
{...props}
|
||||
>
|
||||
<path d="M699 353h-46.9c-10.2 0-19.9 4.9-25.9 13.3L469 584.3l-71.2-98.8c-6-8.3-15.6-13.3-25.9-13.3H325c-6.5 0-10.3 7.4-6.5 12.7l124.6 172.8a31.8 31.8 0 0 0 51.7 0l210.6-292c3.9-5.3.1-12.7-6.4-12.7z" />
|
||||
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 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 { SVGAttributes } from 'react';
|
||||
|
||||
export function Frown(props: SVGAttributes<any>) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="64 64 896 896"
|
||||
data-icon="frown"
|
||||
width="1em"
|
||||
height="1em"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
{...props}
|
||||
>
|
||||
<path d="M288 421a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm352 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 0 1 248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 0 1 249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 0 1 775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 0 1 775 775zM512 533c-85.5 0-155.6 67.3-160 151.6a8 8 0 0 0 8 8.4h48.1c4.2 0 7.8-3.2 8.1-7.4C420 636.1 461.5 597 512 597s92.1 39.1 95.8 88.6c.3 4.2 3.9 7.4 8.1 7.4H664a8 8 0 0 0 8-8.4C667.6 600.3 597.5 533 512 533z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
export * from './ArrowSM';
|
||||
export * from './Logo';
|
||||
export * from './CheckCircle';
|
||||
export * from './Frown';
|
||||
export * from './Cross';
|
||||
export * from './Snack';
|
||||
export * from './Configuration';
|
||||
|
||||
@@ -29,7 +29,6 @@ export interface LoaderProps {
|
||||
/** smallest spinner icon and hide loading message */
|
||||
small?: boolean;
|
||||
className?: string;
|
||||
fullSize?: boolean;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
|
||||
@@ -44,7 +43,6 @@ export function Loader({
|
||||
hideMessage,
|
||||
secondary,
|
||||
small,
|
||||
fullSize,
|
||||
className,
|
||||
loading = true,
|
||||
onCancel,
|
||||
@@ -70,7 +68,7 @@ export function Loader({
|
||||
}
|
||||
|
||||
return styled(style)(
|
||||
<loader as="div" className={className} {...use({ small, fullSize })}>
|
||||
<loader as="div" className={className} {...use({ small })}>
|
||||
<icon as="div"><img src={spinnerURL} /></icon>
|
||||
{!hideMessage && <message as="div"><Translate token='ui_processing_loading' /></message>}
|
||||
{onCancel && (
|
||||
|
||||
@@ -50,13 +50,6 @@ export const loaderStyles = css`
|
||||
}
|
||||
}
|
||||
|
||||
loader[|fullSize] {
|
||||
& icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotation {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import { Button, IconButton } from '@cloudbeaver/core-blocks';
|
||||
import { NotificationComponentProps } from '@cloudbeaver/core-events';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { NotificationMark } from '@cloudbeaver/core-notifications';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { NotificationMark } from './NotificationMark';
|
||||
import { SNACKBAR_COMMON_STYLES } from './SnackbarCommonStyles';
|
||||
|
||||
type Props = NotificationComponentProps<{
|
||||
@@ -28,8 +28,6 @@ export const ActionSnackbar: React.FC<Props> = function ActionSnackbar({
|
||||
const styles = useStyles(SNACKBAR_COMMON_STYLES);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const translate = useTranslate();
|
||||
const timeStringFromTimestamp = notification.timestamp ? new Date(notification.timestamp).toLocaleTimeString() : '';
|
||||
const translatedTitle = translate(notification.title);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
@@ -37,25 +35,24 @@ export const ActionSnackbar: React.FC<Props> = function ActionSnackbar({
|
||||
|
||||
return styled(styles)(
|
||||
<notification as="div" {...use({ mounted })}>
|
||||
<NotificationMark type={notification.type} />
|
||||
<notification-header as="div">
|
||||
<NotificationMark type={notification.type} />
|
||||
<message as="div">{translate(notification.title)}</message>
|
||||
{!notification.persistent && (
|
||||
<IconButton
|
||||
name="cross"
|
||||
viewBox="0 0 16 16"
|
||||
onClick={notification.close}
|
||||
/>
|
||||
)}
|
||||
</notification-header>
|
||||
<notification-body as="div">
|
||||
<body-text-block as='div'>
|
||||
<text-block-title title={translatedTitle} as='h2'>{translatedTitle}</text-block-title>
|
||||
{notification.message && <message as="div">{translate(notification.message)}</message>}
|
||||
</body-text-block>
|
||||
<notification-footer as='div'>
|
||||
<footer-time as='span'>{timeStringFromTimestamp}</footer-time>
|
||||
<actions as="div">
|
||||
<Button type="button" mod={['outlined']} onClick={onAction}>
|
||||
{translate(actionText)}
|
||||
</Button>
|
||||
</actions>
|
||||
</notification-footer>
|
||||
<actions as="div">
|
||||
<Button type="button" mod={['outlined']} onClick={onAction}>
|
||||
{translate(actionText)}
|
||||
</Button>
|
||||
</actions>
|
||||
</notification-body>
|
||||
{!notification.persistent && (
|
||||
<IconButton name="cross" viewBox="0 0 16 16" onClick={notification.close} />
|
||||
)}
|
||||
</notification>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,12 +21,13 @@ export const SNACKBAR_COMMON_STYLES = composes(
|
||||
composes: theme-elevation-z5 from global;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
width: 500px;
|
||||
margin-bottom: 16px;
|
||||
margin-left: 16px;
|
||||
padding: 16px 16px;
|
||||
padding: 18px 24px;
|
||||
line-height: 1.5;
|
||||
opacity: 0;
|
||||
border-radius: 4px;
|
||||
@@ -42,70 +43,61 @@ export const SNACKBAR_COMMON_STYLES = composes(
|
||||
}
|
||||
}
|
||||
|
||||
notification-body {
|
||||
notification-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 0 0;
|
||||
& body-text-block {
|
||||
margin-top: 8px;
|
||||
padding-right: 24px;
|
||||
& message {
|
||||
font-size: 16px;
|
||||
opacity: 0.8;
|
||||
overflow: auto;
|
||||
max-height: 200px;
|
||||
margin-bottom: 8px;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
text-block-title {
|
||||
composes: theme-typography--headline6 from global;
|
||||
max-width: 392px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
line-height: 1.55rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
notification-footer {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
footer-time {
|
||||
composes: theme-typography--caption from global;
|
||||
opacity: 0.7;
|
||||
message {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
max-height: 200px;
|
||||
padding-right: 24px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
NotificationMark {
|
||||
padding-right: 12px;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: 24px;
|
||||
font-style: normal;
|
||||
line-height: 0;
|
||||
text-align: center;
|
||||
text-transform: none;
|
||||
vertical-align: -0.125em;
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
&[type='Info'] :global(svg) {
|
||||
fill: #52c41a !important;
|
||||
}
|
||||
& :global(svg) {
|
||||
fill: #ed3b26 !important;
|
||||
}
|
||||
}
|
||||
|
||||
NotificationMark + message {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
actions {
|
||||
display: flex;
|
||||
|
||||
&:not(:empty) {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
& Button {
|
||||
margin-left: 16px;
|
||||
|
||||
&:first-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
IconButton {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
@@ -38,7 +38,5 @@ export * from './TextPlaceholder';
|
||||
export * from './useClipboard';
|
||||
export * from './useFocus';
|
||||
export * from './BlocksLocaleService';
|
||||
export * from './Snackbars/NotificationMark';
|
||||
export * from './Snackbars/Snackbar';
|
||||
export * from './Snackbars/ActionSnackbar';
|
||||
export * from './Snackbars/SnackbarCommonStyles';
|
||||
|
||||
+4
-1
@@ -11,7 +11,8 @@ import { useCallback } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import {
|
||||
Table, TableHeader, TableColumnHeader, TableBody, TableItem, TableColumnValue, TableItemSelect, TextPlaceholder
|
||||
Table,
|
||||
TableHeader, TableColumnHeader, TableBody, TableItem, TableColumnValue, TableItemSelect, TextPlaceholder, Loader
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useController } from '@cloudbeaver/core-di';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
@@ -28,6 +29,7 @@ const styles = composes(
|
||||
`,
|
||||
css`
|
||||
box {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
}
|
||||
TableColumnHeader {
|
||||
@@ -97,6 +99,7 @@ export const ConnectionAccess = observer(function ConnectionAccess({
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Loader loading={controller.isLoading} overlay />
|
||||
</box>
|
||||
);
|
||||
});
|
||||
|
||||
+9
-5
@@ -10,7 +10,7 @@ import { computed, observable } from 'mobx';
|
||||
|
||||
import { UsersResource, RolesResource } from '@cloudbeaver/core-authentication';
|
||||
import { injectable, IInitializableController } from '@cloudbeaver/core-di';
|
||||
import { AdminSubjectType } from '@cloudbeaver/core-sdk';
|
||||
import { AdminRoleInfo, AdminSubjectType, AdminUserInfo } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { IConnectionFormModel } from '../IConnectionFormModel';
|
||||
|
||||
@@ -19,14 +19,18 @@ export class Controller
|
||||
implements IInitializableController {
|
||||
@observable selectedSubjects: Map<string, boolean> = new Map();
|
||||
|
||||
@computed get users() {
|
||||
@computed get users(): AdminUserInfo[] {
|
||||
return Array.from(this.usersResource.data.values());
|
||||
}
|
||||
|
||||
@computed get roles() {
|
||||
@computed get roles(): AdminRoleInfo[] {
|
||||
return Array.from(this.rolesResource.data.values());
|
||||
}
|
||||
|
||||
get isLoading(): boolean {
|
||||
return this.usersResource.isLoading() || this.rolesResource.isLoading();
|
||||
}
|
||||
|
||||
private model!: IConnectionFormModel;
|
||||
|
||||
constructor(
|
||||
@@ -34,12 +38,12 @@ implements IInitializableController {
|
||||
private rolesResource: RolesResource
|
||||
) { }
|
||||
|
||||
init(model: IConnectionFormModel) {
|
||||
init(model: IConnectionFormModel): void {
|
||||
this.model = model;
|
||||
this.loadSubjects();
|
||||
}
|
||||
|
||||
onSelect = (subjectId: string, state: boolean) => {
|
||||
onSelect = (subjectId: string, state: boolean): void => {
|
||||
if (!state) {
|
||||
const index = this.model.grantedSubjects!.findIndex(subject => subject.subjectId === subjectId);
|
||||
if (index > -1) {
|
||||
|
||||
+3
-3
@@ -79,12 +79,12 @@ implements IInitializableController {
|
||||
const connection = await this.connectionsResource.update(this.model.connection.id, this.getConnectionConfig());
|
||||
await this.saveSubjectPermissions(connection.id);
|
||||
|
||||
this.notificationService.logSuccess({ title: `Connection ${connection.name} updated` });
|
||||
this.notificationService.logInfo({ title: `Connection ${connection.name} updated` });
|
||||
} else {
|
||||
const connection = await this.connectionsResource.create(this.getConnectionConfig());
|
||||
await this.saveSubjectPermissions(connection.id);
|
||||
this.close();
|
||||
this.notificationService.logSuccess({ title: `Connection ${connection.name} created` });
|
||||
this.notificationService.logInfo({ title: `Connection ${connection.name} created` });
|
||||
}
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'connections_connection_create_fail');
|
||||
@@ -97,7 +97,7 @@ implements IInitializableController {
|
||||
this.isSaving = true;
|
||||
try {
|
||||
await this.connectionsResource.test(this.getConnectionConfig());
|
||||
this.notificationService.logSuccess({ title: 'Connection is established' });
|
||||
this.notificationService.logInfo({ title: 'Connection is established' });
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'connections_connection_test_fail');
|
||||
} finally {
|
||||
|
||||
+6
-2
@@ -60,7 +60,7 @@ implements IInitializableController {
|
||||
): Promise<void> => this.loadDriver(driverId, prevValue);
|
||||
|
||||
onFormChange = (value?: unknown, name?: string): void => {
|
||||
this.updateName();
|
||||
this.updateName(name);
|
||||
this.resetPassword(name || '');
|
||||
};
|
||||
|
||||
@@ -124,7 +124,11 @@ implements IInitializableController {
|
||||
this.updateName();
|
||||
}
|
||||
|
||||
private updateName() {
|
||||
private updateName(name?: string) {
|
||||
if (name === 'name') {
|
||||
return;
|
||||
}
|
||||
|
||||
const databaseNames = ['New', ...this.drivers.map(driver => driver.name!)]
|
||||
.filter(Boolean);
|
||||
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ implements IInitializableController, IDestructibleController {
|
||||
// we create a copy to protect the current value from mutation
|
||||
this.connection = JSON.parse(JSON.stringify(await this.connectionsResource.load(this.connectionId)));
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, "Can't load ConnectionInfo", `Can't load ConnectionInfo ${this.connectionId}`);
|
||||
this.notificationService.logException(exception, `Can't load ConnectionInfo ${this.connectionId}`);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ export class ConnectionsManagerService {
|
||||
await this.connectionInfo.close(id);
|
||||
await this.afterConnectionClose(id);
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, "Can't close connection", `Can't close connection: ${connection.name}`);
|
||||
this.notificationService.logException(exception, `Can't close connection: ${connection.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ export class DBAuthDialogController implements IInitializableController, IDestru
|
||||
try {
|
||||
this.dbDriverResource.loadAll();
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Can\'t load database drivers', '', true);
|
||||
this.notificationService.logException(exception, 'Can\'t load database drivers', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export default [
|
||||
['connections_connection_port', 'Порт'],
|
||||
['connections_connection_template', 'Шаблон'],
|
||||
['connections_connection_test', 'Проверить подключение'],
|
||||
['connections_connection_test_fail', 'Не удалось выполнить подключение'],
|
||||
['connections_connection_test_fail', 'Не удалось выполнить тестовое подключение'],
|
||||
['connections_connection_create_fail', 'Не удалось создать подключение'],
|
||||
['connections_connection_save_fail', 'Не удалось сохранить подключение'],
|
||||
['connections_driver_search_placeholder', 'Введите название драйвера...'],
|
||||
|
||||
@@ -34,7 +34,6 @@ export interface INotification<TProps extends INotificationExtraProps<any> = INo
|
||||
type: ENotificationType;
|
||||
title: string;
|
||||
message?: string;
|
||||
timestamp: number;
|
||||
details?: string | Error;
|
||||
persistent?: boolean;
|
||||
isSilent: boolean;
|
||||
@@ -51,6 +50,5 @@ export interface INotificationOptions<TProps extends INotificationExtraProps<any
|
||||
isSilent?: boolean;
|
||||
persistent?: boolean;
|
||||
extraProps?: TProps;
|
||||
timestamp?: number;
|
||||
customComponent?: () => NotificationComponent<TProps>;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ export class NotificationService {
|
||||
customComponent: options.customComponent,
|
||||
extraProps: options.extraProps || {} as TProps,
|
||||
persistent: options.persistent,
|
||||
timestamp: options.timestamp || Date.now(),
|
||||
type,
|
||||
close: this.close.bind(this, id),
|
||||
showDetails: this.showDetails.bind(this, id),
|
||||
@@ -88,21 +87,16 @@ export class NotificationService {
|
||||
this.notify(notification, ENotificationType.Info);
|
||||
}
|
||||
|
||||
logSuccess<T>(notification: INotificationOptions<T>): void {
|
||||
this.notify(notification, ENotificationType.Success);
|
||||
}
|
||||
|
||||
logError<T>(notification: INotificationOptions<T>): void {
|
||||
this.notify(notification, ENotificationType.Error);
|
||||
}
|
||||
|
||||
logException(exception: Error | GQLError, title?: string, message?: string, silent?: boolean): void {
|
||||
logException(exception: Error | GQLError, message?: string, silent?: boolean): void {
|
||||
const exceptionMessage = hasDetails(exception) ? exception.errorText : exception.message || exception.name;
|
||||
|
||||
if (!silent) {
|
||||
this.logError({
|
||||
title: title || exception.name,
|
||||
message: message || exceptionMessage,
|
||||
title: message || exceptionMessage,
|
||||
details: hasDetails(exception) ? exception : undefined,
|
||||
isSilent: silent,
|
||||
});
|
||||
|
||||
@@ -142,7 +142,7 @@ export class LocalizationService extends Bootstrap {
|
||||
}
|
||||
this.localeMap.set(localeKey, locale);
|
||||
} catch (error) {
|
||||
this.notificationService.logException(error, 'Locale is not found', '', true);
|
||||
this.notificationService.logException(error, 'Locale is not found', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import { Snackbar } from '@cloudbeaver/core-blocks';
|
||||
import { useController } from '@cloudbeaver/core-di';
|
||||
import { INotification } from '@cloudbeaver/core-events';
|
||||
|
||||
import { NotificationItemController } from './NotificationItemController';
|
||||
import { Snackbar } from './Snackbar/Snackbar';
|
||||
|
||||
interface Props {
|
||||
notification: INotification<any>;
|
||||
@@ -28,10 +28,8 @@ export const NotificationsItem = observer(function Notification({ notification }
|
||||
|
||||
return (
|
||||
<Snackbar
|
||||
title={notification.title}
|
||||
message={notification.message}
|
||||
text={notification.title}
|
||||
type={notification.type}
|
||||
time={notification.timestamp}
|
||||
disableShowDetails={controller.isDetailsDialogOpen}
|
||||
closeAfter={controller.closeAfter}
|
||||
onClose={controller.handleClose}
|
||||
|
||||
+5
-5
@@ -8,10 +8,9 @@
|
||||
|
||||
import { HTMLProps } from 'react';
|
||||
|
||||
import { CheckCircle, Frown } from '@cloudbeaver/core-blocks';
|
||||
import { ENotificationType } from '@cloudbeaver/core-events';
|
||||
|
||||
import { IconOrImage } from '../IconOrImage';
|
||||
|
||||
type IconProps = HTMLProps<HTMLDivElement> & {
|
||||
type: ENotificationType;
|
||||
};
|
||||
@@ -19,9 +18,10 @@ type IconProps = HTMLProps<HTMLDivElement> & {
|
||||
export function NotificationMark({ type, ...props }: IconProps) {
|
||||
return (
|
||||
<div {...props}>
|
||||
{type === ENotificationType.Info && <IconOrImage icon='/icons/info_icon.svg' />}
|
||||
{type === ENotificationType.Error && <IconOrImage icon='/icons/error_icon.svg' />}
|
||||
{type === ENotificationType.Success && <IconOrImage icon='/icons/success_icon.svg' />}
|
||||
{/* todo change to info icon */}
|
||||
{type === ENotificationType.Info && <CheckCircle />}
|
||||
{type === ENotificationType.Error && <Frown />}
|
||||
{type === ENotificationType.Success && <CheckCircle />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+22
-32
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import { Button, IconButton, SNACKBAR_COMMON_STYLES } from '@cloudbeaver/core-blocks';
|
||||
@@ -18,31 +18,25 @@ import { NotificationMark } from './NotificationMark';
|
||||
|
||||
interface SnackbarProps {
|
||||
type?: ENotificationType;
|
||||
message?: string;
|
||||
title: string;
|
||||
text: string;
|
||||
closeAfter?: number;
|
||||
disableShowDetails?: boolean;
|
||||
time?: number;
|
||||
onClose?: () => void;
|
||||
onShowDetails?: () => void;
|
||||
}
|
||||
|
||||
export function Snackbar({
|
||||
type,
|
||||
message,
|
||||
title,
|
||||
text,
|
||||
closeAfter,
|
||||
disableShowDetails,
|
||||
onClose,
|
||||
onShowDetails,
|
||||
time,
|
||||
}: SnackbarProps) {
|
||||
const styles = useStyles(SNACKBAR_COMMON_STYLES);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [closing, setClosing] = useState(false);
|
||||
const translate = useTranslate();
|
||||
const timeStringFromTimestamp = time ? new Date(time).toLocaleTimeString() : '';
|
||||
const translatedTitle = translate(title);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
@@ -70,31 +64,27 @@ export function Snackbar({
|
||||
|
||||
return styled(styles)(
|
||||
<notification as="div" {...use({ mounted, closing })}>
|
||||
{type && <NotificationMark type={type} />}
|
||||
<notification-header as="div">
|
||||
{type && <NotificationMark type={type} />}
|
||||
<message as="div">{translate(text)}</message>
|
||||
{!closeAfter && onClose && (
|
||||
<IconButton name="cross" viewBox="0 0 16 16" onClick={onClose} />
|
||||
)}
|
||||
</notification-header>
|
||||
<notification-body as="div">
|
||||
<body-text-block as='div'>
|
||||
<text-block-title title={translatedTitle} as='h2'>{translatedTitle}</text-block-title>
|
||||
{message && <message as="div">{translate(message)}</message>}
|
||||
</body-text-block>
|
||||
<notification-footer as='div'>
|
||||
<footer-time as='span'>{timeStringFromTimestamp}</footer-time>
|
||||
{onShowDetails && (
|
||||
<actions as="div">
|
||||
<Button
|
||||
type="button"
|
||||
mod={['outlined']}
|
||||
disabled={disableShowDetails}
|
||||
onClick={onShowDetails}
|
||||
>
|
||||
{translate('ui_errors_details')}
|
||||
</Button>
|
||||
</actions>
|
||||
)}
|
||||
</notification-footer>
|
||||
{onShowDetails && (
|
||||
<actions as="div">
|
||||
<Button
|
||||
type="button"
|
||||
mod={['outlined']}
|
||||
disabled={disableShowDetails}
|
||||
onClick={onShowDetails}
|
||||
>
|
||||
{translate('ui_errors_details')}
|
||||
</Button>
|
||||
</actions>
|
||||
)}
|
||||
</notification-body>
|
||||
{!closeAfter && onClose && (
|
||||
<IconButton name="cross" viewBox="0 0 16 16" onClick={onClose} />
|
||||
)}
|
||||
</notification>
|
||||
);
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
export * from './NotificationsItem/Snackbar/NotificationMark';
|
||||
export * from './NotificationsItem/ErrorDetailsDialog/ErrorDetailsDialog';
|
||||
export * from './Notifications';
|
||||
export * from './NotificationsItem/Snackbar/NotificationMark';
|
||||
|
||||
+2
-2
@@ -86,7 +86,7 @@ export class UserFormController implements IInitializableController, IDestructib
|
||||
grantedConnections: this.getGrantedConnections(),
|
||||
});
|
||||
this.collapse();
|
||||
this.notificationService.logSuccess({ title: 'authentication_administration_user_created' });
|
||||
this.notificationService.logInfo({ title: 'authentication_administration_user_created' });
|
||||
} else {
|
||||
if (this.credentials.password) {
|
||||
await this.usersResource.updateCredentials(this.user.userId, { password: this.credentials.password });
|
||||
@@ -94,7 +94,7 @@ export class UserFormController implements IInitializableController, IDestructib
|
||||
await this.updateRoles();
|
||||
await this.saveConnectionPermissions();
|
||||
await this.usersResource.refresh(this.user.userId);
|
||||
this.notificationService.logSuccess({ title: 'authentication_administration_user_updated' });
|
||||
this.notificationService.logInfo({ title: 'authentication_administration_user_updated' });
|
||||
}
|
||||
} catch (exception) {
|
||||
if (!this.error.catch(exception) || this.isDistructed) {
|
||||
|
||||
+2
-2
@@ -77,7 +77,7 @@ implements IInitializableController, IDestructibleController {
|
||||
try {
|
||||
await this.customConnectionService.testConnectionAsync(this.getConnectionConfig());
|
||||
|
||||
this.notificationService.logSuccess({ title: 'Connection is established' });
|
||||
this.notificationService.logInfo({ title: 'Connection is established' });
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'connections_connection_test_fail');
|
||||
} finally {
|
||||
@@ -91,7 +91,7 @@ implements IInitializableController, IDestructibleController {
|
||||
try {
|
||||
const connection = await this.customConnectionService.createConnectionAsync(this.getConnectionConfig());
|
||||
|
||||
this.notificationService.logSuccess({ title: `Connection ${connection.name} created` });
|
||||
this.notificationService.logInfo({ title: `Connection ${connection.name} created` });
|
||||
this.onClose();
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'connections_connection_create_fail');
|
||||
|
||||
+1
-1
@@ -102,7 +102,7 @@ implements IInitializableController, IDestructibleController, IConnectionControl
|
||||
try {
|
||||
await this.connectionInfoResource.init(connection.id, this.credentials);
|
||||
|
||||
this.notificationService.logSuccess({ title: `Connection ${connection.name} established` });
|
||||
this.notificationService.logInfo({ title: `Connection ${connection.name} established` });
|
||||
this.onClose();
|
||||
} catch (exception) {
|
||||
this.showError(exception, 'Failed to establish connection');
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
*/
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import styled, { use, css } from 'reshadow';
|
||||
|
||||
import {
|
||||
Button, Loader, IconButton, SNACKBAR_COMMON_STYLES, NotificationMark
|
||||
Button, Loader, IconButton, SNACKBAR_COMMON_STYLES
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useController } from '@cloudbeaver/core-di';
|
||||
import { ENotificationType, NotificationComponentProps } from '@cloudbeaver/core-events';
|
||||
import { NotificationComponentProps } from '@cloudbeaver/core-events';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
import { EDeferredState } from '@cloudbeaver/core-utils';
|
||||
@@ -35,11 +35,8 @@ const styles = css`
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
loader-container {
|
||||
& Loader {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
IconButton {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -53,9 +50,6 @@ export const ExportNotification: React.FC<Props> = observer(function ExportNotif
|
||||
const controller = useController(ExportNotificationController, notification);
|
||||
const translate = useTranslate();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const timeStringFromTimestamp = notification.timestamp ? new Date(notification.timestamp).toLocaleTimeString() : '';
|
||||
const exportNotificationType = controller.isSuccess ? ENotificationType.Info : ENotificationType.Error;
|
||||
const translatedStatus = translate(controller.status);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
@@ -63,73 +57,61 @@ export const ExportNotification: React.FC<Props> = observer(function ExportNotif
|
||||
|
||||
return styled(useStyles(SNACKBAR_COMMON_STYLES, styles))(
|
||||
<notification as="div" {...use({ mounted })}>
|
||||
{!controller.isPending && <NotificationMark type={exportNotificationType} />}
|
||||
{controller.isPending && (
|
||||
<loader-container as='div'>
|
||||
<Loader fullSize hideMessage />
|
||||
</loader-container>
|
||||
)}
|
||||
<notification-header as="div">
|
||||
<Loader loading={controller.isPending} hideMessage />
|
||||
<message as="div">{translate(controller.status)}</message>
|
||||
{!controller.isPending && (
|
||||
<IconButton name="cross" viewBox="0 0 16 16" onClick={controller.delete} />
|
||||
)}
|
||||
</notification-header>
|
||||
<notification-body as="div">
|
||||
<body-text-block as='div'>
|
||||
<text-block-title title={translatedStatus} as="h2">{translatedStatus}</text-block-title>
|
||||
<message as="div">
|
||||
{controller.sourceName}
|
||||
{controller.task?.context.sourceName && (
|
||||
<pre title={controller.task?.context.sourceName}>
|
||||
{controller.task?.context.sourceName}
|
||||
</pre>
|
||||
)}
|
||||
</message>
|
||||
</body-text-block>
|
||||
<notification-footer as='div'>
|
||||
<footer-time as='span'>{timeStringFromTimestamp}</footer-time>
|
||||
<actions as="div">
|
||||
{controller.isSuccess && (
|
||||
<>
|
||||
<Button
|
||||
type="button"
|
||||
mod={['outlined']}
|
||||
onClick={controller.delete}
|
||||
>
|
||||
{translate('data_transfer_notification_delete')}
|
||||
</Button>
|
||||
<Button
|
||||
tag='a'
|
||||
href={controller.downloadUrl}
|
||||
mod={['unelevated']}
|
||||
download
|
||||
onClick={controller.download}
|
||||
>
|
||||
{translate('data_transfer_notification_download')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{controller.hasDetails && (
|
||||
<source-name as="div">
|
||||
{controller.sourceName}
|
||||
<pre title={controller.task?.context.sourceName}>{controller.task?.context.sourceName}</pre>
|
||||
</source-name>
|
||||
<actions as="div">
|
||||
{controller.isSuccess && (
|
||||
<>
|
||||
<Button
|
||||
type="button"
|
||||
mod={['outlined']}
|
||||
disabled={controller.isDetailsDialogOpen}
|
||||
onClick={controller.showDetails}
|
||||
onClick={controller.delete}
|
||||
>
|
||||
{translate('ui_errors_details')}
|
||||
{translate('data_transfer_notification_delete')}
|
||||
</Button>
|
||||
)}
|
||||
{controller.isPending && (
|
||||
<Button
|
||||
type="button"
|
||||
mod={['outlined']}
|
||||
disabled={controller.process?.getState() === EDeferredState.CANCELLING}
|
||||
onClick={controller.cancel}
|
||||
tag='a'
|
||||
href={controller.downloadUrl}
|
||||
mod={['unelevated']}
|
||||
download
|
||||
onClick={controller.download}
|
||||
>
|
||||
{translate('ui_processing_cancel')}
|
||||
{translate('data_transfer_notification_download')}
|
||||
</Button>
|
||||
)}
|
||||
</actions>
|
||||
</notification-footer>
|
||||
</>
|
||||
)}
|
||||
{controller.hasDetails && (
|
||||
<Button
|
||||
type="button"
|
||||
mod={['outlined']}
|
||||
disabled={controller.isDetailsDialogOpen}
|
||||
onClick={controller.showDetails}
|
||||
>
|
||||
{translate('ui_errors_details')}
|
||||
</Button>
|
||||
)}
|
||||
{controller.isPending && (
|
||||
<Button
|
||||
type="button"
|
||||
mod={['outlined']}
|
||||
disabled={controller.process?.getState() === EDeferredState.CANCELLING}
|
||||
onClick={controller.cancel}
|
||||
>
|
||||
{translate('ui_processing_cancel')}
|
||||
</Button>
|
||||
)}
|
||||
</actions>
|
||||
</notification-body>
|
||||
{!controller.isPending && (
|
||||
<IconButton name="cross" viewBox="0 0 16 16" onClick={controller.delete} />
|
||||
)}
|
||||
</notification>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -72,7 +72,7 @@ export class DataViewerTabService {
|
||||
// tabInfo.trySwitchHandler(this.tabHandler);
|
||||
// }
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Data Viewer Error', 'Error in Data Viewer while processing action with database node');
|
||||
this.notificationService.logException(exception, 'Error in Data Viewer while processing action with database node');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ export class DdlViewerTabService {
|
||||
this.ddlViewerService.loadDdlMetadata(tabContext.nodeInfo.nodeId);
|
||||
}
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Object Viewer Error', 'Error in Object Viewer while processing action with ddl-viewer');
|
||||
this.notificationService.logException(exception, 'Error in Object Viewer while processing action with ddl-viewer');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ export class ObjectPropertiesPageService {
|
||||
|
||||
tabContext.trySwitchPage(this.page);
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Object Viewer Error', 'Error in Object Viewer while processing action with database node');
|
||||
this.notificationService.logException(exception, 'Error in Object Viewer while processing action with database node');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { useDatabaseObjectInfo } from '@cloudbeaver/core-app';
|
||||
import { TableHeader, TableBody, Table } from '@cloudbeaver/core-blocks';
|
||||
@@ -18,13 +19,19 @@ interface ObjectChildrenPropertyTableProps {
|
||||
nodeIds: string[];
|
||||
}
|
||||
|
||||
const styles = css`
|
||||
Table {
|
||||
flex: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ObjectChildrenPropertyTable = observer(function ObjectPropertyTable({
|
||||
nodeIds,
|
||||
}: ObjectChildrenPropertyTableProps) {
|
||||
const firstChild = nodeIds[0] || '';
|
||||
const properties = useDatabaseObjectInfo(firstChild).dbObject?.properties;
|
||||
|
||||
return (
|
||||
return styled(styles)(
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<Header properties={properties || []} />
|
||||
|
||||
@@ -270,7 +270,7 @@ export class ObjectViewerTabService {
|
||||
|
||||
await this.dbObjectService.loadChildren(folderId, resourceKeyList(folderChildren));
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Object Viewer Error', 'Error in Object Viewer while tab selecting');
|
||||
this.notificationService.logException(exception, 'Error in Object Viewer while tab selecting');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +319,7 @@ export class ObjectViewerTabService {
|
||||
this.navigationTabsService.selectTab(tab.id);
|
||||
}
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Object Viewer Error', 'Error in Object Viewer while processing action with database node');
|
||||
this.notificationService.logException(exception, 'Error in Object Viewer while processing action with database node');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ export class SqlEditorNavigatorService {
|
||||
}
|
||||
return;
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'SQL Editor Error', 'Error in SQL Editor while processing connection close');
|
||||
this.notificationService.logException(exception, 'Error in SQL Editor while processing connection close');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ export class SqlEditorNavigatorService {
|
||||
}
|
||||
this.navigationTabsService.selectTab(tab.id);
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'SQL Editor Error', 'Error in SQL Editor while processing action with editor');
|
||||
this.notificationService.logException(exception, 'Error in SQL Editor while processing action with editor');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ export class SqlEditorTabService {
|
||||
try {
|
||||
await this.gql.sdk.sqlContextDestroy({ connectionId, contextId });
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, `Failed to destroy SQL-context ${contextId}`, '', true);
|
||||
this.notificationService.logException(exception, `Failed to destroy SQL-context ${contextId}`, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ export class SQLQueryExecutionProcess extends Deferred<SqlExecuteInfo> {
|
||||
if (this.getState() === EDeferredState.CANCELLING) {
|
||||
this.toCancelled();
|
||||
const message = `Query execution has been canceled${status ? `: ${status}` : ''}`;
|
||||
this.notificationService.logException(error, 'Query execution Error', message);
|
||||
this.notificationService.logException(error, message);
|
||||
} else {
|
||||
this.toRejected(error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user