Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c51dad39e0 | |||
| 1d350a3a29 | |||
| 81e3cce070 | |||
| f9de2d77b5 | |||
| 3956eaf389 | |||
| d13e2c2d87 | |||
| ebf2371da9 | |||
| fa4b12448d | |||
| 5fe6dfa551 | |||
| 6061c8b0a5 | |||
| 1ac0aa8a3e | |||
| 5d04d7f01f | |||
| 9c97e347c5 | |||
| 22967d123d | |||
| 3fed650254 | |||
| b57b2083d3 | |||
| 1f47e8c62e | |||
| d7ce653d74 | |||
| 07c803efee | |||
| 26b6d9133e | |||
| 146084bdb3 | |||
| fa82b4630b | |||
| d00841030f | |||
| c517bb0be6 | |||
| e585d8be8f | |||
| 8be76832a5 | |||
| 99df266a3e | |||
| 5660874992 | |||
| b0dade9da3 | |||
| a533858804 | |||
| d3bcc984e7 | |||
| 99e8307a80 | |||
| 73926ea392 | |||
| 5ff24526b7 | |||
| 32ed1c57bd | |||
| f4c3a95348 | |||
| b1a908343a | |||
| 7f9d7eb36e | |||
| 30820e29fc | |||
| a85ea2e0f7 | |||
| 993e713955 | |||
| 3151e30db1 | |||
| eb5219dd68 | |||
| bb44783369 | |||
| 33b46c4db3 | |||
| 3730aae62a | |||
| 065062d58a | |||
| 7b2f58e68e | |||
| e2fc23fcf8 | |||
| 6f56ef284d | |||
| 08a644ba39 | |||
| 6ae19ac4a6 | |||
| 7761cbe81d | |||
| f981d88150 | |||
| e2a23eaa0d | |||
| 9d510b3c08 | |||
| a98f5ac45e | |||
| b989e964c0 | |||
| 3ff6eefa06 | |||
| 67fde9be3c | |||
| df7ac89723 | |||
| 358df9f53b | |||
| 02e3bfaa8a | |||
| dde74fa73b |
@@ -47,7 +47,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 6b5e2ff831db9baedb2a43862daa4247810b15de
|
||||
ref: 87c3efdaf83786abee4366dee2c58fea355edc4c
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 6b5e2ff831db9baedb2a43862daa4247810b15de
|
||||
ref: 87c3efdaf83786abee4366dee2c58fea355edc4c
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 6b5e2ff831db9baedb2a43862daa4247810b15de
|
||||
ref: 87c3efdaf83786abee4366dee2c58fea355edc4c
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 6b5e2ff831db9baedb2a43862daa4247810b15de
|
||||
ref: 87c3efdaf83786abee4366dee2c58fea355edc4c
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 6b5e2ff831db9baedb2a43862daa4247810b15de
|
||||
ref: 87c3efdaf83786abee4366dee2c58fea355edc4c
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
cd dbgate-merged
|
||||
node adjustNpmPackageJsonPremium
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
run: npm install -g npm@11.5.1
|
||||
- name: Remove dbmodel - should be not published
|
||||
run: |
|
||||
cd ..
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
with:
|
||||
node-version: 22.x
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
run: npm install -g npm@11.5.1
|
||||
- name: yarn install
|
||||
run: |
|
||||
yarn install
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 6b5e2ff831db9baedb2a43862daa4247810b15de
|
||||
ref: 87c3efdaf83786abee4366dee2c58fea355edc4c
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 6b5e2ff831db9baedb2a43862daa4247810b15de
|
||||
ref: 87c3efdaf83786abee4366dee2c58fea355edc4c
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
- GUI uses Svelte4 (packages/web)
|
||||
- GUI is tested with E2E tests in `e2e-tests` folder, using Cypress. Use data-testid attribute in components to make them easier to test.
|
||||
- data-testid format: ComponentName_identifier. Use reasonable identifiers
|
||||
- don't change content of storageModel.js - this is generated from table YAMLs with "yarn storage-json" command
|
||||
@@ -9,6 +9,26 @@ Builds:
|
||||
- linux - application for linux
|
||||
- win - application for Windows
|
||||
|
||||
## 7.1.8
|
||||
|
||||
- FIXED: NPM packages build
|
||||
|
||||
## 7.1.7
|
||||
|
||||
- FIXED: Resolved duplicate translation tags #1420
|
||||
- FIXED: Referer error on map display #1418
|
||||
- FIXED: Export failure when password mode is enabled #1409
|
||||
- FIXED: Unreadable text in export #1408
|
||||
- FIXED: Column names set to "undefined" in export #1406
|
||||
- FIXED: Fixed freezing issues with large fields #1399
|
||||
- ADDED: "Fetch All" button #1398
|
||||
- ADDED: Option to disable AI features
|
||||
- ADDED: PostgreSQL loading optimalization
|
||||
|
||||
## 7.1.6
|
||||
|
||||
- FIXED: Issues with cloud and file loading
|
||||
|
||||
## 7.1.5
|
||||
|
||||
- FIXED: Issues with cloud and file loading
|
||||
|
||||
@@ -400,6 +400,14 @@ function createWindow() {
|
||||
},
|
||||
});
|
||||
|
||||
mainWindow.webContents.session.webRequest.onBeforeSendHeaders(
|
||||
{ urls: ['https://*.tile.openstreetmap.org/*'] },
|
||||
(details, callback) => {
|
||||
details.requestHeaders['Referer'] = 'https://www.dbgate.io';
|
||||
callback({ requestHeaders: details.requestHeaders });
|
||||
}
|
||||
);
|
||||
|
||||
if (initialConfig['winIsMaximized']) {
|
||||
mainWindow.maximize();
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "7.1.7-premium-beta.1",
|
||||
"version": "7.1.8",
|
||||
"name": "dbgate-all",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
|
||||
@@ -19,6 +19,26 @@ const unzipDirectory = require('../shell/unzipDirectory');
|
||||
|
||||
const logger = getLogger('archive');
|
||||
|
||||
/**
|
||||
* Rejects any archive name (folder or file) that contains path-traversal
|
||||
* sequences, directory separators, or null bytes. These values are used
|
||||
* directly in path.join() calls; allowing traversal would let callers read
|
||||
* or write arbitrary files outside the archive directory.
|
||||
*/
|
||||
function assertSafeArchiveName(name, label) {
|
||||
if (typeof name !== 'string' || name.length === 0) {
|
||||
throw new Error(`DBGM-00000 Invalid ${label}: must be a non-empty string`);
|
||||
}
|
||||
if (name.includes('\0') || name.includes('..') || name.includes('/') || name.includes('\\')) {
|
||||
throw new Error(`DBGM-00000 Invalid ${label}: path traversal not allowed`);
|
||||
}
|
||||
// Reject names that resolve to the archive root itself (e.g. '.')
|
||||
const resolved = path.resolve(archivedir(), name);
|
||||
if (resolved === path.resolve(archivedir())) {
|
||||
throw new Error(`DBGM-00000 Invalid ${label}: must not resolve to the archive root`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
folders_meta: true,
|
||||
async folders() {
|
||||
@@ -39,6 +59,7 @@ module.exports = {
|
||||
|
||||
createFolder_meta: true,
|
||||
async createFolder({ folder }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
await fs.mkdir(path.join(archivedir(), folder));
|
||||
socket.emitChanged('archive-folders-changed');
|
||||
return true;
|
||||
@@ -46,8 +67,12 @@ module.exports = {
|
||||
|
||||
createLink_meta: true,
|
||||
async createLink({ linkedFolder }) {
|
||||
if ( typeof linkedFolder !== 'string' || linkedFolder.length === 0) {
|
||||
throw new Error(`DBGM-00000 Invalid linkedFolder: must be a non-empty string`);
|
||||
}
|
||||
assertSafeArchiveName(path.parse(linkedFolder).name, 'linkedFolder');
|
||||
const folder = await this.getNewArchiveFolder({ database: path.parse(linkedFolder).name + '.link' });
|
||||
fs.writeFile(path.join(archivedir(), folder), linkedFolder);
|
||||
await fs.writeFile(path.join(archivedir(), folder), linkedFolder);
|
||||
clearArchiveLinksCache();
|
||||
socket.emitChanged('archive-folders-changed');
|
||||
return folder;
|
||||
@@ -71,6 +96,7 @@ module.exports = {
|
||||
|
||||
files_meta: true,
|
||||
async files({ folder }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
try {
|
||||
if (folder.endsWith('.zip')) {
|
||||
if (await fs.exists(path.join(archivedir(), folder))) {
|
||||
@@ -121,6 +147,9 @@ module.exports = {
|
||||
|
||||
createFile_meta: true,
|
||||
async createFile({ folder, file, fileType, tableInfo }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(file, 'file');
|
||||
assertSafeArchiveName(fileType, 'fileType');
|
||||
await fs.writeFile(
|
||||
path.join(resolveArchiveFolder(folder), `${file}.${fileType}`),
|
||||
tableInfo ? JSON.stringify({ __isStreamHeader: true, tableInfo }) : ''
|
||||
@@ -131,6 +160,9 @@ module.exports = {
|
||||
|
||||
deleteFile_meta: true,
|
||||
async deleteFile({ folder, file, fileType }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(file, 'file');
|
||||
assertSafeArchiveName(fileType, 'fileType');
|
||||
await fs.unlink(path.join(resolveArchiveFolder(folder), `${file}.${fileType}`));
|
||||
socket.emitChanged(`archive-files-changed`, { folder });
|
||||
return true;
|
||||
@@ -138,6 +170,10 @@ module.exports = {
|
||||
|
||||
renameFile_meta: true,
|
||||
async renameFile({ folder, file, newFile, fileType }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(file, 'file');
|
||||
assertSafeArchiveName(newFile, 'newFile');
|
||||
assertSafeArchiveName(fileType, 'fileType');
|
||||
await fs.rename(
|
||||
path.join(resolveArchiveFolder(folder), `${file}.${fileType}`),
|
||||
path.join(resolveArchiveFolder(folder), `${newFile}.${fileType}`)
|
||||
@@ -148,6 +184,8 @@ module.exports = {
|
||||
|
||||
modifyFile_meta: true,
|
||||
async modifyFile({ folder, file, changeSet, mergedRows, mergeKey, mergeMode }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(file, 'file');
|
||||
await jsldata.closeDataStore(`archive://${folder}/${file}`);
|
||||
const changedFilePath = path.join(resolveArchiveFolder(folder), `${file}.jsonl`);
|
||||
|
||||
@@ -187,6 +225,8 @@ module.exports = {
|
||||
|
||||
renameFolder_meta: true,
|
||||
async renameFolder({ folder, newFolder }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(newFolder, 'newFolder');
|
||||
const uniqueName = await this.getNewArchiveFolder({ database: newFolder });
|
||||
await fs.rename(path.join(archivedir(), folder), path.join(archivedir(), uniqueName));
|
||||
socket.emitChanged(`archive-folders-changed`);
|
||||
@@ -196,6 +236,7 @@ module.exports = {
|
||||
deleteFolder_meta: true,
|
||||
async deleteFolder({ folder }) {
|
||||
if (!folder) throw new Error('Missing folder parameter');
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
if (folder.endsWith('.link') || folder.endsWith('.zip')) {
|
||||
await fs.unlink(path.join(archivedir(), folder));
|
||||
} else {
|
||||
@@ -207,6 +248,8 @@ module.exports = {
|
||||
|
||||
saveText_meta: true,
|
||||
async saveText({ folder, file, text }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(file, 'file');
|
||||
await fs.writeFile(path.join(resolveArchiveFolder(folder), `${file}.jsonl`), text);
|
||||
socket.emitChanged(`archive-files-changed`, { folder });
|
||||
return true;
|
||||
@@ -214,6 +257,8 @@ module.exports = {
|
||||
|
||||
saveJslData_meta: true,
|
||||
async saveJslData({ folder, file, jslid, changeSet }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(file, 'file');
|
||||
const source = getJslFileName(jslid);
|
||||
const target = path.join(resolveArchiveFolder(folder), `${file}.jsonl`);
|
||||
if (changeSet) {
|
||||
@@ -232,11 +277,20 @@ module.exports = {
|
||||
|
||||
saveRows_meta: true,
|
||||
async saveRows({ folder, file, rows }) {
|
||||
const fileStream = fs.createWriteStream(path.join(resolveArchiveFolder(folder), `${file}.jsonl`));
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(file, 'file');
|
||||
const filePath = path.join(resolveArchiveFolder(folder), `${file}.jsonl`);
|
||||
const fileStream = fs.createWriteStream(filePath);
|
||||
for (const row of rows) {
|
||||
await fileStream.write(JSON.stringify(row) + '\n');
|
||||
const ok = fileStream.write(JSON.stringify(row) + '\n');
|
||||
if (!ok) {
|
||||
await new Promise(resolve => fileStream.once('drain', resolve));
|
||||
}
|
||||
}
|
||||
await fileStream.close();
|
||||
await new Promise((resolve, reject) => {
|
||||
fileStream.end(() => resolve());
|
||||
fileStream.on('error', reject);
|
||||
});
|
||||
socket.emitChanged(`archive-files-changed`, { folder });
|
||||
return true;
|
||||
},
|
||||
@@ -256,6 +310,8 @@ module.exports = {
|
||||
|
||||
getArchiveData_meta: true,
|
||||
async getArchiveData({ folder, file }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
assertSafeArchiveName(file, 'file');
|
||||
let rows;
|
||||
if (folder.endsWith('.zip')) {
|
||||
rows = await unzipJsonLinesFile(path.join(archivedir(), folder), `${file}.jsonl`);
|
||||
@@ -270,7 +326,7 @@ module.exports = {
|
||||
if (!fileName?.endsWith('.zip')) {
|
||||
throw new Error(`${fileName} is not a ZIP file`);
|
||||
}
|
||||
|
||||
assertSafeArchiveName(fileName.slice(0, -4), 'fileName');
|
||||
const folder = await this.getNewArchiveFolder({ database: fileName });
|
||||
await fs.copyFile(filePath, path.join(archivedir(), folder));
|
||||
socket.emitChanged(`archive-folders-changed`);
|
||||
@@ -280,6 +336,7 @@ module.exports = {
|
||||
|
||||
zip_meta: true,
|
||||
async zip({ folder }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
const newFolder = await this.getNewArchiveFolder({ database: folder + '.zip' });
|
||||
await zipDirectory(path.join(archivedir(), folder), path.join(archivedir(), newFolder));
|
||||
socket.emitChanged(`archive-folders-changed`);
|
||||
@@ -289,6 +346,7 @@ module.exports = {
|
||||
|
||||
unzip_meta: true,
|
||||
async unzip({ folder }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
const newFolder = await this.getNewArchiveFolder({ database: folder.slice(0, -4) });
|
||||
await unzipDirectory(path.join(archivedir(), folder), path.join(archivedir(), newFolder));
|
||||
socket.emitChanged(`archive-folders-changed`);
|
||||
@@ -298,6 +356,7 @@ module.exports = {
|
||||
|
||||
getZippedPath_meta: true,
|
||||
async getZippedPath({ folder }) {
|
||||
assertSafeArchiveName(folder, 'folder');
|
||||
if (folder.endsWith('.zip')) {
|
||||
return { filePath: path.join(archivedir(), folder) };
|
||||
}
|
||||
|
||||
@@ -33,19 +33,35 @@ function readCore(reader, skip, limit, filter) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
read_meta: true,
|
||||
async read({ skip, limit, filter }) {
|
||||
function readJsonl({ skip, limit, filter }) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const fileName = path.join(datadir(), 'query-history.jsonl');
|
||||
// @ts-ignore
|
||||
if (!(await fs.exists(fileName))) return [];
|
||||
if (!(await fs.exists(fileName))) return resolve([]);
|
||||
const reader = fsReverse(fileName);
|
||||
const res = await readCore(reader, skip, limit, filter);
|
||||
return res;
|
||||
resolve(res);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
read_meta: true,
|
||||
async read({ skip, limit, filter }, req) {
|
||||
const storage = require('./storage');
|
||||
const storageResult = await storage.readQueryHistory({ skip, limit, filter }, req);
|
||||
if (storageResult) return storageResult;
|
||||
return readJsonl({ skip, limit, filter });
|
||||
},
|
||||
|
||||
write_meta: true,
|
||||
async write({ data }) {
|
||||
async write({ data }, req) {
|
||||
const storage = require('./storage');
|
||||
const written = await storage.writeQueryHistory({ data }, req);
|
||||
if (written) {
|
||||
socket.emit('query-history-changed');
|
||||
return 'OK';
|
||||
}
|
||||
|
||||
const fileName = path.join(datadir(), 'query-history.jsonl');
|
||||
await fs.appendFile(fileName, JSON.stringify(data) + '\n');
|
||||
socket.emit('query-history-changed');
|
||||
|
||||
@@ -10,6 +10,7 @@ const {
|
||||
extractShellApiPlugins,
|
||||
compileShellApiFunctionName,
|
||||
jsonScriptToJavascript,
|
||||
assertValidShellApiFunctionName,
|
||||
getLogger,
|
||||
safeJsonParse,
|
||||
pinoLogRecordToMessageRecord,
|
||||
@@ -54,19 +55,23 @@ logger.info('DBGM-00014 Finished job script');
|
||||
dbgateApi.runScript(run);
|
||||
`;
|
||||
|
||||
const loaderScriptTemplate = (prefix, functionName, props, runid) => `
|
||||
const loaderScriptTemplate = (functionName, props, runid) => {
|
||||
const plugins = extractShellApiPlugins(functionName, props);
|
||||
const prefix = plugins.map(packageName => `// @require ${packageName}\n`).join('');
|
||||
return `
|
||||
${prefix}
|
||||
const dbgateApi = require(process.env.DBGATE_API);
|
||||
dbgateApi.initializeApiEnvironment();
|
||||
${requirePluginsTemplate(extractShellApiPlugins(functionName, props))}
|
||||
${requirePluginsTemplate(plugins)}
|
||||
require=null;
|
||||
async function run() {
|
||||
const reader=await ${compileShellApiFunctionName(functionName)}(${JSON.stringify(props)});
|
||||
const writer=await dbgateApi.collectorWriter({runid: '${runid}'});
|
||||
const writer=await dbgateApi.collectorWriter({runid: ${JSON.stringify(runid)}});
|
||||
await dbgateApi.copyStream(reader, writer);
|
||||
}
|
||||
dbgateApi.runScript(run);
|
||||
`;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
/** @type {import('dbgate-types').OpenedRunner[]} */
|
||||
@@ -377,14 +382,12 @@ module.exports = {
|
||||
return { errorMessage: 'DBGM-00289 Unallowed file' };
|
||||
}
|
||||
}
|
||||
const prefix = extractShellApiPlugins(functionName)
|
||||
.map(packageName => `// @require ${packageName}\n`)
|
||||
.join('');
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
assertValidShellApiFunctionName(functionName);
|
||||
const runid = crypto.randomUUID();
|
||||
this.requests[runid] = { resolve, reject, exitOnStreamError: true };
|
||||
this.startCore(runid, loaderScriptTemplate(prefix, functionName, props, runid));
|
||||
this.startCore(runid, loaderScriptTemplate(functionName, props, runid));
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
@@ -16,23 +16,53 @@ function unzipDirectory(zipPath, outputDirectory) {
|
||||
return new Promise((resolve, reject) => {
|
||||
yauzl.open(zipPath, { lazyEntries: true }, (err, zipFile) => {
|
||||
if (err) return reject(err);
|
||||
|
||||
let settled = false;
|
||||
/** Track active streams so we can destroy them on early abort */
|
||||
const activeStreams = new Set();
|
||||
const safeReject = rejectErr => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
for (const s of activeStreams) {
|
||||
s.destroy();
|
||||
}
|
||||
activeStreams.clear();
|
||||
zipFile.close();
|
||||
reject(rejectErr);
|
||||
};
|
||||
/** Pending per-file extractions – we resolve the main promise after they’re all done */
|
||||
const pending = [];
|
||||
|
||||
// Resolved output boundary used for zip-slip checks on every entry
|
||||
const resolvedOutputDir = path.resolve(outputDirectory);
|
||||
|
||||
// kick things off
|
||||
zipFile.readEntry();
|
||||
|
||||
zipFile.on('entry', entry => {
|
||||
// Null-byte poison check
|
||||
if (entry.fileName.includes('\0')) {
|
||||
return safeReject(new Error(`DBGM-00000 ZIP entry with null byte in filename rejected`));
|
||||
}
|
||||
|
||||
const destPath = path.join(outputDirectory, entry.fileName);
|
||||
const resolvedDest = path.resolve(destPath);
|
||||
|
||||
// Zip-slip protection: every extracted path must stay inside outputDirectory
|
||||
if (resolvedDest !== resolvedOutputDir && !resolvedDest.startsWith(resolvedOutputDir + path.sep)) {
|
||||
return safeReject(
|
||||
new Error(`DBGM-00000 ZIP slip detected: entry "${entry.fileName}" would escape output directory`)
|
||||
);
|
||||
}
|
||||
|
||||
// Handle directories (their names always end with “/” in ZIPs)
|
||||
if (/\/$/.test(entry.fileName)) {
|
||||
// Ensure directory exists, then continue to next entry
|
||||
fs.promises
|
||||
.mkdir(destPath, { recursive: true })
|
||||
.then(() => zipFile.readEntry())
|
||||
.catch(reject);
|
||||
.then(() => {
|
||||
if (!settled) zipFile.readEntry();
|
||||
})
|
||||
.catch(safeReject);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -46,17 +76,29 @@ function unzipDirectory(zipPath, outputDirectory) {
|
||||
if (err) return rej(err);
|
||||
|
||||
const writeStream = fs.createWriteStream(destPath);
|
||||
activeStreams.add(readStream);
|
||||
activeStreams.add(writeStream);
|
||||
readStream.pipe(writeStream);
|
||||
|
||||
// proceed to next entry once we’ve consumed *this* one
|
||||
readStream.on('end', () => zipFile.readEntry());
|
||||
// proceed to next entry once we've consumed *this* one
|
||||
readStream.on('end', () => {
|
||||
activeStreams.delete(readStream);
|
||||
if (!settled) zipFile.readEntry();
|
||||
});
|
||||
|
||||
readStream.on('error', readErr => {
|
||||
activeStreams.delete(readStream);
|
||||
rej(readErr);
|
||||
});
|
||||
|
||||
writeStream.on('finish', () => {
|
||||
activeStreams.delete(writeStream);
|
||||
logger.info(`DBGM-00068 Extracted "${entry.fileName}" → "${destPath}".`);
|
||||
res();
|
||||
});
|
||||
|
||||
writeStream.on('error', writeErr => {
|
||||
activeStreams.delete(writeStream);
|
||||
logger.error(
|
||||
extractErrorLogData(writeErr),
|
||||
`DBGM-00069 Error extracting "${entry.fileName}" from "${zipPath}".`
|
||||
@@ -67,22 +109,29 @@ function unzipDirectory(zipPath, outputDirectory) {
|
||||
})
|
||||
);
|
||||
|
||||
// Immediately abort the whole unzip if this file fails; otherwise the
|
||||
// zip would never emit 'end' (lazyEntries won't advance without readEntry).
|
||||
filePromise.catch(safeReject);
|
||||
pending.push(filePromise);
|
||||
});
|
||||
|
||||
// Entire archive enumerated; wait for all streams to finish
|
||||
zipFile.on('end', () => {
|
||||
if (settled) return;
|
||||
Promise.all(pending)
|
||||
.then(() => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
zipFile.close();
|
||||
logger.info(`DBGM-00070 Archive "${zipPath}" fully extracted to "${outputDirectory}".`);
|
||||
resolve(true);
|
||||
})
|
||||
.catch(reject);
|
||||
.catch(safeReject);
|
||||
});
|
||||
|
||||
zipFile.on('error', err => {
|
||||
logger.error(extractErrorLogData(err), `DBGM-00071 ZIP file error in ${zipPath}.`);
|
||||
reject(err);
|
||||
safeReject(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -874,6 +874,114 @@ module.exports = {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"pureName": "query_history",
|
||||
"columns": [
|
||||
{
|
||||
"pureName": "query_history",
|
||||
"columnName": "id",
|
||||
"dataType": "int",
|
||||
"autoIncrement": true,
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"pureName": "query_history",
|
||||
"columnName": "created",
|
||||
"dataType": "bigint",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"pureName": "query_history",
|
||||
"columnName": "user_id",
|
||||
"dataType": "int",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"pureName": "query_history",
|
||||
"columnName": "role_id",
|
||||
"dataType": "int",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"pureName": "query_history",
|
||||
"columnName": "sql",
|
||||
"dataType": "text",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"pureName": "query_history",
|
||||
"columnName": "conid",
|
||||
"dataType": "varchar(100)",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"pureName": "query_history",
|
||||
"columnName": "database",
|
||||
"dataType": "varchar(200)",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"constraintType": "foreignKey",
|
||||
"constraintName": "FK_query_history_user_id",
|
||||
"pureName": "query_history",
|
||||
"refTableName": "users",
|
||||
"deleteAction": "CASCADE",
|
||||
"columns": [
|
||||
{
|
||||
"columnName": "user_id",
|
||||
"refColumnName": "id"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"constraintType": "foreignKey",
|
||||
"constraintName": "FK_query_history_role_id",
|
||||
"pureName": "query_history",
|
||||
"refTableName": "roles",
|
||||
"deleteAction": "CASCADE",
|
||||
"columns": [
|
||||
{
|
||||
"columnName": "role_id",
|
||||
"refColumnName": "id"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"constraintName": "idx_query_history_user_id",
|
||||
"pureName": "query_history",
|
||||
"constraintType": "index",
|
||||
"columns": [
|
||||
{
|
||||
"columnName": "user_id"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"constraintName": "idx_query_history_role_id",
|
||||
"pureName": "query_history",
|
||||
"constraintType": "index",
|
||||
"columns": [
|
||||
{
|
||||
"columnName": "role_id"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"pureName": "query_history",
|
||||
"constraintType": "primaryKey",
|
||||
"constraintName": "PK_query_history",
|
||||
"columns": [
|
||||
{
|
||||
"columnName": "id"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"pureName": "roles",
|
||||
"columns": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import _uniq from 'lodash/uniq';
|
||||
import _cloneDeepWith from 'lodash/cloneDeepWith';
|
||||
import { evalShellApiFunctionName, compileShellApiFunctionName, extractShellApiPlugins } from './packageTools';
|
||||
import { evalShellApiFunctionName, compileShellApiFunctionName, extractShellApiPlugins, assertValidJsIdentifier, assertValidShellApiFunctionName } from './packageTools';
|
||||
|
||||
export interface ScriptWriterGeneric {
|
||||
allocVariable(prefix?: string);
|
||||
@@ -40,6 +40,7 @@ export class ScriptWriterJavaScript implements ScriptWriterGeneric {
|
||||
}
|
||||
|
||||
assignCore(variableName, functionName, props) {
|
||||
assertValidJsIdentifier(variableName, 'variableName');
|
||||
this._put(`const ${variableName} = await ${functionName}(${JSON.stringify(props)});`);
|
||||
}
|
||||
|
||||
@@ -49,6 +50,7 @@ export class ScriptWriterJavaScript implements ScriptWriterGeneric {
|
||||
}
|
||||
|
||||
assignValue(variableName, jsonValue) {
|
||||
assertValidJsIdentifier(variableName, 'variableName');
|
||||
this._put(`const ${variableName} = ${JSON.stringify(jsonValue)};`);
|
||||
}
|
||||
|
||||
@@ -57,8 +59,13 @@ export class ScriptWriterJavaScript implements ScriptWriterGeneric {
|
||||
}
|
||||
|
||||
copyStream(sourceVar, targetVar, colmapVar = null, progressName?: string | { name: string; runid: string }) {
|
||||
assertValidJsIdentifier(sourceVar, 'sourceVar');
|
||||
assertValidJsIdentifier(targetVar, 'targetVar');
|
||||
let opts = '{';
|
||||
if (colmapVar) opts += `columns: ${colmapVar}, `;
|
||||
if (colmapVar) {
|
||||
assertValidJsIdentifier(colmapVar, 'colmapVar');
|
||||
opts += `columns: ${colmapVar}, `;
|
||||
}
|
||||
if (progressName) opts += `progressName: ${JSON.stringify(progressName)}, `;
|
||||
opts += '}';
|
||||
|
||||
@@ -89,7 +96,7 @@ export class ScriptWriterJavaScript implements ScriptWriterGeneric {
|
||||
}
|
||||
|
||||
zipDirectory(inputDirectory, outputFile) {
|
||||
this._put(`await dbgateApi.zipDirectory('${inputDirectory}', '${outputFile}');`);
|
||||
this._put(`await dbgateApi.zipDirectory(${JSON.stringify(inputDirectory)}, ${JSON.stringify(outputFile)});`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +221,8 @@ export class ScriptWriterEval implements ScriptWriterGeneric {
|
||||
requirePackage(packageName) {}
|
||||
|
||||
async assign(variableName, functionName, props) {
|
||||
assertValidJsIdentifier(variableName, 'variableName');
|
||||
assertValidShellApiFunctionName(functionName);
|
||||
const func = evalShellApiFunctionName(functionName, this.dbgateApi, this.requirePlugin);
|
||||
|
||||
this.variables[variableName] = await func(
|
||||
@@ -226,10 +235,14 @@ export class ScriptWriterEval implements ScriptWriterGeneric {
|
||||
}
|
||||
|
||||
assignValue(variableName, jsonValue) {
|
||||
assertValidJsIdentifier(variableName, 'variableName');
|
||||
this.variables[variableName] = jsonValue;
|
||||
}
|
||||
|
||||
async copyStream(sourceVar, targetVar, colmapVar = null, progressName?: string | { name: string; runid: string }) {
|
||||
assertValidJsIdentifier(sourceVar, 'sourceVar');
|
||||
assertValidJsIdentifier(targetVar, 'targetVar');
|
||||
if (colmapVar != null) assertValidJsIdentifier(colmapVar, 'colmapVar');
|
||||
await this.dbgateApi.copyStream(this.variables[sourceVar], this.variables[targetVar], {
|
||||
progressName: _cloneDeepWith(progressName, node => {
|
||||
if (node?.$runid) {
|
||||
|
||||
@@ -3,6 +3,64 @@ import _camelCase from 'lodash/camelCase';
|
||||
import _isString from 'lodash/isString';
|
||||
import _isPlainObject from 'lodash/isPlainObject';
|
||||
|
||||
const JS_IDENTIFIER_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
||||
|
||||
// ECMAScript reserved words, strict-mode keywords, and async-context keywords
|
||||
// that cannot be used as variable or function names in the generated scripts.
|
||||
// Sources: ECMA-262 §12.7.2 (reserved words), §12.7.3 (strict mode), §14 (contextual).
|
||||
const JS_RESERVED_WORDS = new Set([
|
||||
// Keywords
|
||||
'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default',
|
||||
'delete', 'do', 'else', 'export', 'extends', 'false', 'finally', 'for',
|
||||
'function', 'if', 'import', 'in', 'instanceof', 'let', 'new', 'null', 'return',
|
||||
'static', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'var',
|
||||
'void', 'while', 'with', 'yield',
|
||||
// Strict-mode reserved words
|
||||
'implements', 'interface', 'package', 'private', 'protected', 'public',
|
||||
// Async context keywords
|
||||
'async', 'await',
|
||||
// Future reserved
|
||||
'enum',
|
||||
'eval', 'arguments',
|
||||
]);
|
||||
|
||||
export function isValidJsIdentifier(name: string): boolean {
|
||||
return typeof name === 'string' && JS_IDENTIFIER_RE.test(name) && !JS_RESERVED_WORDS.has(name);
|
||||
}
|
||||
|
||||
export function assertValidJsIdentifier(name: string, label: string): void {
|
||||
if (!isValidJsIdentifier(name)) {
|
||||
throw new Error(`DBGM-00000 Invalid ${label}: ${String(name).substring(0, 100)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a shell API function name.
|
||||
* Allowed forms:
|
||||
* - "someFunctionName" (plain identifier, resolved as dbgateApi.someFunctionName)
|
||||
* - "funcName@dbgate-plugin-xxx" (namespaced, resolved as plugin.shellApi.funcName)
|
||||
*/
|
||||
export function assertValidShellApiFunctionName(functionName: string): void {
|
||||
if (typeof functionName !== 'string') {
|
||||
throw new Error('DBGM-00000 functionName must be a string');
|
||||
}
|
||||
const nsMatch = functionName.match(/^([^@]+)@([^@]+)$/);
|
||||
if (nsMatch) {
|
||||
if (!isValidJsIdentifier(nsMatch[1])) {
|
||||
throw new Error(`DBGM-00000 Invalid function part in functionName: ${nsMatch[1].substring(0, 100)}`);
|
||||
}
|
||||
if (!/^dbgate-plugin-[a-zA-Z0-9_-]+$/.test(nsMatch[2])) {
|
||||
throw new Error(`DBGM-00000 Invalid plugin package in functionName: ${nsMatch[2].substring(0, 100)}`);
|
||||
}
|
||||
} else {
|
||||
if (!isValidJsIdentifier(functionName)) {
|
||||
throw new Error(`DBGM-00000 Invalid functionName: ${functionName.substring(0, 100)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const VALID_PLUGIN_NAME_RE = /^dbgate-plugin-[a-zA-Z0-9_-]+$/;
|
||||
|
||||
export function extractShellApiPlugins(functionName, props): string[] {
|
||||
const res = [];
|
||||
const nsMatch = functionName.match(/^([^@]+)@([^@]+)/);
|
||||
@@ -15,6 +73,11 @@ export function extractShellApiPlugins(functionName, props): string[] {
|
||||
res.push(nsMatchEngine[2]);
|
||||
}
|
||||
}
|
||||
for (const plugin of res) {
|
||||
if (!VALID_PLUGIN_NAME_RE.test(plugin)) {
|
||||
throw new Error(`DBGM-00000 Invalid plugin name: ${String(plugin).substring(0, 100)}`);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -28,7 +91,8 @@ export function extractPackageName(name): string {
|
||||
}
|
||||
|
||||
export function compileShellApiFunctionName(functionName) {
|
||||
const nsMatch = functionName.match(/^([^@]+)@([^@]+)/);
|
||||
assertValidShellApiFunctionName(functionName);
|
||||
const nsMatch = functionName.match(/^([^@]+)@([^@]+)$/);
|
||||
if (nsMatch) {
|
||||
return `${_camelCase(nsMatch[2])}.shellApi.${nsMatch[1]}`;
|
||||
}
|
||||
@@ -36,7 +100,8 @@ export function compileShellApiFunctionName(functionName) {
|
||||
}
|
||||
|
||||
export function evalShellApiFunctionName(functionName, dbgateApi, requirePlugin) {
|
||||
const nsMatch = functionName.match(/^([^@]+)@([^@]+)/);
|
||||
assertValidShellApiFunctionName(functionName);
|
||||
const nsMatch = functionName.match(/^([^@]+)@([^@]+)$/);
|
||||
if (nsMatch) {
|
||||
return requirePlugin(nsMatch[2]).shellApi[nsMatch[1]];
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -46,7 +46,7 @@ import { isProApp } from '../utility/proTools';
|
||||
import { openWebLink } from '../utility/simpleTools';
|
||||
import { _t } from '../translations';
|
||||
import ExportImportConnectionsModal from '../modals/ExportImportConnectionsModal.svelte';
|
||||
import { getBoolSettingsValue } from '../settings/settingsTools';
|
||||
import { getBoolSettingsValue, isAiDisabled } from '../settings/settingsTools';
|
||||
import { __t } from '../translations';
|
||||
|
||||
// function themeCommand(theme: ThemeDefinition) {
|
||||
@@ -753,7 +753,8 @@ if (isProApp()) {
|
||||
testEnabled: () =>
|
||||
getCurrentDatabase() != null &&
|
||||
findEngineDriver(getCurrentDatabase()?.connection, getExtensions())?.databaseEngineTypes?.includes('sql') &&
|
||||
hasPermission('dbops/chat'),
|
||||
hasPermission('dbops/chat') &&
|
||||
!isAiDisabled(),
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
title: 'Chat',
|
||||
@@ -776,7 +777,8 @@ if (isProApp()) {
|
||||
testEnabled: () =>
|
||||
getCurrentDatabase() != null &&
|
||||
findEngineDriver(getCurrentDatabase()?.connection, getExtensions())?.databaseEngineTypes?.includes('graphql') &&
|
||||
hasPermission('dbops/chat'),
|
||||
hasPermission('dbops/chat') &&
|
||||
!isAiDisabled(),
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
title: 'GraphQL Chat',
|
||||
|
||||
@@ -36,6 +36,10 @@ export function getObjectSettingsValue(name, defaultValue) {
|
||||
return res;
|
||||
}
|
||||
|
||||
export function isAiDisabled(): boolean {
|
||||
return getBoolSettingsValue('storage.disableAiFeatures', false);
|
||||
}
|
||||
|
||||
export function getConnectionClickActionSetting(): 'connect' | 'openDetails' | 'none' {
|
||||
return getStringSettingsValue('defaultAction.connectionClick', 'connect');
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
name: __t('command.query.AiAssistant', { defaultMessage: 'AI Assistant' }),
|
||||
keyText: 'Shift+Alt+A',
|
||||
icon: 'icon ai',
|
||||
testEnabled: () => isProApp(),
|
||||
testEnabled: () => isProApp() && !isAiDisabled(),
|
||||
onClick: () => getCurrentEditor().toggleAiAssistant(),
|
||||
});
|
||||
registerCommand({
|
||||
@@ -164,7 +164,7 @@
|
||||
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
|
||||
import uuidv1 from 'uuid/v1';
|
||||
import ToolStripButton from '../buttons/ToolStripButton.svelte';
|
||||
import { getIntSettingsValue } from '../settings/settingsTools';
|
||||
import { getIntSettingsValue, isAiDisabled } from '../settings/settingsTools';
|
||||
import RowsLimitModal from '../modals/RowsLimitModal.svelte';
|
||||
import _ from 'lodash';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
@@ -197,19 +197,19 @@
|
||||
},
|
||||
{
|
||||
value: '@',
|
||||
text: _t('query.variable', { defaultMessage: '@variable' }),
|
||||
text: '@' + _t('query.variable', { defaultMessage: 'variable' }),
|
||||
},
|
||||
{
|
||||
value: ':',
|
||||
text: _t('query.named', { defaultMessage: ':variable' }),
|
||||
text: ':' + _t('query.variable', { defaultMessage: 'variable' }),
|
||||
},
|
||||
{
|
||||
value: '$',
|
||||
text: _t('query.variable', { defaultMessage: '$variable' }),
|
||||
text: '$' + _t('query.variable', { defaultMessage: 'variable' }),
|
||||
},
|
||||
{
|
||||
value: '#',
|
||||
text: _t('query.variable', { defaultMessage: '#variable' }),
|
||||
text: '#' + _t('query.variable', { defaultMessage: 'variable' }),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -253,6 +253,10 @@
|
||||
let isAiAssistantVisible = isProApp() && localStorage.getItem(`tabdata_isAiAssistantVisible_${tabid}`) == 'true';
|
||||
let domAiAssistant;
|
||||
|
||||
$: if ($settingsValue?.['storage.disableAiFeatures']) {
|
||||
isAiAssistantVisible = false;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
intervalId = setInterval(() => {
|
||||
if (!driver?.singleConnectionOnly && sessionId) {
|
||||
@@ -619,7 +623,7 @@
|
||||
}
|
||||
|
||||
async function handleExplainError(errorObject) {
|
||||
if (!isProApp()) return;
|
||||
if (!isProApp() || isAiDisabled()) return;
|
||||
isAiAssistantVisible = true;
|
||||
await tick();
|
||||
domAiAssistant?.explainError({
|
||||
|
||||
@@ -20,8 +20,11 @@
|
||||
import SQLEditorSettings from '../settings/SQLEditorSettings.svelte';
|
||||
import AiSettingsTab from '../settings/AiSettingsTab.svelte';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
import { useSettings } from '../utility/metadataLoaders';
|
||||
import { openedTabs } from '../stores';
|
||||
|
||||
const settings = useSettings();
|
||||
|
||||
export let selectedItem = 'general';
|
||||
export let tabid = null;
|
||||
|
||||
@@ -33,7 +36,7 @@
|
||||
);
|
||||
}
|
||||
|
||||
const menuItems = [
|
||||
$: menuItems = [
|
||||
{
|
||||
label: _t('settings.general', { defaultMessage: 'General' }),
|
||||
identifier: 'general',
|
||||
@@ -113,7 +116,8 @@
|
||||
testid: 'settings-license',
|
||||
},
|
||||
hasPermission('settings/change') &&
|
||||
isProApp() && {
|
||||
isProApp() &&
|
||||
!$settings?.['storage.disableAiFeatures'] && {
|
||||
label: _t('settings.AI', { defaultMessage: 'AI' }),
|
||||
identifier: 'ai',
|
||||
component: AiSettingsTab,
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
import { rightPanelWidget } from '../stores';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
import { useSettings } from '../utility/metadataLoaders';
|
||||
import ThemeAiAssistantWidget from '../ai/ThemeAiAssistantWidget.svelte';
|
||||
|
||||
const settings = useSettings();
|
||||
</script>
|
||||
|
||||
{#if $rightPanelWidget == 'themeAiAssistant' && hasPermission('widgets/themeAiAssistant') && isProApp()}
|
||||
{#if $rightPanelWidget == 'themeAiAssistant' && hasPermission('widgets/themeAiAssistant') && isProApp() && !$settings?.['storage.disableAiFeatures']}
|
||||
<ThemeAiAssistantWidget />
|
||||
{/if}
|
||||
|
||||
@@ -207,6 +207,8 @@
|
||||
"command.datagrid.editCell": "Upravit hodnotu buňky",
|
||||
"command.datagrid.editJsonDocument": "Upravit řádek jako JSON dokument",
|
||||
"command.datagrid.editSelection": "Upravit výběr jako tabulku",
|
||||
"command.datagrid.fetchAll": "Načíst všechny řádky",
|
||||
"command.datagrid.fetchAll.toolbar": "Načíst vše",
|
||||
"command.datagrid.filterSelected": "Filtrovat vybranou hodnotu",
|
||||
"command.datagrid.findColumn": "Najít sloupec",
|
||||
"command.datagrid.generateSql": "Generovat SQL",
|
||||
@@ -723,6 +725,11 @@
|
||||
"datagrid.columnName": "Název sloupce",
|
||||
"datagrid.columnNameFilter": "Filtr názvu sloupce",
|
||||
"datagrid.copyAdvanced": "Pokročilé kopírování",
|
||||
"datagrid.fetchAll.confirm": "Načíst vše",
|
||||
"datagrid.fetchAll.progress": "Načítání všech řádků... načteno {count}",
|
||||
"datagrid.fetchAll.progressDb": "Načítání dat z databáze...",
|
||||
"datagrid.fetchAll.title": "Načíst všechny řádky",
|
||||
"datagrid.fetchAll.warning": "Tímto se načtou všechny zbývající řádky do paměti. U velkých tabulek to může spotřebovat významné množství paměti a ovlivnit výkon aplikace.",
|
||||
"datagrid.macros.calculation": "Výpočet",
|
||||
"datagrid.macros.calculationDescription": "Vlastní výraz. Použijte řádek.název_sloupce pro přístup k hodnotám sloupců, value pro původní hodnotu",
|
||||
"datagrid.macros.changeTextCase": "Změnit velikost písmen",
|
||||
@@ -1238,7 +1245,6 @@
|
||||
"query.groupFilter": "Filtr skupiny",
|
||||
"query.isolationLevel": "Úroveň izolace",
|
||||
"query.limitRows": "Omezit na {queryRowsLimit} řádků",
|
||||
"query.named": ":proměnná",
|
||||
"query.noParameters": "(žádné parametry)",
|
||||
"query.noRowsLimit": "(bez limitu řádků)",
|
||||
"query.orFilter": "NEBO filtr {number}",
|
||||
@@ -1256,7 +1262,7 @@
|
||||
"query.sortOrder": "Řazení",
|
||||
"query.table": "Tabulka",
|
||||
"query.unlimitedRows": "Neomezený počet řádků",
|
||||
"query.variable": "#proměnná",
|
||||
"query.variable": "proměnná",
|
||||
"queryParameters.editQueryParameters": "Upravit parametry dotazu",
|
||||
"queryParameters.runQuery": "Spustit dotaz",
|
||||
"queryParameters.stringValuesMustBeQuoted": "Řetězcové hodnoty musí být 'v uvozovkách'. Můžete použít platné SQL výrazy.",
|
||||
@@ -1333,6 +1339,7 @@
|
||||
"settings.confirmations": "Potvrzení",
|
||||
"settings.confirmations.skipConfirm.collectionDataSave": "Přeskočit potvrzení při ukládání údajů kolekce (NoSQL)",
|
||||
"settings.confirmations.skipConfirm.tableDataSave": "Přeskočit potvrzení při ukládání údajů tabulky (SQL)",
|
||||
"settings.confirmations.skipFetchAllConfirm": "Přeskočit potvrzení při načítání všech řádků",
|
||||
"settings.connection": "Připojení",
|
||||
"settings.connection.autoRefresh": "Automatické obnovení modelu databáze na pozadí",
|
||||
"settings.connection.autoRefreshInterval": "Interval mezi automatickým načítáním struktury DB v sekundách",
|
||||
|
||||
@@ -207,6 +207,8 @@
|
||||
"command.datagrid.editCell": "Zellwert bearbeiten",
|
||||
"command.datagrid.editJsonDocument": "Zeile als JSON-Dokument bearbeiten",
|
||||
"command.datagrid.editSelection": "Auswahl als Tabelle bearbeiten",
|
||||
"command.datagrid.fetchAll": "Alle Zeilen laden",
|
||||
"command.datagrid.fetchAll.toolbar": "Alle laden",
|
||||
"command.datagrid.filterSelected": "Ausgewählten Wert filtern",
|
||||
"command.datagrid.findColumn": "Spalte finden",
|
||||
"command.datagrid.generateSql": "SQL generieren",
|
||||
@@ -723,6 +725,11 @@
|
||||
"datagrid.columnName": "Spaltenname",
|
||||
"datagrid.columnNameFilter": "Spaltenname-Filter",
|
||||
"datagrid.copyAdvanced": "Erweitert kopieren",
|
||||
"datagrid.fetchAll.confirm": "Alle laden",
|
||||
"datagrid.fetchAll.progress": "Alle Zeilen werden geladen... {count} geladen",
|
||||
"datagrid.fetchAll.progressDb": "Daten werden aus der Datenbank geladen...",
|
||||
"datagrid.fetchAll.title": "Alle Zeilen laden",
|
||||
"datagrid.fetchAll.warning": "Dadurch werden alle verbleibenden Zeilen in den Speicher geladen. Bei großen Tabellen kann dies erheblichen Speicher verbrauchen und die Anwendungsleistung beeinträchtigen.",
|
||||
"datagrid.macros.calculation": "Berechnung",
|
||||
"datagrid.macros.calculationDescription": "Benutzerdefinierter Ausdruck. Verwenden Sie row.spaltenname für den Zugriff auf Spaltenwerte, value für den ursprünglichen Wert",
|
||||
"datagrid.macros.changeTextCase": "Textgroß-/Kleinschreibung ändern",
|
||||
@@ -1238,7 +1245,6 @@
|
||||
"query.groupFilter": "Gruppenfilter",
|
||||
"query.isolationLevel": "Isolationsstufe",
|
||||
"query.limitRows": "Auf {queryRowsLimit} Zeilen begrenzen",
|
||||
"query.named": ":Variable",
|
||||
"query.noParameters": "(keine Parameter)",
|
||||
"query.noRowsLimit": "(Keine Zeilenbegrenzung)",
|
||||
"query.orFilter": "ODER-Filter {number}",
|
||||
@@ -1256,7 +1262,7 @@
|
||||
"query.sortOrder": "Sortierreihenfolge",
|
||||
"query.table": "Tabelle",
|
||||
"query.unlimitedRows": "Unbegrenzte Zeilen",
|
||||
"query.variable": "#Variable",
|
||||
"query.variable": "Variable",
|
||||
"queryParameters.editQueryParameters": "Abfrageparameter bearbeiten",
|
||||
"queryParameters.runQuery": "Abfrage ausführen",
|
||||
"queryParameters.stringValuesMustBeQuoted": "Zeichenkettenwerte müssen in 'Anführungszeichen' stehen. Sie können gültige SQL-Ausdrücke verwenden.",
|
||||
@@ -1333,6 +1339,7 @@
|
||||
"settings.confirmations": "Bestätigungen",
|
||||
"settings.confirmations.skipConfirm.collectionDataSave": "Bestätigung beim Speichern von Sammlungsdaten überspringen (NoSQL)",
|
||||
"settings.confirmations.skipConfirm.tableDataSave": "Bestätigung beim Speichern von Tabellendaten überspringen (SQL)",
|
||||
"settings.confirmations.skipFetchAllConfirm": "Bestätigung beim Laden aller Zeilen überspringen",
|
||||
"settings.connection": "Verbindung",
|
||||
"settings.connection.autoRefresh": "Automatische Aktualisierung des Datenbankmodells im Hintergrund",
|
||||
"settings.connection.autoRefreshInterval": "Intervall zwischen automatischen DB-Strukturaktualisierungen in Sekunden",
|
||||
|
||||
@@ -207,6 +207,8 @@
|
||||
"command.datagrid.editCell": "Edit cell value",
|
||||
"command.datagrid.editJsonDocument": "Edit row as JSON document",
|
||||
"command.datagrid.editSelection": "Edit selection as table",
|
||||
"command.datagrid.fetchAll": "Fetch all rows",
|
||||
"command.datagrid.fetchAll.toolbar": "Fetch all",
|
||||
"command.datagrid.filterSelected": "Filter selected value",
|
||||
"command.datagrid.findColumn": "Find column",
|
||||
"command.datagrid.generateSql": "Generate SQL",
|
||||
@@ -723,6 +725,11 @@
|
||||
"datagrid.columnName": "Column name",
|
||||
"datagrid.columnNameFilter": "Column name filter",
|
||||
"datagrid.copyAdvanced": "Copy advanced",
|
||||
"datagrid.fetchAll.confirm": "Fetch All",
|
||||
"datagrid.fetchAll.progress": "Fetching all rows... {count} loaded",
|
||||
"datagrid.fetchAll.progressDb": "Fetching data from database...",
|
||||
"datagrid.fetchAll.title": "Fetch All Rows",
|
||||
"datagrid.fetchAll.warning": "This will load all remaining rows into memory. For large tables, this may consume a significant amount of memory and could affect application performance.",
|
||||
"datagrid.macros.calculation": "Calculation",
|
||||
"datagrid.macros.calculationDescription": "Custom expression. Use row.column_name for accessing column values, value for original value",
|
||||
"datagrid.macros.changeTextCase": "Change text case",
|
||||
@@ -1238,7 +1245,6 @@
|
||||
"query.groupFilter": "Group filter",
|
||||
"query.isolationLevel": "Isolation level",
|
||||
"query.limitRows": "Limit {queryRowsLimit} rows",
|
||||
"query.named": ":variable",
|
||||
"query.noParameters": "(no parameters)",
|
||||
"query.noRowsLimit": "(No rows limit)",
|
||||
"query.orFilter": "OR Filter {number}",
|
||||
@@ -1256,7 +1262,7 @@
|
||||
"query.sortOrder": "Sort order",
|
||||
"query.table": "Table",
|
||||
"query.unlimitedRows": "Unlimited rows",
|
||||
"query.variable": "#variable",
|
||||
"query.variable": "variable",
|
||||
"queryParameters.editQueryParameters": "Edit query parameters",
|
||||
"queryParameters.runQuery": "Run query",
|
||||
"queryParameters.stringValuesMustBeQuoted": "String values must be 'quoted'. You can use valid SQL expressions.",
|
||||
@@ -1333,6 +1339,7 @@
|
||||
"settings.confirmations": "Confirmations",
|
||||
"settings.confirmations.skipConfirm.collectionDataSave": "Skip confirmation when saving collection data (NoSQL)",
|
||||
"settings.confirmations.skipConfirm.tableDataSave": "Skip confirmation when saving table data (SQL)",
|
||||
"settings.confirmations.skipFetchAllConfirm": "Skip confirmation when fetching all rows",
|
||||
"settings.connection": "Connection",
|
||||
"settings.connection.autoRefresh": "Automatic refresh of database model on background",
|
||||
"settings.connection.autoRefreshInterval": "Interval between automatic DB structure reloads in seconds",
|
||||
|
||||
@@ -207,6 +207,8 @@
|
||||
"command.datagrid.editCell": "Editar valor de celda",
|
||||
"command.datagrid.editJsonDocument": "Editar fila como documento JSON",
|
||||
"command.datagrid.editSelection": "Editar selección como tabla",
|
||||
"command.datagrid.fetchAll": "Cargar todas las filas",
|
||||
"command.datagrid.fetchAll.toolbar": "Cargar todo",
|
||||
"command.datagrid.filterSelected": "Filtrar valor seleccionado",
|
||||
"command.datagrid.findColumn": "Buscar columna",
|
||||
"command.datagrid.generateSql": "Generar SQL",
|
||||
@@ -723,6 +725,11 @@
|
||||
"datagrid.columnName": "Nombre de columna",
|
||||
"datagrid.columnNameFilter": "Filtro de nombre de columna",
|
||||
"datagrid.copyAdvanced": "Copiar avanzado",
|
||||
"datagrid.fetchAll.confirm": "Cargar todo",
|
||||
"datagrid.fetchAll.progress": "Cargando todas las filas... {count} cargadas",
|
||||
"datagrid.fetchAll.progressDb": "Cargando datos desde la base de datos...",
|
||||
"datagrid.fetchAll.title": "Cargar todas las filas",
|
||||
"datagrid.fetchAll.warning": "Esto cargará todas las filas restantes en memoria. Para tablas grandes, esto puede consumir una cantidad significativa de memoria y podría afectar el rendimiento de la aplicación.",
|
||||
"datagrid.macros.calculation": "Cálculo",
|
||||
"datagrid.macros.calculationDescription": "Expresión personalizada. Use row.column_name para acceder a valores de columna, value para valor original",
|
||||
"datagrid.macros.changeTextCase": "Cambiar mayúsculas/minúsculas",
|
||||
@@ -1238,7 +1245,6 @@
|
||||
"query.groupFilter": "Filtro de grupo",
|
||||
"query.isolationLevel": "Nivel de aislamiento",
|
||||
"query.limitRows": "Limitar {queryRowsLimit} filas",
|
||||
"query.named": ":variable",
|
||||
"query.noParameters": "(sin parámetros)",
|
||||
"query.noRowsLimit": "(Sin límite de filas)",
|
||||
"query.orFilter": "Filtro OR {number}",
|
||||
@@ -1256,7 +1262,7 @@
|
||||
"query.sortOrder": "Orden de clasificación",
|
||||
"query.table": "Tabla",
|
||||
"query.unlimitedRows": "Filas ilimitadas",
|
||||
"query.variable": "#variable",
|
||||
"query.variable": "variable",
|
||||
"queryParameters.editQueryParameters": "Editar parámetros de consulta",
|
||||
"queryParameters.runQuery": "Ejecutar consulta",
|
||||
"queryParameters.stringValuesMustBeQuoted": "Los valores de cadena deben estar 'entre comillas'. Puede usar expresiones SQL válidas.",
|
||||
@@ -1333,6 +1339,7 @@
|
||||
"settings.confirmations": "Confirmaciones",
|
||||
"settings.confirmations.skipConfirm.collectionDataSave": "Omitir confirmación al guardar datos de colección (NoSQL)",
|
||||
"settings.confirmations.skipConfirm.tableDataSave": "Omitir confirmación al guardar datos de tabla (SQL)",
|
||||
"settings.confirmations.skipFetchAllConfirm": "Omitir confirmación al cargar todas las filas",
|
||||
"settings.connection": "Conexión",
|
||||
"settings.connection.autoRefresh": "Recarga automática del modelo de base de datos en segundo plano",
|
||||
"settings.connection.autoRefreshInterval": "Intervalo entre recargas automáticas de estructura de BD en segundos",
|
||||
|
||||
@@ -207,6 +207,8 @@
|
||||
"command.datagrid.editCell": "Modifier la valeur de cellule",
|
||||
"command.datagrid.editJsonDocument": "Modifier la ligne en tant que document JSON",
|
||||
"command.datagrid.editSelection": "Modifier la sélection en tant que table",
|
||||
"command.datagrid.fetchAll": "Charger toutes les lignes",
|
||||
"command.datagrid.fetchAll.toolbar": "Tout charger",
|
||||
"command.datagrid.filterSelected": "Filtrer la valeur sélectionnée",
|
||||
"command.datagrid.findColumn": "Rechercher une colonne",
|
||||
"command.datagrid.generateSql": "Générer du SQL",
|
||||
@@ -723,6 +725,11 @@
|
||||
"datagrid.columnName": "Nom de la colonne",
|
||||
"datagrid.columnNameFilter": "Filtre de nom de colonne",
|
||||
"datagrid.copyAdvanced": "Copie avancée",
|
||||
"datagrid.fetchAll.confirm": "Tout charger",
|
||||
"datagrid.fetchAll.progress": "Chargement de toutes les lignes... {count} chargées",
|
||||
"datagrid.fetchAll.progressDb": "Chargement des données depuis la base de données...",
|
||||
"datagrid.fetchAll.title": "Charger toutes les lignes",
|
||||
"datagrid.fetchAll.warning": "Cela chargera toutes les lignes restantes en mémoire. Pour les grandes tables, cela peut consommer une quantité importante de mémoire et affecter les performances de l'application.",
|
||||
"datagrid.macros.calculation": "Calcul",
|
||||
"datagrid.macros.calculationDescription": "Expression personnalisée. Utilisez row.column_name pour accéder aux valeurs de colonne et value pour la valeur d'origine",
|
||||
"datagrid.macros.changeTextCase": "Modifier la casse du texte",
|
||||
@@ -1238,7 +1245,6 @@
|
||||
"query.groupFilter": "Filtre de groupe",
|
||||
"query.isolationLevel": "Niveau d'isolation",
|
||||
"query.limitRows": "Limiter à {queryRowsLimit} lignes",
|
||||
"query.named": ":variable",
|
||||
"query.noParameters": "(aucun paramètre)",
|
||||
"query.noRowsLimit": "(Aucune limite de lignes)",
|
||||
"query.orFilter": "Filtre OU {number}",
|
||||
@@ -1256,7 +1262,7 @@
|
||||
"query.sortOrder": "Ordre de tri",
|
||||
"query.table": "Table",
|
||||
"query.unlimitedRows": "Lignes illimitées",
|
||||
"query.variable": "#variable",
|
||||
"query.variable": "variable",
|
||||
"queryParameters.editQueryParameters": "Modifier les paramètres de requête",
|
||||
"queryParameters.runQuery": "Exécuter la requête",
|
||||
"queryParameters.stringValuesMustBeQuoted": "Les valeurs de type chaîne doivent être 'entre guillemets'. Vous pouvez utiliser des expressions SQL valides.",
|
||||
@@ -1333,6 +1339,7 @@
|
||||
"settings.confirmations": "Confirmations",
|
||||
"settings.confirmations.skipConfirm.collectionDataSave": "Ignorer la confirmation lors de l'enregistrement des données de collection (NoSQL)",
|
||||
"settings.confirmations.skipConfirm.tableDataSave": "Ignorer la confirmation lors de l'enregistrement des données de table (SQL)",
|
||||
"settings.confirmations.skipFetchAllConfirm": "Ignorer la confirmation lors du chargement de toutes les lignes",
|
||||
"settings.connection": "Connexion",
|
||||
"settings.connection.autoRefresh": "Rafraîchissement automatique du modèle de base de données en arrière-plan",
|
||||
"settings.connection.autoRefreshInterval": "Intervalle entre les rechargements automatiques de la structure de BD en secondes",
|
||||
|
||||
@@ -207,6 +207,8 @@
|
||||
"command.datagrid.editCell": "Modifica valore cella",
|
||||
"command.datagrid.editJsonDocument": "Modifica riga come documento JSON",
|
||||
"command.datagrid.editSelection": "Modifica selezione come tabella",
|
||||
"command.datagrid.fetchAll": "Carica tutte le righe",
|
||||
"command.datagrid.fetchAll.toolbar": "Carica tutto",
|
||||
"command.datagrid.filterSelected": "Filtra valore selezionato",
|
||||
"command.datagrid.findColumn": "Trova colonna",
|
||||
"command.datagrid.generateSql": "Genera SQL",
|
||||
@@ -723,6 +725,11 @@
|
||||
"datagrid.columnName": "Nome colonna",
|
||||
"datagrid.columnNameFilter": "Filtro nome colonna",
|
||||
"datagrid.copyAdvanced": "Copia avanzato",
|
||||
"datagrid.fetchAll.confirm": "Carica tutto",
|
||||
"datagrid.fetchAll.progress": "Caricamento di tutte le righe... {count} caricate",
|
||||
"datagrid.fetchAll.progressDb": "Caricamento dati dal database...",
|
||||
"datagrid.fetchAll.title": "Carica tutte le righe",
|
||||
"datagrid.fetchAll.warning": "Questo caricherà tutte le righe rimanenti in memoria. Per tabelle grandi, potrebbe consumare una quantità significativa di memoria e influire sulle prestazioni dell'applicazione.",
|
||||
"datagrid.macros.calculation": "Calcolo",
|
||||
"datagrid.macros.calculationDescription": "Espressione personalizzata. Usa row.column_name per accedere ai valori colonna, value per il valore originale",
|
||||
"datagrid.macros.changeTextCase": "Cambia maiuscole/minuscole",
|
||||
@@ -1238,7 +1245,6 @@
|
||||
"query.groupFilter": "Filtro gruppo",
|
||||
"query.isolationLevel": "Livello di isolamento",
|
||||
"query.limitRows": "Limita a {queryRowsLimit} righe",
|
||||
"query.named": ":variabile",
|
||||
"query.noParameters": "(nessun parametro)",
|
||||
"query.noRowsLimit": "(Nessun limite righe)",
|
||||
"query.orFilter": "Filtro OR {number}",
|
||||
@@ -1256,7 +1262,7 @@
|
||||
"query.sortOrder": "Ordinamento",
|
||||
"query.table": "Tabella",
|
||||
"query.unlimitedRows": "Righe illimitate",
|
||||
"query.variable": "#variabile",
|
||||
"query.variable": "variabile",
|
||||
"queryParameters.editQueryParameters": "Modifica parametri query",
|
||||
"queryParameters.runQuery": "Esegui query",
|
||||
"queryParameters.stringValuesMustBeQuoted": "I valori stringa devono essere 'quoted'. Puoi usare espressioni SQL valide.",
|
||||
@@ -1333,6 +1339,7 @@
|
||||
"settings.confirmations": "Conferme",
|
||||
"settings.confirmations.skipConfirm.collectionDataSave": "Salta conferma quando salvi dati collezione (NoSQL)",
|
||||
"settings.confirmations.skipConfirm.tableDataSave": "Salta conferma quando salvi dati tabella (SQL)",
|
||||
"settings.confirmations.skipFetchAllConfirm": "Salta conferma quando carichi tutte le righe",
|
||||
"settings.connection": "Connessione",
|
||||
"settings.connection.autoRefresh": "Aggiornamento automatico del modello database in background",
|
||||
"settings.connection.autoRefreshInterval": "Intervallo tra ricaricamenti automatici struttura DB in secondi",
|
||||
|
||||
@@ -207,6 +207,8 @@
|
||||
"command.datagrid.editCell": "セルの値を編集",
|
||||
"command.datagrid.editJsonDocument": "行をJSONドキュメントとして編集",
|
||||
"command.datagrid.editSelection": "選択範囲をテーブルとして編集",
|
||||
"command.datagrid.fetchAll": "すべての行を取得",
|
||||
"command.datagrid.fetchAll.toolbar": "すべて取得",
|
||||
"command.datagrid.filterSelected": "選択した値でフィルター",
|
||||
"command.datagrid.findColumn": "カラムを検索",
|
||||
"command.datagrid.generateSql": "SQLを生成",
|
||||
@@ -723,6 +725,11 @@
|
||||
"datagrid.columnName": "カラム名",
|
||||
"datagrid.columnNameFilter": "カラム名フィルター",
|
||||
"datagrid.copyAdvanced": "高度なコピー",
|
||||
"datagrid.fetchAll.confirm": "すべて取得",
|
||||
"datagrid.fetchAll.progress": "すべての行を取得中... {count} 件読み込み済み",
|
||||
"datagrid.fetchAll.progressDb": "データベースからデータを取得中...",
|
||||
"datagrid.fetchAll.title": "すべての行を取得",
|
||||
"datagrid.fetchAll.warning": "これにより、残りのすべての行がメモリに読み込まれます。大きなテーブルの場合、かなりのメモリを消費し、アプリケーションのパフォーマンスに影響を与える可能性があります。",
|
||||
"datagrid.macros.calculation": "Calculation",
|
||||
"datagrid.macros.calculationDescription": "Custom expression. Use row.column_name for accessing column values, value for original value",
|
||||
"datagrid.macros.changeTextCase": "Change text case",
|
||||
@@ -1238,7 +1245,6 @@
|
||||
"query.groupFilter": "グループフィルター",
|
||||
"query.isolationLevel": "分離レベル",
|
||||
"query.limitRows": "{queryRowsLimit}行に制限",
|
||||
"query.named": ":variable",
|
||||
"query.noParameters": "(パラメーターなし)",
|
||||
"query.noRowsLimit": "(行数制限なし)",
|
||||
"query.orFilter": "ORフィルター {number}",
|
||||
@@ -1256,7 +1262,7 @@
|
||||
"query.sortOrder": "ソート順",
|
||||
"query.table": "テーブル",
|
||||
"query.unlimitedRows": "無制限",
|
||||
"query.variable": "#variable",
|
||||
"query.variable": "変数",
|
||||
"queryParameters.editQueryParameters": "クエリパラメーターを編集",
|
||||
"queryParameters.runQuery": "クエリを実行",
|
||||
"queryParameters.stringValuesMustBeQuoted": "文字列値は 'クォート' する必要があります。有効なSQL式を使用できます。",
|
||||
@@ -1333,6 +1339,7 @@
|
||||
"settings.confirmations": "確認",
|
||||
"settings.confirmations.skipConfirm.collectionDataSave": "コレクションデータ保存時の確認をスキップ (NoSQL)",
|
||||
"settings.confirmations.skipConfirm.tableDataSave": "テーブルデータ保存時の確認をスキップ (SQL)",
|
||||
"settings.confirmations.skipFetchAllConfirm": "すべての行を取得する際の確認をスキップ",
|
||||
"settings.connection": "接続",
|
||||
"settings.connection.autoRefresh": "バックグラウンドでデータベースモデルを自動更新",
|
||||
"settings.connection.autoRefreshInterval": "DB構造の自動再読み込み間隔(秒)",
|
||||
|
||||
@@ -207,6 +207,8 @@
|
||||
"command.datagrid.editCell": "셀 값 편집",
|
||||
"command.datagrid.editJsonDocument": "행을 JSON 문서로 편집",
|
||||
"command.datagrid.editSelection": "선택 영역을 테이블로 편집",
|
||||
"command.datagrid.fetchAll": "모든 행 가져오기",
|
||||
"command.datagrid.fetchAll.toolbar": "모두 가져오기",
|
||||
"command.datagrid.filterSelected": "선택한 값으로 필터",
|
||||
"command.datagrid.findColumn": "컬럼 찾기",
|
||||
"command.datagrid.generateSql": "SQL 생성",
|
||||
@@ -723,6 +725,11 @@
|
||||
"datagrid.columnName": "컬럼 이름",
|
||||
"datagrid.columnNameFilter": "컬럼 이름 필터",
|
||||
"datagrid.copyAdvanced": "고급 복사",
|
||||
"datagrid.fetchAll.confirm": "모든 행 가져오기",
|
||||
"datagrid.fetchAll.progress": "모든 행 가져오는 중... {count}개 로드됨",
|
||||
"datagrid.fetchAll.progressDb": "데이터베이스에서 데이터 가져오는 중...",
|
||||
"datagrid.fetchAll.title": "모든 행 가져오기",
|
||||
"datagrid.fetchAll.warning": "남은 모든 행을 메모리로 로드합니다. 테이블이 큰 경우 상당한 메모리를 사용할 수 있으며 애플리케이션 성능에 영향을 줄 수 있습니다.",
|
||||
"datagrid.macros.calculation": "계산",
|
||||
"datagrid.macros.calculationDescription": "사용자 정의 표현식. 컬럼 값에 접근하려면 row.column_name, 원래 값은 value 사용",
|
||||
"datagrid.macros.changeTextCase": "대소문자 변경",
|
||||
@@ -1238,7 +1245,6 @@
|
||||
"query.groupFilter": "그룹 필터",
|
||||
"query.isolationLevel": "격리 수준",
|
||||
"query.limitRows": "{queryRowsLimit}행 제한",
|
||||
"query.named": ":variable",
|
||||
"query.noParameters": "(매개변수 없음)",
|
||||
"query.noRowsLimit": "(행 제한 없음)",
|
||||
"query.orFilter": "OR 필터 {number}",
|
||||
@@ -1256,7 +1262,7 @@
|
||||
"query.sortOrder": "정렬 순서",
|
||||
"query.table": "테이블",
|
||||
"query.unlimitedRows": "무제한 행",
|
||||
"query.variable": "#variable",
|
||||
"query.variable": "변수",
|
||||
"queryParameters.editQueryParameters": "쿼리 매개변수 편집",
|
||||
"queryParameters.runQuery": "쿼리 실행",
|
||||
"queryParameters.stringValuesMustBeQuoted": "문자열 값은 '따옴표'로 감싸야 합니다. 유효한 SQL 표현식을 사용할 수 있습니다.",
|
||||
@@ -1333,6 +1339,7 @@
|
||||
"settings.confirmations": "확인",
|
||||
"settings.confirmations.skipConfirm.collectionDataSave": "컬렉션 데이터 저장 시 확인 건너뛰기(NoSQL)",
|
||||
"settings.confirmations.skipConfirm.tableDataSave": "테이블 데이터 저장 시 확인 건너뛰기(SQL)",
|
||||
"settings.confirmations.skipFetchAllConfirm": "모든 행 가져오기 시 확인 건너뛰기",
|
||||
"settings.connection": "연결",
|
||||
"settings.connection.autoRefresh": "백그라운드에서 데이터베이스 모델 자동 새로 고침",
|
||||
"settings.connection.autoRefreshInterval": "자동 DB 구조 재로딩 간격(초)",
|
||||
|
||||
@@ -207,6 +207,8 @@
|
||||
"command.datagrid.editCell": "Editar valor da célula",
|
||||
"command.datagrid.editJsonDocument": "Editar linha como documento JSON",
|
||||
"command.datagrid.editSelection": "Editar seleção como tabela",
|
||||
"command.datagrid.fetchAll": "Buscar todas as linhas",
|
||||
"command.datagrid.fetchAll.toolbar": "Buscar todas",
|
||||
"command.datagrid.filterSelected": "Filtrar valor selecionado",
|
||||
"command.datagrid.findColumn": "Localizar coluna",
|
||||
"command.datagrid.generateSql": "Gerar SQL",
|
||||
@@ -723,6 +725,11 @@
|
||||
"datagrid.columnName": "Nome da coluna",
|
||||
"datagrid.columnNameFilter": "Filtro de nome de coluna",
|
||||
"datagrid.copyAdvanced": "Cópia avançada",
|
||||
"datagrid.fetchAll.confirm": "Buscar todas",
|
||||
"datagrid.fetchAll.progress": "Buscando todas as linhas... {count} carregadas",
|
||||
"datagrid.fetchAll.progressDb": "Buscando dados do banco de dados...",
|
||||
"datagrid.fetchAll.title": "Buscar todas as linhas",
|
||||
"datagrid.fetchAll.warning": "Isso irá carregar todas as linhas restantes na memória. Para tabelas grandes, isso pode consumir uma quantidade significativa de memória e afetar o desempenho da aplicação.",
|
||||
"datagrid.macros.calculation": "Cálculo",
|
||||
"datagrid.macros.calculationDescription": "Expressão personalizada. Use row.nome_coluna para acessar valores de colunas, value para valor original",
|
||||
"datagrid.macros.changeTextCase": "Alterar maiúsculas/minúsculas",
|
||||
@@ -1238,7 +1245,6 @@
|
||||
"query.groupFilter": "Filtro de grupo",
|
||||
"query.isolationLevel": "Nível de isolamento",
|
||||
"query.limitRows": "Limitar a {queryRowsLimit} linhas",
|
||||
"query.named": ":variável",
|
||||
"query.noParameters": "(sem parâmetros)",
|
||||
"query.noRowsLimit": "(Sem limite de linhas)",
|
||||
"query.orFilter": "Filtro OU {number}",
|
||||
@@ -1256,7 +1262,7 @@
|
||||
"query.sortOrder": "Ordem de classificação",
|
||||
"query.table": "Tabela",
|
||||
"query.unlimitedRows": "Linhas ilimitadas",
|
||||
"query.variable": "#variável",
|
||||
"query.variable": "variável",
|
||||
"queryParameters.editQueryParameters": "Editar parâmetros da consulta",
|
||||
"queryParameters.runQuery": "Executar consulta",
|
||||
"queryParameters.stringValuesMustBeQuoted": "Valores de texto devem estar 'entre aspas'. Você pode usar expressões SQL válidas.",
|
||||
@@ -1333,6 +1339,7 @@
|
||||
"settings.confirmations": "Confirmações",
|
||||
"settings.confirmations.skipConfirm.collectionDataSave": "Pular confirmação ao salvar dados de coleção (NoSQL)",
|
||||
"settings.confirmations.skipConfirm.tableDataSave": "Pular confirmação ao salvar dados de tabela (SQL)",
|
||||
"settings.confirmations.skipFetchAllConfirm": "Pular confirmação ao buscar todas as linhas",
|
||||
"settings.connection": "Conexão",
|
||||
"settings.connection.autoRefresh": "Atualização automática do modelo de banco de dados em segundo plano",
|
||||
"settings.connection.autoRefreshInterval": "Intervalo entre recarregamentos automáticos da estrutura do BD em segundos",
|
||||
|
||||
@@ -207,6 +207,8 @@
|
||||
"command.datagrid.editCell": "Upraviť hodnotu bunky",
|
||||
"command.datagrid.editJsonDocument": "Upraviť riadok ako JSON dokument",
|
||||
"command.datagrid.editSelection": "Upraviť výber ako tabuľku",
|
||||
"command.datagrid.fetchAll": "Načítať všetky riadky",
|
||||
"command.datagrid.fetchAll.toolbar": "Načítať všetko",
|
||||
"command.datagrid.filterSelected": "Filtrovať vybranú hodnotu",
|
||||
"command.datagrid.findColumn": "Nájsť stĺpec",
|
||||
"command.datagrid.generateSql": "Generovať SQL",
|
||||
@@ -723,6 +725,11 @@
|
||||
"datagrid.columnName": "Názov stĺpca",
|
||||
"datagrid.columnNameFilter": "Filter názvu stĺpca",
|
||||
"datagrid.copyAdvanced": "Pokročilé kopírovanie",
|
||||
"datagrid.fetchAll.confirm": "Načítať všetko",
|
||||
"datagrid.fetchAll.progress": "Načítavam všetky riadky... {count} načítaných",
|
||||
"datagrid.fetchAll.progressDb": "Načítavanie dát z databázy...",
|
||||
"datagrid.fetchAll.title": "Načítať všetky riadky",
|
||||
"datagrid.fetchAll.warning": "Týmto sa načítajú všetky zostávajúce riadky do pamäte. Pri veľkých tabuľkách to môže spotrebovať značné množstvo pamäte a môže ovplyvniť výkon aplikácie.",
|
||||
"datagrid.macros.calculation": "Výpočet",
|
||||
"datagrid.macros.calculationDescription": "Vlastný výraz. Použite riadok.názov_stĺpca pre prístup k hodnotám stĺpcov, value pre pôvodnú hodnotu",
|
||||
"datagrid.macros.changeTextCase": "Zmeniť veľkosť písmen",
|
||||
@@ -1238,7 +1245,6 @@
|
||||
"query.groupFilter": "Filter skupiny",
|
||||
"query.isolationLevel": "Úroveň izolácie",
|
||||
"query.limitRows": "Obmedziť na {queryRowsLimit} riadkov",
|
||||
"query.named": ":premenná",
|
||||
"query.noParameters": "(žiadne parametre)",
|
||||
"query.noRowsLimit": "(bez limitu riadkov)",
|
||||
"query.orFilter": "OR filter {number}",
|
||||
@@ -1256,7 +1262,7 @@
|
||||
"query.sortOrder": "Poradie zoradenia",
|
||||
"query.table": "Tabuľka",
|
||||
"query.unlimitedRows": "Neobmedzené riadky",
|
||||
"query.variable": "#premenná",
|
||||
"query.variable": "premenná",
|
||||
"queryParameters.editQueryParameters": "Upraviť parametre dotazu",
|
||||
"queryParameters.runQuery": "Spustiť dotaz",
|
||||
"queryParameters.stringValuesMustBeQuoted": "Reťazcové hodnoty musia byť 'v úvodzovkách'. Môžete použiť platné SQL výrazy.",
|
||||
@@ -1333,6 +1339,7 @@
|
||||
"settings.confirmations": "Potvrdenia",
|
||||
"settings.confirmations.skipConfirm.collectionDataSave": "Preskočiť potvrdenie pri ukladaní údajov kolekcie (NoSQL)",
|
||||
"settings.confirmations.skipConfirm.tableDataSave": "Preskočiť potvrdenie pri ukladaní údajov tabuľky (SQL)",
|
||||
"settings.confirmations.skipFetchAllConfirm": "Preskočiť potvrdenie pri načítaní všetkých riadkov",
|
||||
"settings.connection": "Pripojenie",
|
||||
"settings.connection.autoRefresh": "Automatické obnovenie modelu databázy na pozadí",
|
||||
"settings.connection.autoRefreshInterval": "Interval medzi automatickým načítaním štruktúry DB (v sekundách)",
|
||||
|
||||
@@ -207,6 +207,8 @@
|
||||
"command.datagrid.editCell": "编辑单元格值",
|
||||
"command.datagrid.editJsonDocument": "将行编辑为 JSON 文档",
|
||||
"command.datagrid.editSelection": "将选区编辑为表",
|
||||
"command.datagrid.fetchAll": "获取所有行",
|
||||
"command.datagrid.fetchAll.toolbar": "获取全部",
|
||||
"command.datagrid.filterSelected": "筛选选中值",
|
||||
"command.datagrid.findColumn": "查找列",
|
||||
"command.datagrid.generateSql": "生成 SQL",
|
||||
@@ -723,6 +725,11 @@
|
||||
"datagrid.columnName": "列名",
|
||||
"datagrid.columnNameFilter": "列名筛选",
|
||||
"datagrid.copyAdvanced": "高级复制",
|
||||
"datagrid.fetchAll.confirm": "获取全部",
|
||||
"datagrid.fetchAll.progress": "正在获取所有行... 已加载 {count}",
|
||||
"datagrid.fetchAll.progressDb": "正在从数据库获取数据...",
|
||||
"datagrid.fetchAll.title": "获取所有行",
|
||||
"datagrid.fetchAll.warning": "这将把所有剩余的行加载到内存中。对于大型表,这可能会占用大量内存,并可能影响应用程序性能。",
|
||||
"datagrid.macros.calculation": "计算",
|
||||
"datagrid.macros.calculationDescription": "自定义表达式。使用 row.column_name 访问列值,value 访问原始值",
|
||||
"datagrid.macros.changeTextCase": "更改文本大小写",
|
||||
@@ -1238,7 +1245,6 @@
|
||||
"query.groupFilter": "分组筛选",
|
||||
"query.isolationLevel": "隔离级别",
|
||||
"query.limitRows": "限制 {queryRowsLimit} 行",
|
||||
"query.named": ":variable",
|
||||
"query.noParameters": "(无参数)",
|
||||
"query.noRowsLimit": "(无行数限制)",
|
||||
"query.orFilter": "OR 筛选 {number}",
|
||||
@@ -1256,7 +1262,7 @@
|
||||
"query.sortOrder": "排序顺序",
|
||||
"query.table": "表",
|
||||
"query.unlimitedRows": "不限行数",
|
||||
"query.variable": "#variable",
|
||||
"query.variable": "变量",
|
||||
"queryParameters.editQueryParameters": "编辑查询参数",
|
||||
"queryParameters.runQuery": "运行查询",
|
||||
"queryParameters.stringValuesMustBeQuoted": "字符串值必须使用引号括起来。您可以使用有效的 SQL 表达式。",
|
||||
@@ -1333,6 +1339,7 @@
|
||||
"settings.confirmations": "确认",
|
||||
"settings.confirmations.skipConfirm.collectionDataSave": "保存集合数据时跳过确认(NoSQL)",
|
||||
"settings.confirmations.skipConfirm.tableDataSave": "保存表数据时跳过确认(SQL)",
|
||||
"settings.confirmations.skipFetchAllConfirm": "获取所有行时跳过确认",
|
||||
"settings.connection": "连接",
|
||||
"settings.connection.autoRefresh": "后台自动刷新数据库模型",
|
||||
"settings.connection.autoRefreshInterval": "自动重新加载数据库结构的间隔(秒)",
|
||||
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
|
||||
# Ensure npm 11.5.1 or later is installed
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
run: npm install -g npm@11.5.1
|
||||
|
||||
- name: Remove dbmodel - should be not published
|
||||
run: |
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
# Ensure npm 11.5.1 or later is installed
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
run: npm install -g npm@11.5.1
|
||||
|
||||
# - name: Configure NPM token
|
||||
# env:
|
||||
|
||||
@@ -7,7 +7,7 @@ checkout-and-merge-pro:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: 6b5e2ff831db9baedb2a43862daa4247810b15de
|
||||
ref: 87c3efdaf83786abee4366dee2c58fea355edc4c
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
Reference in New Issue
Block a user