diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java index fe1f9a7860..e9a18438ac 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java @@ -198,7 +198,7 @@ public class WebConnectionInfo { @Property public String getConnectTime() { return dataSourceContainer.getConnectTime() == null ? null : - CBModelConstants.ISO_DATE_FORMAT.format(dataSourceContainer.getConnectTime()); + CBModelConstants.ISO_DATE_FORMAT.format(dataSourceContainer.getConnectTime().toInstant()); } @Property diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebServerMessage.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebServerMessage.java index 249acd7d47..6dc4bfa762 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebServerMessage.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebServerMessage.java @@ -20,6 +20,9 @@ import io.cloudbeaver.utils.CBModelConstants; import java.io.PrintWriter; import java.io.StringWriter; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; /** * Web server message @@ -35,7 +38,7 @@ public class WebServerMessage { } private final MessageType type; - private final long time; + private final Instant time; private final String message; private final Throwable error; @@ -50,7 +53,7 @@ public class WebServerMessage { public WebServerMessage(MessageType type, String message, Throwable error) { this.type = type; - this.time = System.currentTimeMillis(); + this.time = Instant.now(); this.message = message; this.error = error; } diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java index a75154fd9c..48e550d0d7 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java @@ -73,6 +73,7 @@ import org.jkiss.dbeaver.runtime.jobs.DisconnectJob; import org.jkiss.utils.CommonUtils; import java.lang.reflect.InvocationTargetException; +import java.time.Instant; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; @@ -147,12 +148,12 @@ public class WebSession extends BaseWebSession @Property public String getCreateTime() { - return CBModelConstants.ISO_DATE_FORMAT.format(createTime); + return CBModelConstants.ISO_DATE_FORMAT.format(Instant.ofEpochMilli(createTime)); } @Property public synchronized String getLastAccessTime() { - return CBModelConstants.ISO_DATE_FORMAT.format(lastAccessTime); + return CBModelConstants.ISO_DATE_FORMAT.format(Instant.ofEpochMilli(lastAccessTime)); } public String getLastRemoteAddr() { diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/CBModelConstants.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/CBModelConstants.java index 2287eef8f1..d76e34655e 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/CBModelConstants.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/CBModelConstants.java @@ -18,9 +18,10 @@ package io.cloudbeaver.utils; import org.jkiss.dbeaver.model.DBConstants; -import java.text.DateFormat; -import java.text.SimpleDateFormat; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; public class CBModelConstants { - public static final DateFormat ISO_DATE_FORMAT = new SimpleDateFormat(DBConstants.DEFAULT_ISO_TIMESTAMP_FORMAT); + public static final DateTimeFormatter ISO_DATE_FORMAT = DateTimeFormatter.ofPattern(DBConstants.DEFAULT_ISO_TIMESTAMP_FORMAT) + .withZone(ZoneId.of("UTC")); } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLUtils.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLUtils.java index 9e531c8c72..1e46ba9b8b 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLUtils.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLUtils.java @@ -61,7 +61,7 @@ public class WebSQLUtils { return cellValue; } if (cellValue instanceof Date) { - return CBModelConstants.ISO_DATE_FORMAT.format(cellValue); + return CBModelConstants.ISO_DATE_FORMAT.format(((Date) cellValue).toInstant()); } else if (cellValue instanceof Number) { if (cellValue instanceof Double) { return CommonUtils.niceFormatDouble((Double) cellValue); diff --git a/webapp/packages/core-app/src/locales/en.ts b/webapp/packages/core-app/src/locales/en.ts index 1ff46f407e..82928d3791 100644 --- a/webapp/packages/core-app/src/locales/en.ts +++ b/webapp/packages/core-app/src/locales/en.ts @@ -11,9 +11,4 @@ export default [ ], ['app_shared_sql_generators_panel_title', 'Generate SQL'], ['app_shared_sql_generators_dialog_title', 'Generated SQL'], - ['app_log_view_clear_log', 'Clear logs'], - ['app_log_view_entry_type', 'Type'], - ['app_log_view_entry_timestamp', 'Timestamp'], - ['app_log_view_entry_message', 'Message'], - ['app_log_view_entry_details', 'Details'], ]; diff --git a/webapp/packages/core-app/src/locales/it.ts b/webapp/packages/core-app/src/locales/it.ts index da27b7c045..baebf7d32f 100644 --- a/webapp/packages/core-app/src/locales/it.ts +++ b/webapp/packages/core-app/src/locales/it.ts @@ -9,9 +9,4 @@ export default [ 'app_shared_navigationTabsBar_placeholder', 'Non ci sono oggetti da mostrare. Fai doppio click su un oggetto per aprirlo.', ], - ['app_log_view_clear_log', 'Pulisci i log'], - ['app_log_view_entry_type', 'Digita'], - ['app_log_view_entry_timestamp', 'Timestamp'], - ['app_log_view_entry_message', 'Messaggio'], - ['app_log_view_entry_details', 'Dettagli'], ]; diff --git a/webapp/packages/core-app/src/locales/ru.ts b/webapp/packages/core-app/src/locales/ru.ts index 7df83a6ede..909f271442 100644 --- a/webapp/packages/core-app/src/locales/ru.ts +++ b/webapp/packages/core-app/src/locales/ru.ts @@ -11,9 +11,4 @@ export default [ ['app_shared_inlineEditor_dialog_cancel', 'Отменить'], ['app_shared_sql_generators_panel_title', 'Сгенерировать SQL'], ['app_shared_sql_generators_dialog_title', 'Сгенерированный SQL'], - ['app_log_view_clear_log', 'Очистить лог'], - ['app_log_view_entry_type', 'Тип'], - ['app_log_view_entry_timestamp', 'Время'], - ['app_log_view_entry_message', 'Сообщение'], - ['app_log_view_entry_details', 'Детали'], ]; diff --git a/webapp/packages/core-app/src/locales/zh.ts b/webapp/packages/core-app/src/locales/zh.ts index 63ec2a13f4..a7e323732d 100644 --- a/webapp/packages/core-app/src/locales/zh.ts +++ b/webapp/packages/core-app/src/locales/zh.ts @@ -11,9 +11,4 @@ export default [ ], ['app_shared_sql_generators_panel_title', '生成SQL'], ['app_shared_sql_generators_dialog_title', '生成的SQL'], - ['app_log_view_clear_log', '清除日志'], - ['app_log_view_entry_type', '类型'], - ['app_log_view_entry_timestamp', '时间戳'], - ['app_log_view_entry_message', '消息'], - ['app_log_view_entry_details', '详情'], ]; diff --git a/webapp/packages/core-blocks/src/ResourcesHooks/useResource.ts b/webapp/packages/core-blocks/src/ResourcesHooks/useResource.ts index f399e5c138..3e06e2b759 100644 --- a/webapp/packages/core-blocks/src/ResourcesHooks/useResource.ts +++ b/webapp/packages/core-blocks/src/ResourcesHooks/useResource.ts @@ -137,7 +137,7 @@ export function useResource< const errorContext = useContext(ErrorContext); let key: ResourceKey | null = keyObj as ResourceKey; let includes: TIncludes = [] as unknown as TIncludes; - const [loadFunctionName] = useState(`${component.name}.useResource(${resource.getName()}).load`); + const [loadFunctionName] = useState(`${component.name}.useResource(${resource.getName()}).load` as const); if (isKeyWithIncludes(keyObj)) { key = keyObj.key; @@ -204,21 +204,21 @@ export function useResource< const refObj = useObservableRef(() => ({ loadingPromise: null as (Promise | null), exception: null as Error | null, - useRef: [null, ''] as [TKeyArg | null, string], - get resourceException() { + useRef: [null, ''] as [ResourceKey | null, string], + get resourceException(): Error | null { if (propertiesRef.key === null) { return null; } return propertiesRef.resource.getException(propertiesRef.key); }, - isResourceError() { + isResourceError(): boolean { return isContainsException(this.resourceException); }, - use(key: TKeyArg | null) { + use(key: ResourceKey | null): void { key = toJS(key); - if (this.useRef[0] !== null) { + if (this.useRef[0] !== null && propertiesRef.resource.hasUseId(this.useRef[1])) { if (key !== null && propertiesRef.resource.isIntersect(key, this.useRef[0])) { return; } @@ -227,7 +227,7 @@ export function useResource< } this.useRef = [key, key === null ? '' : propertiesRef.resource.use(key)]; }, - async [loadFunctionName](refresh?: boolean) { + async [loadFunctionName](refresh?: boolean): Promise { const { key, includes, resource } = propertiesRef; if (refresh) { @@ -236,7 +236,7 @@ export function useResource< await resource.load(key, includes as any); }, - async load(refresh?: boolean) { + async load(refresh?: boolean): Promise { if (this.loadingPromise) { return this.loadingPromise; } @@ -246,7 +246,7 @@ export function useResource< } try { - this.loadingPromise = this[loadFunctionName](refresh); + this.loadingPromise = (this[loadFunctionName] as (refresh?: boolean) => Promise)(refresh); await this.loadingPromise; this.exception = null; } catch (exception: any) { @@ -285,7 +285,7 @@ export function useResource< ) & IResourcePrivateState >(() => ({ preloaded, - get canLoad() { + get canLoad(): boolean { return ( propertiesRef.key !== null && this.preloaded @@ -399,24 +399,26 @@ export function useResource< return comparer.default(a, b); }, }), - tryGetData: computed({ - equals: (a, b) => { - if (Array.isArray(a) && Array.isArray(b)) { - return isArraysEqual(a, b, undefined, true); - } + // TODO: in case when array is mutated, but not replaced, it will not be updated + // tryGetData: computed({ + // equals: (a, b) => { + // console.log(a, b, isArraysEqual(a, b, undefined, true)); + // if (Array.isArray(a) && Array.isArray(b)) { + // return isArraysEqual(a, b, undefined, true); + // } - return comparer.default(a, b); - }, - }), - data: computed({ - equals: (a, b) => { - if (Array.isArray(a) && Array.isArray(b)) { - return isArraysEqual(a, b, undefined, true); - } + // return comparer.default(a, b); + // }, + // }), + // data: computed({ + // equals: (a, b) => { + // if (Array.isArray(a) && Array.isArray(b)) { + // return isArraysEqual(a, b, undefined, true); + // } - return comparer.default(a, b); - }, - }), + // return comparer.default(a, b); + // }, + // }), outdated: computed, loaded: computed, loading: computed, diff --git a/webapp/packages/core-sdk/src/AsyncTask/AsyncTaskInfoService.ts b/webapp/packages/core-sdk/src/AsyncTask/AsyncTaskInfoService.ts index 8448980f6c..782e5b3c07 100644 --- a/webapp/packages/core-sdk/src/AsyncTask/AsyncTaskInfoService.ts +++ b/webapp/packages/core-sdk/src/AsyncTask/AsyncTaskInfoService.ts @@ -13,7 +13,7 @@ import { GraphQLService } from '../GraphQLService'; import type { AsyncTaskInfo } from '../sdk'; import { AsyncTask } from './AsyncTask'; -const DELAY_BETWEEN_TRIES = 1000; +const DELAY_BETWEEN_TRIES = 250; @injectable() export class AsyncTaskInfoService { diff --git a/webapp/packages/core-utils/src/index.ts b/webapp/packages/core-utils/src/index.ts index 51b342ea6a..0ac81e7d23 100644 --- a/webapp/packages/core-utils/src/index.ts +++ b/webapp/packages/core-utils/src/index.ts @@ -29,6 +29,7 @@ export * from './isArraysEqual'; export * from './isPrimitive'; export * from './isPropertiesEqual'; export * from './isSafari'; +export * from './isSameDay'; export * from './isValuesEqual'; export * from './md5'; export * from './MetadataMap'; diff --git a/webapp/packages/core-utils/src/isSameDay.ts b/webapp/packages/core-utils/src/isSameDay.ts new file mode 100644 index 0000000000..1fd71a58f9 --- /dev/null +++ b/webapp/packages/core-utils/src/isSameDay.ts @@ -0,0 +1,15 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +export function isSameDay(dateA: Date, dateB: Date): boolean { + return ( + dateA.getFullYear() === dateB.getFullYear() + && dateA.getMonth() === dateB.getMonth() + && dateA.getDate() === dateB.getDate() + ); +} \ No newline at end of file diff --git a/webapp/packages/plugin-log-viewer/src/LogViewer/LogEntry.tsx b/webapp/packages/plugin-log-viewer/src/LogViewer/LogEntry.tsx index 587790bc34..c3b57d106e 100644 --- a/webapp/packages/plugin-log-viewer/src/LogViewer/LogEntry.tsx +++ b/webapp/packages/plugin-log-viewer/src/LogViewer/LogEntry.tsx @@ -10,6 +10,7 @@ import { observer } from 'mobx-react-lite'; import styled, { css, use } from 'reshadow'; import { BASE_TABLE_STYLES, IconOrImage, Link, TableColumnValue, TableItem, useStyles, useTranslate } from '@cloudbeaver/core-blocks'; +import { isSameDay } from '@cloudbeaver/core-utils'; import type { ILogEntry } from './ILogEntry'; @@ -68,6 +69,9 @@ export const LogEntry = observer(function LogEntry({ const isError = !!item.stackTrace; const message = isError ? item.message || translate('ui_error') : item.message; let icon: string | null = null; + const time = new Date(item.time); + const fullTime = time.toLocaleString(); + const displayTime = isSameDay(time, new Date()) ? time.toLocaleTimeString() : fullTime; switch (item.type) { case 'ERROR': @@ -83,7 +87,7 @@ export const LogEntry = observer(function LogEntry({ {icon && } - {item.time} + {displayTime} diff --git a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerTable.tsx b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerTable.tsx index 0cbbaf6914..40b1df7633 100644 --- a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerTable.tsx +++ b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerTable.tsx @@ -35,9 +35,6 @@ const styles = css` & message-title { flex: 1; } - & Button { - flex-shrink: 0; - } } [|buttons] { text-align: right; @@ -72,17 +69,14 @@ export const LogViewerTable = observer(function LogViewerTable({ items, s - {translate('app_log_view_entry_timestamp')} + {translate('plugin_log_viewer_entry_timestamp')} - {translate('app_log_view_entry_message')} - {/* */} + {translate('plugin_log_viewer_entry_message')} {translate('ui_clear')} diff --git a/webapp/packages/plugin-log-viewer/src/LogViewer/useLogViewer.tsx b/webapp/packages/plugin-log-viewer/src/LogViewer/useLogViewer.tsx index 65c0fc0511..6f409bef0c 100644 --- a/webapp/packages/plugin-log-viewer/src/LogViewer/useLogViewer.tsx +++ b/webapp/packages/plugin-log-viewer/src/LogViewer/useLogViewer.tsx @@ -45,7 +45,7 @@ export function useLogViewer() { return props.logViewerService.isActive; }, get logItems() { - return sessionLogsLoader.data; + return sessionLogsLoader.tryGetData; }, clearLog() { sessionLogsLoader.resource.clear(); diff --git a/webapp/packages/plugin-log-viewer/src/locales/en.ts b/webapp/packages/plugin-log-viewer/src/locales/en.ts index b151a91bd1..c24f86cb03 100644 --- a/webapp/packages/plugin-log-viewer/src/locales/en.ts +++ b/webapp/packages/plugin-log-viewer/src/locales/en.ts @@ -1,4 +1,9 @@ export default [ ['plugin_log_viewer_action_enable_label', 'Log Viewer'], ['plugin_log_viewer_placeholder', 'Log viewer is not active'], + ['plugin_log_viewer_clear_log', 'Clear logs'], + ['plugin_log_viewer_entry_type', 'Type'], + ['plugin_log_viewer_entry_timestamp', 'Time'], + ['plugin_log_viewer_entry_message', 'Message'], + ['plugin_log_viewer_entry_details', 'Details'], ]; diff --git a/webapp/packages/plugin-log-viewer/src/locales/it.ts b/webapp/packages/plugin-log-viewer/src/locales/it.ts index d1cd81df14..ea0ad3262d 100644 --- a/webapp/packages/plugin-log-viewer/src/locales/it.ts +++ b/webapp/packages/plugin-log-viewer/src/locales/it.ts @@ -1,4 +1,9 @@ export default [ ['plugin_log_viewer_action_enable_label', 'Visualizzatore del Log'], ['plugin_log_viewer_placeholder', 'Il visualizzatore del log non è attivo'], + ['plugin_log_viewer_clear_log', 'Pulisci i log'], + ['plugin_log_viewer_entry_type', 'Digita'], + ['plugin_log_viewer_entry_timestamp', 'Time'], + ['plugin_log_viewer_entry_message', 'Messaggio'], + ['plugin_log_viewer_entry_details', 'Dettagli'], ]; diff --git a/webapp/packages/plugin-log-viewer/src/locales/ru.ts b/webapp/packages/plugin-log-viewer/src/locales/ru.ts index 99b0c43e74..657a5a6642 100644 --- a/webapp/packages/plugin-log-viewer/src/locales/ru.ts +++ b/webapp/packages/plugin-log-viewer/src/locales/ru.ts @@ -1,4 +1,9 @@ export default [ ['plugin_log_viewer_action_enable_label', 'Просмотр лога'], ['plugin_log_viewer_placeholder', 'Просмотр логов неактивен'], + ['plugin_log_viewer_clear_log', 'Очистить лог'], + ['plugin_log_viewer_entry_type', 'Тип'], + ['plugin_log_viewer_entry_timestamp', 'Время'], + ['plugin_log_viewer_entry_message', 'Сообщение'], + ['plugin_log_viewer_entry_details', 'Детали'], ]; diff --git a/webapp/packages/plugin-log-viewer/src/locales/zh.ts b/webapp/packages/plugin-log-viewer/src/locales/zh.ts index 06f4aade2f..b4cedef2a2 100644 --- a/webapp/packages/plugin-log-viewer/src/locales/zh.ts +++ b/webapp/packages/plugin-log-viewer/src/locales/zh.ts @@ -1,4 +1,9 @@ export default [ ['plugin_log_viewer_action_enable_label', '日志查看器'], ['plugin_log_viewer_placeholder', '日志查看器未激活'], + ['plugin_log_viewer_clear_log', '清除日志'], + ['plugin_log_viewer_entry_type', '类型'], + ['plugin_log_viewer_entry_timestamp', '时间戳'], + ['plugin_log_viewer_entry_message', '消息'], + ['plugin_log_viewer_entry_details', '详情'], ];