feat: show username/password fields for Access database connections
This commit is contained in:
1 parent
2e021d37d1
commit
edba8214b5
477 files changed
+5720
-19773
No files matched your search
@@ -46,28 +46,12 @@ import ProcedureExecutionDialog from "@/components/objects/ProcedureExecutionDia
|
||||
import * as api from "@/lib/api";
|
||||
import type { ConnectionConfig, ObjectInfo, ObjectSourceKind } from "@/types/database";
|
||||
import { isSchemaAware } from "@/lib/databaseCapabilities";
|
||||
import {
|
||||
supportsSchemaDiagram,
|
||||
supportsTableImport,
|
||||
supportsTableStructureEditing,
|
||||
supportsTableTruncate,
|
||||
} from "@/lib/databaseFeatureSupport";
|
||||
import { supportsSchemaDiagram, supportsTableImport, supportsTableStructureEditing, supportsTableTruncate } from "@/lib/databaseFeatureSupport";
|
||||
import { connectionUsesDatabaseObjectTreeMode, effectiveDatabaseTypeForConnection } from "@/lib/jdbcDialect";
|
||||
import { buildTableSelectSql } from "@/lib/tableSelectSql";
|
||||
import {
|
||||
buildDropObjectSql,
|
||||
buildDuplicateTableStructureSql,
|
||||
buildEmptyTableSql,
|
||||
buildTruncateTableSql,
|
||||
type TableAdminSqlOptions,
|
||||
} from "@/lib/dbAdminSql";
|
||||
import { buildDropObjectSql, buildDuplicateTableStructureSql, buildEmptyTableSql, buildTruncateTableSql, type TableAdminSqlOptions } from "@/lib/dbAdminSql";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import {
|
||||
buildExecutableObjectSourceStatements,
|
||||
buildRoutineRenameObjectSourceStatements,
|
||||
objectSourceSaveExecutionMode,
|
||||
supportsSourceBackedRoutineRename,
|
||||
} from "@/lib/objectSourceEditor";
|
||||
import { buildExecutableObjectSourceStatements, buildRoutineRenameObjectSourceStatements, objectSourceSaveExecutionMode, supportsSourceBackedRoutineRename } from "@/lib/objectSourceEditor";
|
||||
import { buildRenameObjectSql, supportsObjectRename } from "@/lib/objectRenameSql";
|
||||
import { buildViewDdl } from "@/lib/viewDdl";
|
||||
import { isTauriRuntime } from "@/lib/tauriRuntime";
|
||||
@@ -83,16 +67,7 @@ import QueryEditor from "@/components/editor/QueryEditor.vue";
|
||||
import type { SqlFormatDialect } from "@/lib/sqlFormatter";
|
||||
import { isCancelSearchShortcut } from "@/lib/keyboardShortcuts";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import {
|
||||
buildObjectBrowserRows,
|
||||
filterObjectBrowserRows,
|
||||
formatObjectBrowserTimestamp,
|
||||
initialObjectBrowserSortDirection,
|
||||
sortObjectBrowserRows,
|
||||
type ObjectBrowserRow,
|
||||
type ObjectBrowserSortDirection,
|
||||
type ObjectBrowserSortKey,
|
||||
} from "@/lib/objectBrowserRows";
|
||||
import { buildObjectBrowserRows, filterObjectBrowserRows, formatObjectBrowserTimestamp, initialObjectBrowserSortDirection, sortObjectBrowserRows, type ObjectBrowserRow, type ObjectBrowserSortDirection, type ObjectBrowserSortKey } from "@/lib/objectBrowserRows";
|
||||
|
||||
type ObjectFilter = "all" | "tables" | "views" | "procedures" | "functions" | "sequences" | "packages";
|
||||
|
||||
@@ -130,9 +105,7 @@ const sourceContent = ref("");
|
||||
const sourceError = ref("");
|
||||
const sourceRow = ref<ObjectBrowserRow | null>(null);
|
||||
const sourceEditing = ref(false);
|
||||
const effectiveDatabaseType = computed(
|
||||
() => effectiveDatabaseTypeForConnection(props.connection) ?? props.connection.db_type,
|
||||
);
|
||||
const effectiveDatabaseType = computed(() => effectiveDatabaseTypeForConnection(props.connection) ?? props.connection.db_type);
|
||||
const sourceDraft = ref("");
|
||||
const sourceSaving = ref(false);
|
||||
const sourceSaveError = ref("");
|
||||
@@ -164,29 +137,19 @@ let loadId = 0;
|
||||
// Export via background tracker
|
||||
const { addTask: addExportTask } = useExportTracker();
|
||||
|
||||
const needsSchema = computed(
|
||||
() => isSchemaAware(props.connection.db_type) && !connectionUsesDatabaseObjectTreeMode(props.connection),
|
||||
);
|
||||
const needsSchema = computed(() => isSchemaAware(props.connection.db_type) && !connectionUsesDatabaseObjectTreeMode(props.connection));
|
||||
const tableCount = computed(() => rows.value.filter((row) => row.type === "TABLE").length);
|
||||
const viewCount = computed(() => rows.value.filter((row) => row.type === "VIEW").length);
|
||||
const procedureCount = computed(() => rows.value.filter((row) => row.type === "PROCEDURE").length);
|
||||
const functionCount = computed(() => rows.value.filter((row) => row.type === "FUNCTION").length);
|
||||
const sequenceCount = computed(() => rows.value.filter((row) => row.type === "SEQUENCE").length);
|
||||
const packageCount = computed(
|
||||
() => rows.value.filter((row) => row.type === "PACKAGE" || row.type === "PACKAGE_BODY").length,
|
||||
);
|
||||
const packageCount = computed(() => rows.value.filter((row) => row.type === "PACKAGE" || row.type === "PACKAGE_BODY").length);
|
||||
const canOpenStructureEditor = computed(() => supportsTableStructureEditing(effectiveDatabaseType.value));
|
||||
const canOpenDiagram = computed(() => !!props.database && supportsSchemaDiagram(effectiveDatabaseType.value));
|
||||
const canOpenTableImport = computed(() => !!props.database && supportsTableImport(effectiveDatabaseType.value));
|
||||
const supportsTruncateTable = computed(() => supportsTableTruncate(effectiveDatabaseType.value));
|
||||
const sourceDialect = computed<"mysql" | "postgres" | "sqlserver">(() => {
|
||||
if (
|
||||
effectiveDatabaseType.value === "postgres" ||
|
||||
effectiveDatabaseType.value === "gaussdb" ||
|
||||
effectiveDatabaseType.value === "kwdb" ||
|
||||
effectiveDatabaseType.value === "opengauss"
|
||||
)
|
||||
return "postgres";
|
||||
if (effectiveDatabaseType.value === "postgres" || effectiveDatabaseType.value === "gaussdb" || effectiveDatabaseType.value === "kwdb" || effectiveDatabaseType.value === "opengauss") return "postgres";
|
||||
if (effectiveDatabaseType.value === "sqlserver") return "sqlserver";
|
||||
return "mysql";
|
||||
});
|
||||
@@ -252,11 +215,7 @@ const selectedTableRows = computed(() => {
|
||||
return selectableRows.value.filter((row) => ids.has(row.id));
|
||||
});
|
||||
const selectedTableCount = computed(() => selectedTableRows.value.length);
|
||||
const allVisibleTablesSelected = computed(
|
||||
() =>
|
||||
visibleSelectableRows.value.length > 0 &&
|
||||
visibleSelectableRows.value.every((row) => selectedTableIds.value.has(row.id)),
|
||||
);
|
||||
const allVisibleTablesSelected = computed(() => visibleSelectableRows.value.length > 0 && visibleSelectableRows.value.every((row) => selectedTableIds.value.has(row.id)));
|
||||
|
||||
function iconFor(row: ObjectBrowserRow) {
|
||||
if (row.type === "VIEW") return Eye;
|
||||
@@ -307,9 +266,7 @@ function groupedFilteredRows() {
|
||||
const candidateIds = new Set(candidateRows.map((row) => row.id));
|
||||
const matchingRows = filterObjectBrowserRows(candidateRows, query);
|
||||
const matchingIds = new Set(matchingRows.map((row) => row.id));
|
||||
const parentIdsWithMatchingPartitions = new Set(
|
||||
matchingRows.flatMap((row) => (row.partitionParentId ? [row.partitionParentId] : [])),
|
||||
);
|
||||
const parentIdsWithMatchingPartitions = new Set(matchingRows.flatMap((row) => (row.partitionParentId ? [row.partitionParentId] : [])));
|
||||
const rootRows = candidateRows.filter((row) => {
|
||||
if (row.partitionParentId) return false;
|
||||
if (!query) return true;
|
||||
@@ -325,8 +282,7 @@ function groupedFilteredRows() {
|
||||
const parentMatches = matchingIds.has(row.id);
|
||||
const shouldShowPartitions = expandedPartitionParentIds.value.has(row.id) || !!query;
|
||||
if (!shouldShowPartitions) continue;
|
||||
const visiblePartitions =
|
||||
query && !parentMatches ? partitions.filter((partition) => matchingIds.has(partition.id)) : partitions;
|
||||
const visiblePartitions = query && !parentMatches ? partitions.filter((partition) => matchingIds.has(partition.id)) : partitions;
|
||||
result.push(...sortObjectBrowserRows(visiblePartitions, sortKey.value, sortDirection.value));
|
||||
}
|
||||
|
||||
@@ -355,21 +311,11 @@ function togglePartitionParent(row: ObjectBrowserRow) {
|
||||
}
|
||||
|
||||
function canOpenSource(row: ObjectBrowserRow) {
|
||||
return (
|
||||
row.type === "VIEW" ||
|
||||
row.type === "PROCEDURE" ||
|
||||
row.type === "FUNCTION" ||
|
||||
row.type === "SEQUENCE" ||
|
||||
row.type === "PACKAGE" ||
|
||||
row.type === "PACKAGE_BODY"
|
||||
);
|
||||
return row.type === "VIEW" || row.type === "PROCEDURE" || row.type === "FUNCTION" || row.type === "SEQUENCE" || row.type === "PACKAGE" || row.type === "PACKAGE_BODY";
|
||||
}
|
||||
|
||||
function canRename(row: ObjectBrowserRow) {
|
||||
return (
|
||||
supportsObjectRename(effectiveDatabaseType.value, row.type) ||
|
||||
supportsSourceBackedRoutineRename(effectiveDatabaseType.value, row.type as ObjectSourceKind)
|
||||
);
|
||||
return supportsObjectRename(effectiveDatabaseType.value, row.type) || supportsSourceBackedRoutineRename(effectiveDatabaseType.value, row.type as ObjectSourceKind);
|
||||
}
|
||||
|
||||
function sourceTitle(row: ObjectBrowserRow | null) {
|
||||
@@ -405,13 +351,7 @@ async function openSource(row: ObjectBrowserRow) {
|
||||
sourceSaveError.value = "";
|
||||
sourceLoading.value = true;
|
||||
try {
|
||||
const result = await api.getObjectSource(
|
||||
props.connection.id,
|
||||
props.database,
|
||||
row.schema || selectedSchema.value || props.database,
|
||||
row.name,
|
||||
row.type as ObjectSourceKind,
|
||||
);
|
||||
const result = await api.getObjectSource(props.connection.id, props.database, row.schema || selectedSchema.value || props.database, row.name, row.type as ObjectSourceKind);
|
||||
sourceContent.value = result.source;
|
||||
sourceDraft.value = result.source;
|
||||
sourceEditing.value = row.type !== "SEQUENCE";
|
||||
@@ -425,13 +365,7 @@ async function openSource(row: ObjectBrowserRow) {
|
||||
async function openViewDdl(row: ObjectBrowserRow) {
|
||||
if (row.type !== "VIEW") return;
|
||||
try {
|
||||
const result = await api.getObjectSource(
|
||||
props.connection.id,
|
||||
props.database,
|
||||
row.schema || selectedSchema.value || props.database,
|
||||
row.name,
|
||||
"VIEW",
|
||||
);
|
||||
const result = await api.getObjectSource(props.connection.id, props.database, row.schema || selectedSchema.value || props.database, row.name, "VIEW");
|
||||
const ddl = await buildViewDdl({
|
||||
databaseType: effectiveDatabaseType.value,
|
||||
schema: row.schema || selectedSchema.value || props.database,
|
||||
@@ -534,13 +468,7 @@ async function confirmRename() {
|
||||
try {
|
||||
const schema = row.schema || selectedSchema.value || props.database;
|
||||
if (supportsSourceBackedRoutineRename(effectiveDatabaseType.value, row.type as ObjectSourceKind)) {
|
||||
const source = await api.getObjectSource(
|
||||
props.connection.id,
|
||||
props.database,
|
||||
schema,
|
||||
row.name,
|
||||
row.type as ObjectSourceKind,
|
||||
);
|
||||
const source = await api.getObjectSource(props.connection.id, props.database, schema, row.name, row.type as ObjectSourceKind);
|
||||
const statements = await buildRoutineRenameObjectSourceStatements({
|
||||
databaseType: effectiveDatabaseType.value,
|
||||
objectType: row.type as ObjectSourceKind,
|
||||
@@ -566,11 +494,7 @@ async function confirmRename() {
|
||||
showRenameDialog.value = false;
|
||||
if (sourceRow.value?.id === row.id) closeSource();
|
||||
await reload();
|
||||
await connectionStore.refreshObjectListTreeNode(
|
||||
props.connection.id,
|
||||
props.database,
|
||||
row.schema || selectedSchema.value,
|
||||
);
|
||||
await connectionStore.refreshObjectListTreeNode(props.connection.id, props.database, row.schema || selectedSchema.value);
|
||||
} catch (e: any) {
|
||||
renameError.value = e?.message || String(e);
|
||||
}
|
||||
@@ -587,21 +511,10 @@ async function confirmDrop() {
|
||||
name: row.name,
|
||||
});
|
||||
await api.executeQuery(props.connection.id, props.database, sql);
|
||||
const successKey =
|
||||
row.type === "VIEW"
|
||||
? "contextMenu.dropViewSuccess"
|
||||
: row.type === "PROCEDURE"
|
||||
? "contextMenu.dropProcedureSuccess"
|
||||
: row.type === "FUNCTION"
|
||||
? "contextMenu.dropFunctionSuccess"
|
||||
: "contextMenu.dropTableSuccess";
|
||||
const successKey = row.type === "VIEW" ? "contextMenu.dropViewSuccess" : row.type === "PROCEDURE" ? "contextMenu.dropProcedureSuccess" : row.type === "FUNCTION" ? "contextMenu.dropFunctionSuccess" : "contextMenu.dropTableSuccess";
|
||||
toast(t(successKey, { name: row.name }));
|
||||
await reload();
|
||||
await connectionStore.refreshObjectListTreeNode(
|
||||
props.connection.id,
|
||||
props.database,
|
||||
row.schema || selectedSchema.value,
|
||||
);
|
||||
await connectionStore.refreshObjectListTreeNode(props.connection.id, props.database, row.schema || selectedSchema.value);
|
||||
} catch (e: any) {
|
||||
toast(t("contextMenu.tableOperationFailed", { message: e?.message || String(e) }), 5000);
|
||||
}
|
||||
@@ -798,17 +711,8 @@ async function exportStructure(row: ObjectBrowserRow) {
|
||||
async function exportDataLegacy(row: ObjectBrowserRow, format: "json" | "sql") {
|
||||
try {
|
||||
const schema = row.schema || selectedSchema.value;
|
||||
const tableColumns =
|
||||
format === "sql"
|
||||
? await api.getColumns(props.connection.id, props.database, schema || props.database, row.name)
|
||||
: undefined;
|
||||
const queryColumns =
|
||||
props.connection.db_type === "neo4j"
|
||||
? (
|
||||
tableColumns ??
|
||||
(await api.getColumns(props.connection.id, props.database, schema || props.database, row.name))
|
||||
).map((column) => column.name)
|
||||
: undefined;
|
||||
const tableColumns = format === "sql" ? await api.getColumns(props.connection.id, props.database, schema || props.database, row.name) : undefined;
|
||||
const queryColumns = props.connection.db_type === "neo4j" ? (tableColumns ?? (await api.getColumns(props.connection.id, props.database, schema || props.database, row.name))).map((column) => column.name) : undefined;
|
||||
const result = await fetchTableDataForExport({
|
||||
databaseType: effectiveDatabaseType.value,
|
||||
schema,
|
||||
@@ -848,10 +752,7 @@ async function exportDataLegacy(row: ObjectBrowserRow, format: "json" | "sql") {
|
||||
}
|
||||
}
|
||||
|
||||
function columnTypesForResultColumns(
|
||||
columns: string[],
|
||||
tableColumns: Array<{ name: string; data_type: string }>,
|
||||
): Array<string | undefined> {
|
||||
function columnTypesForResultColumns(columns: string[], tableColumns: Array<{ name: string; data_type: string }>): Array<string | undefined> {
|
||||
const typesByName = new Map(tableColumns.map((column) => [column.name.toLocaleLowerCase(), column.data_type]));
|
||||
return columns.map((column) => typesByName.get(column.toLocaleLowerCase()));
|
||||
}
|
||||
@@ -896,12 +797,7 @@ async function exportTableData(row: ObjectBrowserRow, format: "csv" | "xlsx") {
|
||||
|
||||
let task: ExportTask | null = null;
|
||||
try {
|
||||
const queryColumns =
|
||||
props.connection.db_type === "neo4j"
|
||||
? (await api.getColumns(props.connection.id, props.database, schema || props.database, row.name)).map(
|
||||
(column) => column.name,
|
||||
)
|
||||
: undefined;
|
||||
const queryColumns = props.connection.db_type === "neo4j" ? (await api.getColumns(props.connection.id, props.database, schema || props.database, row.name)).map((column) => column.name) : undefined;
|
||||
|
||||
task = addExportTask(row.name, format, filePath);
|
||||
const currentTask = task;
|
||||
@@ -1118,11 +1014,7 @@ async function loadObjects() {
|
||||
});
|
||||
const availableTableIds = new Set(rows.value.filter((row) => row.type === "TABLE").map((row) => row.id));
|
||||
setSelectedTableIds(new Set([...selectedTableIds.value].filter((id) => availableTableIds.has(id))));
|
||||
expandedPartitionParentIds.value = new Set(
|
||||
[...expandedPartitionParentIds.value].filter((id) =>
|
||||
rows.value.some((row) => row.id === id && row.partitionCount),
|
||||
),
|
||||
);
|
||||
expandedPartitionParentIds.value = new Set([...expandedPartitionParentIds.value].filter((id) => rows.value.some((row) => row.id === id && row.partitionCount)));
|
||||
} catch (e: any) {
|
||||
if (id !== loadId) return;
|
||||
error.value = e?.message || String(e);
|
||||
@@ -1160,20 +1052,7 @@ function filterCount(filter: ObjectFilter) {
|
||||
}
|
||||
|
||||
function filterLabel(filter: ObjectFilter) {
|
||||
const key =
|
||||
filter === "tables"
|
||||
? "objects.tables"
|
||||
: filter === "views"
|
||||
? "objects.views"
|
||||
: filter === "procedures"
|
||||
? "objects.procedures"
|
||||
: filter === "functions"
|
||||
? "objects.functions"
|
||||
: filter === "sequences"
|
||||
? "objects.sequences"
|
||||
: filter === "packages"
|
||||
? "objects.packages"
|
||||
: "objects.all";
|
||||
const key = filter === "tables" ? "objects.tables" : filter === "views" ? "objects.views" : filter === "procedures" ? "objects.procedures" : filter === "functions" ? "objects.functions" : filter === "sequences" ? "objects.sequences" : filter === "packages" ? "objects.packages" : "objects.all";
|
||||
return `${t(key)} ${filterCount(filter)}`;
|
||||
}
|
||||
|
||||
@@ -1227,17 +1106,11 @@ function exportDataSubmenu(item: ObjectBrowserRow): ContextMenuItem {
|
||||
function getTableMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
return [
|
||||
{ label: t("contextMenu.viewData"), action: () => openRow(item), icon: Table2 },
|
||||
...(canOpenStructureEditor.value
|
||||
? [{ label: t("contextMenu.editStructure"), action: () => openStructureEditor(item), icon: PencilRuler }]
|
||||
: []),
|
||||
...(canRename(item)
|
||||
? [{ label: t("contextMenu.renameObject"), action: () => requestRename(item), icon: Pencil }]
|
||||
: []),
|
||||
...(canOpenStructureEditor.value ? [{ label: t("contextMenu.editStructure"), action: () => openStructureEditor(item), icon: PencilRuler }] : []),
|
||||
...(canRename(item) ? [{ label: t("contextMenu.renameObject"), action: () => requestRename(item), icon: Pencil }] : []),
|
||||
{ label: t("contextMenu.newQuery"), action: () => openNewQuery(item), icon: TerminalSquare },
|
||||
...(canOpenDiagram.value ? [{ label: t("diagram.open"), action: () => openDiagram(item), icon: Network }] : []),
|
||||
...(canOpenTableImport.value
|
||||
? [{ label: t("contextMenu.importData"), action: () => openTableImport(item), icon: Download }]
|
||||
: []),
|
||||
...(canOpenTableImport.value ? [{ label: t("contextMenu.importData"), action: () => openTableImport(item), icon: Download }] : []),
|
||||
{ label: t("dataCompare.title"), action: () => openDataCompare(item), icon: ArrowRightLeft },
|
||||
{ label: "", separator: true },
|
||||
exportDataSubmenu(item),
|
||||
@@ -1279,9 +1152,7 @@ function getViewMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
{ label: t("contextMenu.editView"), action: () => openSource(item), icon: PencilLine },
|
||||
{ label: t("contextMenu.viewSource"), action: () => openSource(item), icon: Code2 },
|
||||
{ label: t("contextMenu.viewDdl"), action: () => openViewDdl(item), icon: ScrollText },
|
||||
...(canRename(item)
|
||||
? [{ label: t("contextMenu.renameObject"), action: () => requestRename(item), icon: Pencil }]
|
||||
: []),
|
||||
...(canRename(item) ? [{ label: t("contextMenu.renameObject"), action: () => requestRename(item), icon: Pencil }] : []),
|
||||
{ label: t("contextMenu.newQuery"), action: () => openNewQuery(item), icon: TerminalSquare },
|
||||
...(canOpenDiagram.value ? [{ label: t("diagram.open"), action: () => openDiagram(item), icon: Network }] : []),
|
||||
{ label: "", separator: true },
|
||||
@@ -1302,13 +1173,9 @@ function getViewMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
|
||||
function getProcFuncMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
return [
|
||||
...(item.type === "PROCEDURE"
|
||||
? [{ label: t("contextMenu.executeProcedure"), action: () => openProcedureExecution(item), icon: Play }]
|
||||
: []),
|
||||
...(item.type === "PROCEDURE" ? [{ label: t("contextMenu.executeProcedure"), action: () => openProcedureExecution(item), icon: Play }] : []),
|
||||
{ label: t("contextMenu.viewSource"), action: () => openSource(item), icon: Code2 },
|
||||
...(canRename(item)
|
||||
? [{ label: t("contextMenu.renameObject"), action: () => requestRename(item), icon: Pencil }]
|
||||
: []),
|
||||
...(canRename(item) ? [{ label: t("contextMenu.renameObject"), action: () => requestRename(item), icon: Pencil }] : []),
|
||||
{ label: "", separator: true },
|
||||
{
|
||||
label: item.type === "PROCEDURE" ? t("contextMenu.dropProcedure") : t("contextMenu.dropFunction"),
|
||||
@@ -1346,19 +1213,11 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
<div class="min-w-0 truncate text-sm font-medium">
|
||||
{{ props.database }}<template v-if="selectedSchema"> / {{ selectedSchema }}</template>
|
||||
</div>
|
||||
<div class="shrink-0 rounded border bg-muted/40 px-1.5 py-0.5 text-xs text-muted-foreground">
|
||||
{{ filteredRows.length }} / {{ rows.length }}
|
||||
</div>
|
||||
<div class="shrink-0 rounded border bg-muted/40 px-1.5 py-0.5 text-xs text-muted-foreground">{{ filteredRows.length }} / {{ rows.length }}</div>
|
||||
</div>
|
||||
<div class="flex min-w-[240px] flex-1 items-center gap-2">
|
||||
<Search class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<Input
|
||||
v-model="search"
|
||||
data-object-search-input
|
||||
class="h-7 text-xs"
|
||||
:placeholder="t('objects.search')"
|
||||
@keydown="onSearchKeydown"
|
||||
/>
|
||||
<Input v-model="search" data-object-search-input class="h-7 text-xs" :placeholder="t('objects.search')" @keydown="onSearchKeydown" />
|
||||
<div v-if="showObjectFilter" class="flex h-7 shrink-0 items-center rounded border bg-muted/20 p-0.5">
|
||||
<button
|
||||
v-for="filter in objectFilters"
|
||||
@@ -1375,12 +1234,7 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Select
|
||||
v-if="needsSchema"
|
||||
:model-value="selectedSchema"
|
||||
:disabled="loadingSchemas"
|
||||
@update:model-value="onSchemaChange"
|
||||
>
|
||||
<Select v-if="needsSchema" :model-value="selectedSchema" :disabled="loadingSchemas" @update:model-value="onSchemaChange">
|
||||
<SelectTrigger class="h-7 w-36 text-xs">
|
||||
<SelectValue :placeholder="loadingSchemas ? t('objects.loadingSchemas') : t('objects.schema')" />
|
||||
</SelectTrigger>
|
||||
@@ -1417,23 +1271,12 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
<div v-else-if="error" class="flex flex-1 items-center justify-center px-6 text-center text-sm text-destructive">
|
||||
{{ error }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="filteredRows.length === 0"
|
||||
class="flex flex-1 items-center justify-center text-sm text-muted-foreground"
|
||||
>
|
||||
<div v-else-if="filteredRows.length === 0" class="flex flex-1 items-center justify-center text-sm text-muted-foreground">
|
||||
{{ t("objects.empty") }}
|
||||
</div>
|
||||
<div v-else class="flex min-h-0 flex-1 flex-col">
|
||||
<div
|
||||
class="grid h-8 shrink-0 items-center gap-3 border-b bg-muted/40 px-3 text-xs font-medium text-muted-foreground"
|
||||
:style="{ gridTemplateColumns }"
|
||||
>
|
||||
<button
|
||||
class="flex h-6 w-6 items-center justify-center rounded-sm hover:bg-accent"
|
||||
type="button"
|
||||
:disabled="visibleSelectableRows.length === 0"
|
||||
@click="toggleVisibleTableSelection"
|
||||
>
|
||||
<div class="grid h-8 shrink-0 items-center gap-3 border-b bg-muted/40 px-3 text-xs font-medium text-muted-foreground" :style="{ gridTemplateColumns }">
|
||||
<button class="flex h-6 w-6 items-center justify-center rounded-sm hover:bg-accent" type="button" :disabled="visibleSelectableRows.length === 0" @click="toggleVisibleTableSelection">
|
||||
<CheckSquare v-if="allVisibleTablesSelected" class="h-3.5 w-3.5 text-primary" />
|
||||
<Square v-else class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
@@ -1445,42 +1288,20 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
<span class="truncate">{{ t("objects.type") }}</span>
|
||||
<component :is="sortIconFor('type')" v-if="sortIconFor('type')" class="h-3 w-3 shrink-0" />
|
||||
</button>
|
||||
<button
|
||||
v-if="hasCreatedAt"
|
||||
class="flex min-w-0 items-center gap-1 truncate text-left"
|
||||
type="button"
|
||||
@click="toggleSort('created_at')"
|
||||
>
|
||||
<button v-if="hasCreatedAt" class="flex min-w-0 items-center gap-1 truncate text-left" type="button" @click="toggleSort('created_at')">
|
||||
<span class="truncate">{{ t("objects.createdAt") }}</span>
|
||||
<component :is="sortIconFor('created_at')" v-if="sortIconFor('created_at')" class="h-3 w-3 shrink-0" />
|
||||
</button>
|
||||
<button
|
||||
v-if="hasUpdatedAt"
|
||||
class="flex min-w-0 items-center gap-1 truncate text-left"
|
||||
type="button"
|
||||
@click="toggleSort('updated_at')"
|
||||
>
|
||||
<button v-if="hasUpdatedAt" class="flex min-w-0 items-center gap-1 truncate text-left" type="button" @click="toggleSort('updated_at')">
|
||||
<span class="truncate">{{ t("objects.updatedAt") }}</span>
|
||||
<component :is="sortIconFor('updated_at')" v-if="sortIconFor('updated_at')" class="h-3 w-3 shrink-0" />
|
||||
</button>
|
||||
<button
|
||||
v-if="hasComments"
|
||||
class="flex min-w-0 items-center gap-1 truncate text-left"
|
||||
type="button"
|
||||
@click="toggleSort('comment')"
|
||||
>
|
||||
<button v-if="hasComments" class="flex min-w-0 items-center gap-1 truncate text-left" type="button" @click="toggleSort('comment')">
|
||||
<span class="truncate">{{ t("objects.comment") }}</span>
|
||||
<component :is="sortIconFor('comment')" v-if="sortIconFor('comment')" class="h-3 w-3 shrink-0" />
|
||||
</button>
|
||||
</div>
|
||||
<RecycleScroller
|
||||
class="object-browser-scroller min-h-0 flex-1"
|
||||
:items="filteredRows"
|
||||
:item-size="38"
|
||||
:buffer="600"
|
||||
:skip-hover="true"
|
||||
key-field="id"
|
||||
>
|
||||
<RecycleScroller class="object-browser-scroller min-h-0 flex-1" :items="filteredRows" :item-size="38" :buffer="600" :skip-hover="true" key-field="id">
|
||||
<template #default="{ item }">
|
||||
<CustomContextMenu :items="getObjectBrowserMenuItems(item)" v-slot="{ onContextMenu }">
|
||||
<div
|
||||
@@ -1493,12 +1314,7 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
@click="onRowClick(item, $event)"
|
||||
@contextmenu="onContextMenu"
|
||||
>
|
||||
<button
|
||||
class="flex h-6 w-6 items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
type="button"
|
||||
:class="{ invisible: item.type !== 'TABLE' }"
|
||||
@click.stop="toggleTableSelection(item)"
|
||||
>
|
||||
<button class="flex h-6 w-6 items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground" type="button" :class="{ invisible: item.type !== 'TABLE' }" @click.stop="toggleTableSelection(item)">
|
||||
<CheckSquare v-if="selectedTableIds.has(item.id)" class="h-3.5 w-3.5 text-primary" />
|
||||
<Square v-else class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
@@ -1516,26 +1332,15 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
<span v-else class="h-5 w-5 shrink-0" :class="{ 'ml-4': item.partitionParentId }" />
|
||||
<component :is="iconFor(item)" class="h-3.5 w-3.5 shrink-0" :class="iconClass(item.type)" />
|
||||
<span class="truncate text-[13px] font-medium text-foreground">{{ item.name }}</span>
|
||||
<span
|
||||
v-if="item.partitionCount"
|
||||
class="shrink-0 rounded border bg-muted/40 px-1.5 py-0.5 text-[10px] font-medium leading-none text-muted-foreground"
|
||||
>
|
||||
<span v-if="item.partitionCount" class="shrink-0 rounded border bg-muted/40 px-1.5 py-0.5 text-[10px] font-medium leading-none text-muted-foreground">
|
||||
{{ t("objects.partitions", { count: item.partitionCount }) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="truncate text-xs text-muted-foreground">{{ typeLabel(item.type) }}</div>
|
||||
<div
|
||||
v-if="hasCreatedAt"
|
||||
class="truncate text-xs tabular-nums text-muted-foreground"
|
||||
:title="formatObjectBrowserTimestamp(item.created_at)"
|
||||
>
|
||||
<div v-if="hasCreatedAt" class="truncate text-xs tabular-nums text-muted-foreground" :title="formatObjectBrowserTimestamp(item.created_at)">
|
||||
{{ formatObjectBrowserTimestamp(item.created_at) }}
|
||||
</div>
|
||||
<div
|
||||
v-if="hasUpdatedAt"
|
||||
class="truncate text-xs tabular-nums text-muted-foreground"
|
||||
:title="formatObjectBrowserTimestamp(item.updated_at)"
|
||||
>
|
||||
<div v-if="hasUpdatedAt" class="truncate text-xs tabular-nums text-muted-foreground" :title="formatObjectBrowserTimestamp(item.updated_at)">
|
||||
{{ formatObjectBrowserTimestamp(item.updated_at) }}
|
||||
</div>
|
||||
<div v-if="hasComments" class="truncate text-xs text-muted-foreground" :title="item.comment || ''">
|
||||
@@ -1549,45 +1354,17 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
<div class="flex h-8 shrink-0 items-center gap-2 border-b bg-muted/20 px-3">
|
||||
<Code2 class="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<span class="min-w-0 flex-1 truncate text-xs font-medium">{{ sourceTitle(sourceRow) }}</span>
|
||||
<Button
|
||||
v-if="sourceEditing"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-6 px-2 text-xs"
|
||||
:disabled="sourceSaving || !sourceDraft.trim()"
|
||||
@click="saveSource"
|
||||
>
|
||||
<Button v-if="sourceEditing" variant="ghost" size="sm" class="h-6 px-2 text-xs" :disabled="sourceSaving || !sourceDraft.trim()" @click="saveSource">
|
||||
<Loader2 v-if="sourceSaving" class="mr-1 h-3 w-3 animate-spin" />
|
||||
{{ t("objects.saveSource") }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="sourceEditing"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-6 px-2 text-xs"
|
||||
:disabled="sourceSaving"
|
||||
@click="cancelEditSource"
|
||||
>
|
||||
<Button v-if="sourceEditing" variant="ghost" size="sm" class="h-6 px-2 text-xs" :disabled="sourceSaving" @click="cancelEditSource">
|
||||
{{ t("objects.cancelEdit") }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="!sourceEditing"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-5 w-5"
|
||||
:disabled="!sourceContent"
|
||||
@click="copySource"
|
||||
>
|
||||
<Button v-if="!sourceEditing" variant="ghost" size="icon" class="h-5 w-5" :disabled="!sourceContent" @click="copySource">
|
||||
<Copy class="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
v-if="!sourceEditing"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-5 w-5"
|
||||
:disabled="!sourceContent"
|
||||
@click="editSource"
|
||||
>
|
||||
<Button v-if="!sourceEditing" variant="ghost" size="icon" class="h-5 w-5" :disabled="!sourceContent" @click="editSource">
|
||||
<PencilLine class="h-3 w-3" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" class="h-5 w-5" @click="closeSource">
|
||||
@@ -1601,18 +1378,7 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
{{ sourceError }}
|
||||
</div>
|
||||
<div v-else-if="sourceEditing" class="flex min-h-0 flex-1 flex-col" data-object-source-editor>
|
||||
<QueryEditor
|
||||
v-model="sourceDraft"
|
||||
class="min-h-0 flex-1"
|
||||
:connection-id="props.connection.id"
|
||||
:database="props.database"
|
||||
:schema="selectedSchema"
|
||||
:database-type="props.connection.db_type"
|
||||
:dialect="sourceDialect"
|
||||
:format-dialect="sourceFormatDialect"
|
||||
force-word-wrap
|
||||
@save="saveSource"
|
||||
/>
|
||||
<QueryEditor v-model="sourceDraft" class="min-h-0 flex-1" :connection-id="props.connection.id" :database="props.database" :schema="selectedSchema" :database-type="props.connection.db_type" :dialect="sourceDialect" :format-dialect="sourceFormatDialect" force-word-wrap @save="saveSource" />
|
||||
<div v-if="sourceSaveError" class="shrink-0 border-t px-3 py-2 text-xs text-destructive">
|
||||
{{ sourceSaveError }}
|
||||
</div>
|
||||
@@ -1636,22 +1402,9 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DangerConfirmDialog
|
||||
v-model:open="showDropConfirm"
|
||||
:title="dropConfirmTitle()"
|
||||
:details="dropConfirmMessage()"
|
||||
:confirm-label="t('dangerDialog.deleteConfirm')"
|
||||
@confirm="confirmDrop"
|
||||
/>
|
||||
<DangerConfirmDialog v-model:open="showDropConfirm" :title="dropConfirmTitle()" :details="dropConfirmMessage()" :confirm-label="t('dangerDialog.deleteConfirm')" @confirm="confirmDrop" />
|
||||
|
||||
<DangerConfirmDialog
|
||||
v-model:open="showBatchDropConfirm"
|
||||
:title="t('objects.confirmBatchDropTitle')"
|
||||
:message="t('objects.confirmBatchDropMessage', { count: selectedTableCount })"
|
||||
:sql="batchDropPreviewSql"
|
||||
:confirm-label="t('objects.dropSelected')"
|
||||
@confirm="confirmBatchDropTables"
|
||||
/>
|
||||
<DangerConfirmDialog v-model:open="showBatchDropConfirm" :title="t('objects.confirmBatchDropTitle')" :message="t('objects.confirmBatchDropMessage', { count: selectedTableCount })" :sql="batchDropPreviewSql" :confirm-label="t('objects.dropSelected')" @confirm="confirmBatchDropTables" />
|
||||
|
||||
<Dialog v-model:open="showRenameDialog">
|
||||
<DialogContent class="sm:max-w-[420px]">
|
||||
@@ -1659,16 +1412,8 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
<DialogTitle>{{ t("contextMenu.renameObjectTitle") }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="grid gap-3">
|
||||
<Input
|
||||
v-model="renameInput"
|
||||
:placeholder="t('contextMenu.renameObjectNamePlaceholder')"
|
||||
@keydown.enter.prevent="confirmRename"
|
||||
/>
|
||||
<pre
|
||||
v-if="renamePreviewSqlText"
|
||||
class="max-h-32 overflow-auto rounded bg-muted p-3 text-xs whitespace-pre-wrap"
|
||||
v-html="highlight(renamePreviewSqlText)"
|
||||
></pre>
|
||||
<Input v-model="renameInput" :placeholder="t('contextMenu.renameObjectNamePlaceholder')" @keydown.enter.prevent="confirmRename" />
|
||||
<pre v-if="renamePreviewSqlText" class="max-h-32 overflow-auto rounded bg-muted p-3 text-xs whitespace-pre-wrap" v-html="highlight(renamePreviewSqlText)"></pre>
|
||||
<p v-if="renameError" class="text-sm text-destructive">{{ renameError }}</p>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
@@ -1689,14 +1434,7 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
@confirm="confirmTruncateTable"
|
||||
/>
|
||||
|
||||
<DangerConfirmDialog
|
||||
v-model:open="showEmptyConfirm"
|
||||
:title="t('contextMenu.confirmEmptyTableTitle')"
|
||||
:message="t('contextMenu.confirmEmptyTableMessage', { name: emptyTarget?.name ?? '' })"
|
||||
:sql="emptyPreviewSql"
|
||||
:confirm-label="t('contextMenu.emptyTable')"
|
||||
@confirm="confirmEmptyTable"
|
||||
/>
|
||||
<DangerConfirmDialog v-model:open="showEmptyConfirm" :title="t('contextMenu.confirmEmptyTableTitle')" :message="t('contextMenu.confirmEmptyTableMessage', { name: emptyTarget?.name ?? '' })" :sql="emptyPreviewSql" :confirm-label="t('contextMenu.emptyTable')" @confirm="confirmEmptyTable" />
|
||||
|
||||
<ProcedureExecutionDialog
|
||||
v-if="procedureExecutionTarget"
|
||||
@@ -1715,11 +1453,7 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t("contextMenu.duplicateNameTitle") }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Input
|
||||
v-model="duplicateTableName"
|
||||
:placeholder="t('contextMenu.duplicateNamePlaceholder')"
|
||||
@keydown.enter.prevent="confirmDuplicateStructure"
|
||||
/>
|
||||
<Input v-model="duplicateTableName" :placeholder="t('contextMenu.duplicateNamePlaceholder')" @keydown.enter.prevent="confirmDuplicateStructure" />
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="showDuplicateDialog = false">{{ t("dangerDialog.cancel") }}</Button>
|
||||
<Button :disabled="!duplicateTableName.trim()" @click="confirmDuplicateStructure">
|
||||
|
||||
@@ -8,12 +8,7 @@ import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "
|
||||
import { Input } from "@/components/ui/input";
|
||||
import LightTooltip from "@/components/ui/LightTooltip.vue";
|
||||
import { loadRoutineParameters } from "@/lib/routineParameters";
|
||||
import {
|
||||
acceptsRoutineInput,
|
||||
buildProcedureExecutionSql,
|
||||
buildProcedureExecutionSqlFromValues,
|
||||
type RoutineParameterValue,
|
||||
} from "@/lib/routineExecutionSql";
|
||||
import { acceptsRoutineInput, buildProcedureExecutionSql, buildProcedureExecutionSqlFromValues, type RoutineParameterValue } from "@/lib/routineExecutionSql";
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -143,9 +138,7 @@ function canEditParameter(parameter: RoutineParameterValue): boolean {
|
||||
|
||||
<template>
|
||||
<Dialog v-model:open="open">
|
||||
<DialogContent
|
||||
class="max-h-[86vh] border border-border !bg-background text-foreground shadow-2xl !backdrop-blur-none sm:max-w-[780px]"
|
||||
>
|
||||
<DialogContent class="max-h-[86vh] border border-border !bg-background text-foreground shadow-2xl !backdrop-blur-none sm:max-w-[780px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t("contextMenu.confirmExecuteProcedureTitle") }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
@@ -170,19 +163,14 @@ function canEditParameter(parameter: RoutineParameterValue): boolean {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="loading"
|
||||
class="flex items-center gap-2 rounded-md border bg-muted/30 px-3 py-2 text-sm text-muted-foreground"
|
||||
>
|
||||
<div v-if="loading" class="flex items-center gap-2 rounded-md border bg-muted/30 px-3 py-2 text-sm text-muted-foreground">
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
{{ t("contextMenu.loadingProcedureParameters") }}
|
||||
</div>
|
||||
|
||||
<div v-else-if="parameters.length" class="overflow-x-auto rounded-md border bg-background">
|
||||
<div class="min-w-[650px]">
|
||||
<div
|
||||
class="grid grid-cols-[minmax(120px,1.2fr)_minmax(96px,1fr)_72px_minmax(160px,1.5fr)_64px_86px] border-b bg-muted px-3 py-2 text-xs font-medium text-muted-foreground"
|
||||
>
|
||||
<div class="grid grid-cols-[minmax(120px,1.2fr)_minmax(96px,1fr)_72px_minmax(160px,1.5fr)_64px_86px] border-b bg-muted px-3 py-2 text-xs font-medium text-muted-foreground">
|
||||
<div>{{ t("contextMenu.parameterName") }}</div>
|
||||
<div>{{ t("contextMenu.parameterType") }}</div>
|
||||
<div>{{ t("contextMenu.parameterMode") }}</div>
|
||||
@@ -191,54 +179,24 @@ function canEditParameter(parameter: RoutineParameterValue): boolean {
|
||||
<div class="flex items-center gap-1">
|
||||
{{ t("contextMenu.parameterDefault") }}
|
||||
<LightTooltip :text="t('contextMenu.parameterDefaultHint')" side="top" :delay="150">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex h-4 w-4 items-center justify-center rounded-full text-muted-foreground hover:bg-background hover:text-foreground"
|
||||
:aria-label="t('contextMenu.parameterDefaultHint')"
|
||||
>
|
||||
<button type="button" class="inline-flex h-4 w-4 items-center justify-center rounded-full text-muted-foreground hover:bg-background hover:text-foreground" :aria-label="t('contextMenu.parameterDefaultHint')">
|
||||
<CircleHelp class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</LightTooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="parameter in parameters"
|
||||
:key="`${parameter.ordinal}:${parameter.name}`"
|
||||
class="grid grid-cols-[minmax(120px,1.2fr)_minmax(96px,1fr)_72px_minmax(160px,1.5fr)_64px_86px] items-center gap-2 border-b px-3 py-2 text-sm last:border-b-0"
|
||||
>
|
||||
<div v-for="parameter in parameters" :key="`${parameter.ordinal}:${parameter.name}`" class="grid grid-cols-[minmax(120px,1.2fr)_minmax(96px,1fr)_72px_minmax(160px,1.5fr)_64px_86px] items-center gap-2 border-b px-3 py-2 text-sm last:border-b-0">
|
||||
<div class="min-w-0 truncate font-medium">{{ parameter.name }}</div>
|
||||
<div class="min-w-0 truncate text-muted-foreground">{{ parameter.dataType || "-" }}</div>
|
||||
<div class="text-xs text-muted-foreground">{{ parameter.mode }}</div>
|
||||
<Input
|
||||
v-model="parameter.value"
|
||||
class="h-8 bg-background font-mono text-xs"
|
||||
:disabled="!canEditParameter(parameter) || parameter.useNull || parameter.useDefault"
|
||||
:placeholder="
|
||||
canEditParameter(parameter) ? t('contextMenu.parameterValuePlaceholder') : t('contextMenu.outputOnly')
|
||||
"
|
||||
/>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="h-4 w-4 accent-primary"
|
||||
:checked="!!parameter.useNull"
|
||||
:disabled="!canEditParameter(parameter) || parameter.useDefault"
|
||||
@change="(event: Event) => (parameter.useNull = (event.target as HTMLInputElement).checked)"
|
||||
/>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="h-4 w-4 accent-primary"
|
||||
:checked="!!parameter.useDefault"
|
||||
:disabled="!canEditParameter(parameter) || !parameter.hasDefault || parameter.useNull"
|
||||
@change="(event: Event) => (parameter.useDefault = (event.target as HTMLInputElement).checked)"
|
||||
/>
|
||||
<Input v-model="parameter.value" class="h-8 bg-background font-mono text-xs" :disabled="!canEditParameter(parameter) || parameter.useNull || parameter.useDefault" :placeholder="canEditParameter(parameter) ? t('contextMenu.parameterValuePlaceholder') : t('contextMenu.outputOnly')" />
|
||||
<input type="checkbox" class="h-4 w-4 accent-primary" :checked="!!parameter.useNull" :disabled="!canEditParameter(parameter) || parameter.useDefault" @change="(event: Event) => (parameter.useNull = (event.target as HTMLInputElement).checked)" />
|
||||
<input type="checkbox" class="h-4 w-4 accent-primary" :checked="!!parameter.useDefault" :disabled="!canEditParameter(parameter) || !parameter.hasDefault || parameter.useNull" @change="(event: Event) => (parameter.useDefault = (event.target as HTMLInputElement).checked)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p
|
||||
v-else-if="loadError"
|
||||
class="rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-800"
|
||||
>
|
||||
<p v-else-if="loadError" class="rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-800">
|
||||
{{ t("contextMenu.procedureParametersUnavailable") }}
|
||||
</p>
|
||||
|
||||
@@ -253,12 +211,7 @@ function canEditParameter(parameter: RoutineParameterValue): boolean {
|
||||
{{ t("contextMenu.resetSqlPreview") }}
|
||||
</Button>
|
||||
</div>
|
||||
<textarea
|
||||
:value="sqlDraft"
|
||||
class="min-h-28 w-full resize-y rounded-md border border-input bg-background px-3 py-2 font-mono text-xs outline-none transition-colors focus:border-ring focus:ring-1 focus:ring-ring/40"
|
||||
spellcheck="false"
|
||||
@input="onSqlInput"
|
||||
></textarea>
|
||||
<textarea :value="sqlDraft" class="min-h-28 w-full resize-y rounded-md border border-input bg-background px-3 py-2 font-mono text-xs outline-none transition-colors focus:border-ring focus:ring-1 focus:ring-ring/40" spellcheck="false" @input="onSqlInput"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in new issue
Block a user