feat: add per-connection read-only mode

Connections default to read-only. SQL editor wraps queries in a
read-only transaction so PostgreSQL rejects mutations. Data mutation
commands (update_row, insert_row, delete_rows) are blocked at the
Rust layer. Toolbar toggle with confirmation dialog lets users
switch to read-write. Badges shown in workspace, table viewer, and
status bar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 19:36:19 +03:00
parent 9b9d2cee94
commit 72c362dfae
14 changed files with 224 additions and 9 deletions

View File

@@ -7,8 +7,9 @@ import { Input } from "@/components/ui/input";
import { updateRow as updateRowApi } from "@/lib/tauri";
import { getTableColumns } from "@/lib/tauri";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useAppStore } from "@/stores/app-store";
import { toast } from "sonner";
import { Save, RotateCcw, Filter, Loader2 } from "lucide-react";
import { Save, RotateCcw, Filter, Loader2, Lock } from "lucide-react";
interface Props {
connectionId: string;
@@ -17,6 +18,9 @@ interface Props {
}
export function TableDataView({ connectionId, schema, table }: Props) {
const readOnlyMap = useAppStore((s) => s.readOnlyMap);
const isReadOnly = readOnlyMap[connectionId] ?? true;
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(50);
const [sortColumn, _setSortColumn] = useState<string | undefined>();
@@ -57,6 +61,12 @@ export function TableDataView({ connectionId, schema, table }: Props) {
const handleCellDoubleClick = useCallback(
(rowIndex: number, colIndex: number, value: unknown) => {
if (isReadOnly) {
toast.warning("Read-only mode is active", {
description: "Switch to Read-Write mode to edit data.",
});
return;
}
const key = `${rowIndex}:${colIndex}`;
const currentValue = pendingChanges.get(key)?.value ?? value;
const newVal = prompt("Edit value (leave empty and click NULL for null):",
@@ -69,7 +79,7 @@ export function TableDataView({ connectionId, schema, table }: Props) {
});
}
},
[pendingChanges]
[pendingChanges, isReadOnly]
);
const handleCommit = async () => {
@@ -121,6 +131,12 @@ export function TableDataView({ connectionId, schema, table }: Props) {
return (
<div className="flex h-full flex-col">
<div className="flex items-center gap-2 border-b px-2 py-1">
{isReadOnly && (
<span className="flex items-center gap-1 rounded bg-yellow-500/10 px-1.5 py-0.5 text-[10px] font-semibold text-yellow-600 dark:text-yellow-500">
<Lock className="h-3 w-3" />
Read-Only
</span>
)}
<Filter className="h-3.5 w-3.5 text-muted-foreground" />
<Input
placeholder="WHERE clause (e.g. id > 10)"