feat(ui): add readonly mode visual badge to sidebar and tab bar
Add visual indicators so users can distinguish readonly connections from read-write connections at a glance: - Sidebar: show a lock icon badge on readonly connection nodes - Tab bar: show a lock icon with tooltip on tabs with readonly connections - Add isConnectionReadonly() helper in tabPresentation.ts - Add readOnlyBadge i18n key for all 6 locales (en, zh-CN, zh-TW, es, it, pt-BR)
This commit is contained in:
parent
cb6c09e346
commit
1ca3081c2f
|
|
@ -1,8 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch, nextTick } from "vue";
|
||||
import type { CSSProperties } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { X, Pin, ChevronDown, Table2, Code2, TableProperties, PencilRuler, KeyRound, Pencil, Package, Check } from "@lucide/vue";
|
||||
import { X, Pin, ChevronDown, Table2, Code2, TableProperties, PencilRuler, KeyRound, Pencil, Package, Check, Lock } from "@lucide/vue";
|
||||
import CustomContextMenu, { type ContextMenuItem } from "@/components/ui/CustomContextMenu.vue";
|
||||
import LightDropdown from "@/components/ui/LightDropdown.vue";
|
||||
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
|
||||
|
|
@ -10,7 +10,7 @@ import { useQueryStore } from "@/stores/queryStore";
|
|||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { useTabScroll } from "@/composables/useTabScroll";
|
||||
import { useTabDrag } from "@/composables/useTabDrag";
|
||||
import { connectionColor, tabDisplayTitle, tabTooltipLines } from "@/lib/tabPresentation";
|
||||
import { connectionColor, isConnectionReadonly, tabDisplayTitle, tabTooltipLines } from "@/lib/tabPresentation";
|
||||
import { hexToRgba } from "@/lib/color";
|
||||
import type { QueryTab } from "@/types/database";
|
||||
|
||||
|
|
@ -307,6 +307,12 @@ function activateTab(tabId: string) {
|
|||
@blur="commitRenameTab(tab)"
|
||||
/>
|
||||
<span v-else class="min-w-0 truncate flex-1">{{ tabDisplayTitle(tab, t) }}</span>
|
||||
<Tooltip v-if="isConnectionReadonly(tab.connectionId)">
|
||||
<TooltipTrigger as-child>
|
||||
<Lock class="h-3 w-3 text-muted-foreground shrink-0" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{{ t("connection.readOnlyBadge") }}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<button class="inline-flex rounded p-0.5 text-muted-foreground hover:bg-muted-foreground/20 hover:text-foreground focus:opacity-100" :class="tab.pinned ? 'visible text-primary' : 'invisible group-hover:visible'" @click.stop="queryStore.togglePinnedTab(tab.id)">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<script setup lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick, watch, onBeforeUnmount, inject } from "vue";
|
||||
import { useSqlHighlighter } from "@/composables/useSqlHighlighter";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
|
@ -47,6 +47,7 @@ import {
|
|||
Package,
|
||||
Clipboard,
|
||||
UsersRound,
|
||||
Lock,
|
||||
} from "@lucide/vue";
|
||||
import CustomContextMenu, { type ContextMenuItem } from "@/components/ui/CustomContextMenu.vue";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
|
|
@ -2236,6 +2237,7 @@ const columnComment = computed(() => (props.node.type === "column" && props.node
|
|||
const tableComment = computed(() => ((props.node.type === "table" || props.node.type === "view" || props.node.type === "mongo-collection") && props.node.comment ? props.node.comment : null));
|
||||
const paddingLeft = computed(() => treeItemPaddingLeft(props.depth));
|
||||
const isConnected = computed(() => props.node.type === "connection" && !!props.node.connectionId && connectionStore.connectedIds.has(props.node.connectionId));
|
||||
const isConnectionReadonly = computed(() => props.node.type === "connection" && !!props.node.connectionId && (connectionStore.getConfig(props.node.connectionId)?.read_only ?? false));
|
||||
const canCloseDatabaseConnection = computed(() => props.node.type === "database" && !!props.node.connectionId && props.node.database != null && connectionStore.connectedIds.has(props.node.connectionId));
|
||||
const nodeIconClass = computed(() => {
|
||||
const infoClass = getIconInfo(props.node)?.colorClass;
|
||||
|
|
@ -2976,6 +2978,7 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
|||
<span v-if="columnComment" class="truncate text-muted-foreground/60 text-[10px] max-w-[40%]">{{ columnComment }}</span>
|
||||
<span v-if="tableComment && !settingsStore.editorSettings.sidebarHideTableComments" class="truncate text-muted-foreground/60 text-[10px] max-w-[25%] group-hover:hidden" :title="tableComment">{{ tableComment }}</span>
|
||||
<span v-if="node.type === 'connection' && node.connectionId && connectionStore.connectedIds.has(node.connectionId)" class="w-1.5 h-1.5 rounded-full bg-green-500 shrink-0" />
|
||||
<Badge v-if="isConnectionReadonly" variant="secondary" class="h-4 px-1.5 text-[10px] gap-0.5"><Lock class="w-2.5 h-2.5" />{{ t("connection.readOnlyBadge") }}</Badge>
|
||||
<ConnectionErrorIndicator v-if="node.type === 'connection'" :connection-id="node.connectionId" trigger-class="h-4 w-4" />
|
||||
<button
|
||||
v-if="canPin"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
export default {
|
||||
app: {
|
||||
name: "DBX",
|
||||
},
|
||||
|
|
@ -300,6 +300,7 @@ export default {
|
|||
idleTimeout: "Idle Timeout (seconds)",
|
||||
readOnly: "Read Only",
|
||||
readOnlyHint: "Block all write operations (INSERT, UPDATE, DELETE, etc.)",
|
||||
readOnlyBadge: "Read-only",
|
||||
proxy: "Proxy",
|
||||
proxyEnable: "Connect database through proxy",
|
||||
proxyType: "Proxy Type",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
export default {
|
||||
app: {
|
||||
name: "DBX",
|
||||
},
|
||||
|
|
@ -282,6 +282,7 @@ export default {
|
|||
idleTimeout: "Tiempo de espera inactivo (segundos)",
|
||||
readOnly: "Solo lectura",
|
||||
readOnlyHint: "Bloquear todas las operaciones de escritura (INSERT, UPDATE, DELETE, etc.)",
|
||||
readOnlyBadge: "Solo lectura",
|
||||
proxy: "Proxy",
|
||||
proxyEnable: "Conectar la base de datos mediante proxy",
|
||||
proxyType: "Tipo de proxy",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
export default {
|
||||
app: {
|
||||
name: "DBX",
|
||||
},
|
||||
|
|
@ -287,6 +287,7 @@ export default {
|
|||
idleTimeout: "Timeout Inattività (secondi)",
|
||||
readOnly: "Sola lettura",
|
||||
readOnlyHint: "Blocca tutte le operazioni di scrittura (INSERT, UPDATE, DELETE, ecc.)",
|
||||
readOnlyBadge: "Sola lettura",
|
||||
proxy: "Proxy",
|
||||
proxyEnable: "Connetti database tramite proxy",
|
||||
proxyType: "Tipo Proxy",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
export default {
|
||||
app: {
|
||||
name: "DBX",
|
||||
},
|
||||
|
|
@ -287,6 +287,7 @@ export default {
|
|||
idleTimeout: "Timeout de Inatividade (segundos)",
|
||||
readOnly: "Somente leitura",
|
||||
readOnlyHint: "Bloquear todas as operações de escrita (INSERT, UPDATE, DELETE, etc.)",
|
||||
readOnlyBadge: "Somente leitura",
|
||||
proxy: "Proxy",
|
||||
proxyEnable: "Conectar ao banco de dados via proxy",
|
||||
proxyType: "Tipo de Proxy",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
export default {
|
||||
app: {
|
||||
name: "DBX",
|
||||
},
|
||||
|
|
@ -301,6 +301,7 @@ export default {
|
|||
idleTimeout: "空闲超时(秒)",
|
||||
readOnly: "只读模式",
|
||||
readOnlyHint: "阻止所有写操作(INSERT、UPDATE、DELETE 等)",
|
||||
readOnlyBadge: "只读",
|
||||
proxy: "代理",
|
||||
proxyEnable: "通过代理连接数据库",
|
||||
proxyType: "代理类型",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
export default {
|
||||
app: {
|
||||
name: "DBX",
|
||||
},
|
||||
|
|
@ -287,6 +287,7 @@ export default {
|
|||
idleTimeout: "閒置逾時(秒)",
|
||||
readOnly: "唯讀模式",
|
||||
readOnlyHint: "阻止所有寫入操作(INSERT、UPDATE、DELETE 等)",
|
||||
readOnlyBadge: "唯讀",
|
||||
proxy: "代理伺服器",
|
||||
proxyEnable: "藉由代理伺服器連線資料庫",
|
||||
proxyType: "代理伺服器類型",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import type { QueryResult, QueryTab } from "@/types/database";
|
||||
|
||||
|
|
@ -14,6 +14,11 @@ export function connectionColor(connectionId: string): string {
|
|||
return connectionStore.getConfig(connectionId)?.color || "";
|
||||
}
|
||||
|
||||
export function isConnectionReadonly(connectionId: string): boolean {
|
||||
const connectionStore = useConnectionStore();
|
||||
return connectionStore.getConfig(connectionId)?.read_only ?? false;
|
||||
}
|
||||
|
||||
export function databaseDisplayNameForTab(connectionId: string, database: string, t: Translate): string {
|
||||
const connectionStore = useConnectionStore();
|
||||
const connection = connectionStore.getConfig(connectionId);
|
||||
|
|
|
|||
Loading…
Reference in New Issue