Compare commits

...
4 Commits
40 changed files with 248 additions and 245 deletions
@@ -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) {
@@ -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);
}
`
);
-2
View File
@@ -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';
@@ -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>
);
});
@@ -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) {
@@ -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 {
@@ -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);
@@ -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}
@@ -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>
);
}
@@ -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';
@@ -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) {
@@ -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');
@@ -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');
}
}
@@ -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');
}
}
}
@@ -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);
}