diff --git a/webapp/packages/plugin-data-spreadsheet-new/package.json b/webapp/packages/plugin-data-spreadsheet-new/package.json
index bca6cf863ac..2b1e55e2cd8 100644
--- a/webapp/packages/plugin-data-spreadsheet-new/package.json
+++ b/webapp/packages/plugin-data-spreadsheet-new/package.json
@@ -25,6 +25,7 @@
"dependencies": {
"@cloudbeaver/core-blocks": "workspace:*",
"@cloudbeaver/core-browser": "workspace:*",
+ "@cloudbeaver/core-connections": "workspace:*",
"@cloudbeaver/core-data-context": "workspace:*",
"@cloudbeaver/core-di": "workspace:*",
"@cloudbeaver/core-dialogs": "workspace:*",
diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableStatusIndicator.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableStatusIndicator.tsx
index e4a5357cb86..9422a9db0bf 100644
--- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableStatusIndicator.tsx
+++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableStatusIndicator.tsx
@@ -9,8 +9,9 @@ import { SqlRowIdentifierState, type SqlResultColumn } from '@cloudbeaver/core-s
import { observer } from 'mobx-react-lite';
import { useContext } from 'react';
-import { IconOrImage, s, useS, useTranslate } from '@cloudbeaver/core-blocks';
-import { isResultSetDataSource } from '@cloudbeaver/plugin-data-viewer';
+import { IconOrImage, s, useResource, useS, useTranslate } from '@cloudbeaver/core-blocks';
+import { ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections';
+import { DatabaseDataFeature, isResultSetDataSource } from '@cloudbeaver/plugin-data-viewer';
import { DataGridContext } from '../DataGridContext.js';
import { TableDataContext } from '../TableDataContext.js';
@@ -20,16 +21,27 @@ import styles from './TableStatusIndicator.module.css';
export const TableStatusIndicator = observer(function TableStatusIndicator() {
const dataGridContext = useContext(DataGridContext);
const tableDataContext = useContext(TableDataContext);
- const readOnlyConnection = dataGridContext.model.isReadonly(dataGridContext.resultIndex);
const translate = useTranslate();
+
+ const source = dataGridContext.model.source;
+ const resultSetSource = isResultSetDataSource(source) ? source : null;
+
+ /* We do NOT use `model.isReadonly()` here —
+ that method aggregates several unrelated reasons
+ and loses information about WHY editing isn't allowed. */
+ const contextInfo = resultSetSource?.executionContext?.context;
+ const connectionKey = contextInfo ? createConnectionParam(contextInfo.projectId, contextInfo.connectionId) : null;
+ const connectionInfoLoader = useResource(TableStatusIndicator, ConnectionInfoResource, connectionKey);
+ const readOnlyConnection = connectionInfoLoader.data?.readOnly ?? false;
+
+ const readOnlyPresentation = source.hasFeature(DatabaseDataFeature.Grouping);
+
const style = useS(styles);
if (!tableDataContext || !dataGridContext) {
return null;
}
- const source = dataGridContext.model.source;
- const resultSetSource = isResultSetDataSource(source) ? source : null;
const rowIdentifierInfo = resultSetSource?.getRowIdentifierInfo(dataGridContext.resultIndex);
const hasRowIdentifier = resultSetSource?.hasElementIdentifier(dataGridContext.resultIndex);
@@ -43,7 +55,10 @@ export const TableStatusIndicator = observer(function TableStatusIndicator() {
const isPrimaryKey = rowIdentifierInfo?.state === SqlRowIdentifierState.PrimaryKey;
const tooltipParts: string[] = [];
- if (readOnlyConnection) {
+ // Presentation-level read-only takes precedence over connection-level read-only.
+ if (readOnlyPresentation) {
+ tooltipParts.push(translate('data_grid_table_readonly_presentation_tooltip'));
+ } else if (readOnlyConnection) {
tooltipParts.push(translate('data_grid_table_readonly_connection_tooltip'));
}
@@ -59,6 +74,15 @@ export const TableStatusIndicator = observer(function TableStatusIndicator() {
tooltipParts.push(translate('data_grid_table_virtual_key_tooltip'));
}
+ const showLockIcon = readOnlyConnection || readOnlyPresentation;
+
+ // Hide the entire indicator when there's nothing meaningful to display (Session Manager)
+ const hasInfo = showLockIcon || !!readOnlyStatus || hasRowIdentifier || isVirtualKey || isPrimaryKey;
+
+ if (!hasInfo) {
+ return null;
+ }
+
const tooltip = tooltipParts.join('\n');
return (
@@ -66,7 +90,7 @@ export const TableStatusIndicator = observer(function TableStatusIndicator() {
title={tooltip}
className="tw:absolute tw:top-1/2 tw:left-1 tw:-translate-y-1/2 tw:z-1 tw:pointer-events-auto tw:flex tw:items-center tw:gap-1 tw:cursor-help"
>
- {readOnlyConnection &&