Compare commits

..

51 Commits

Author SHA1 Message Date
Nybkox c7db9ef481 fix: default options for libsql drive .script 2025-03-13 10:19:25 +01:00
Nybkox fb709c9eb0 fix(libSql): support useTransaction flag for scripts 2025-03-13 09:44:39 +01:00
Nybkox dfe4d2811a fix(ScriptDrivedDeployer): avoid nested transactions 2025-03-13 09:44:21 +01:00
Nybkox 6e4d16749b feat: lib sql server tests 2025-03-12 09:54:13 +01:00
Nybkox 75b2debf0a feat: libsql basic support 2025-03-11 15:11:35 +01:00
Jan Prochazka ba9e124527 Merge pull request #1064 from nyaaao/cassandra-auth
feat: add support for plain-text auth for Cassandra
2025-03-11 12:29:28 +01:00
SPRINX0\prochazka d684ab0e5e translation extractor 2025-03-07 17:33:51 +01:00
SPRINX0\prochazka 2e8205f458 fixed translation extractor 2025-03-07 17:33:07 +01:00
SPRINX0\prochazka 35855297eb SYNC: fix 2025-03-06 07:59:33 +00:00
SPRINX0\prochazka d365ecce46 translations 2025-03-06 08:52:17 +01:00
SPRINX0\prochazka f4edc45507 localization 2025-03-05 18:36:31 +01:00
SPRINX0\prochazka d165d81df0 renamed some lang keys 2025-03-05 18:27:05 +01:00
SPRINX0\prochazka ba0eba7132 SYNC: mognodb - correct handle stream errors 2025-03-05 17:09:48 +00:00
SPRINX0\prochazka 2b7f4281c2 SYNC: mongo import tests 2025-03-05 16:53:29 +00:00
SPRINX0\prochazka 0046575a4f SYNC: test CSV import 2025-03-05 16:10:52 +00:00
SPRINX0\prochazka 58b88d66be catch errors in base bulk importer 2025-03-05 16:56:18 +01:00
SPRINX0\prochazka 57f1019e51 SYNC: CSV with error import test 2025-03-05 15:35:34 +00:00
SPRINX0\prochazka b40168182a SYNC: fixed SQLite E2E tests (use absolute folder) 2025-03-05 14:25:40 +00:00
SPRINX0\prochazka 0ece662d8c SYNC: CSV import test 2025-03-05 13:21:47 +00:00
SPRINX0\prochazka 9265e52d68 SYNC: backup table, truncate, drop table - tests for all DBs 2025-03-05 12:33:16 +00:00
SPRINX0\prochazka 3a04166747 fixed bulk import 2025-03-05 12:55:37 +01:00
SPRINX0\prochazka 75bf58359c import error reporting 2025-03-05 12:51:44 +01:00
SPRINX0\prochazka e7f63e0460 confirm reload 2025-03-05 10:56:29 +01:00
SPRINX0\prochazka 1fdd1c6e88 Merge branch 'feature/localization' 2025-03-05 10:49:08 +01:00
SPRINX0\prochazka 391b9e91e9 gitignore 2025-03-05 10:44:08 +01:00
Nybkox 1e67ca3794 feat: default value for language select 2025-03-04 22:05:04 +01:00
Nybkox ceebf6dbe1 chore: add logging to translations:check ok scenario 2025-03-04 22:05:04 +01:00
Nybkox 8d4f9fd953 fix: use simple languagnes names 2025-03-04 22:05:04 +01:00
Nybkox 1c3032068e feat: remove unused by default when extracting 2025-03-04 21:17:01 +01:00
Nybkox 7b4b72166f chore: move sortJsonKeys helper 2025-03-04 20:54:42 +01:00
Nybkox 707e5bb8b0 chore: update git ignore 2025-03-04 20:53:53 +01:00
Nybkox ad5d364c57 chore: extract translations 2025-03-04 20:53:53 +01:00
Nybkox 138fadf672 feat: compile messages 2025-03-04 20:53:53 +01:00
Nybkox 82eabc41fe feat: sort translation json keys alphabetically 2025-03-04 20:53:52 +01:00
Nybkox 3e6aab6b00 feat: basic translations to ui 2025-03-04 20:53:52 +01:00
Nybkox 5396b3f1fb feat: add translations:check command 2025-03-04 20:53:52 +01:00
Nybkox b1ba887922 feat: separate remove-unused command 2025-03-04 20:53:52 +01:00
Nybkox 93a1c593fe feat: add basic language switch to settings 2025-03-04 20:53:52 +01:00
Nybkox b7044248cb feat: add translations api for fe 2025-03-04 20:53:52 +01:00
Nybkox ea5e2f660b feat: add --removeUnused flag to extract translations 2025-03-04 20:53:52 +01:00
Nybkox e9779a3d2f feat: add add-missing command to translations cli 2025-03-04 20:53:52 +01:00
Nybkox 1c6ec0f8e3 refactor: add index.js to translations-cli, add translations:extract to package.json 2025-03-04 20:53:52 +01:00
Nybkox 84bd81e525 feat: throw when found the same translation key with different default values 2025-03-04 20:53:52 +01:00
Nybkox a84cbee9db feat: basic translations extract 2025-03-04 20:53:52 +01:00
CI workflows 97b16c8c0c chore: auto-update github workflows 2025-03-04 15:43:56 +00:00
CI workflows 0a6a35b022 Update pro ref 2025-03-04 15:43:43 +00:00
SPRINX0\prochazka 6565b4101b SYNC: try to fix local e2e tests 2025-03-04 15:43:27 +00:00
nyaaao fbbcc1172d feat: add support for plain-text auth for Cassandra 2025-03-04 16:58:05 +02:00
SPRINX0\prochazka 53dc50c0dd Merge branch 'feature/impexp' 2025-03-04 15:55:10 +01:00
Jan Prochazka 2a59faec17 Merge pull request #1063 from nyaaao/cassandra-local-data-center-env
feat: configure cassandra local datacenter via environment variable
2025-03-04 12:55:20 +01:00
nyaaao 00534f7edd feat: allow specifying cassandra local datacenter via environment variable 2025-03-04 13:28:29 +02:00
84 changed files with 1924 additions and 431 deletions
+1 -1
View File
@@ -39,7 +39,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 6705e28d1de47cc8a11b3271cefaad714aa313d9
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -39,7 +39,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 6705e28d1de47cc8a11b3271cefaad714aa313d9
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -39,7 +39,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 6705e28d1de47cc8a11b3271cefaad714aa313d9
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -44,7 +44,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 6705e28d1de47cc8a11b3271cefaad714aa313d9
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 6705e28d1de47cc8a11b3271cefaad714aa313d9
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 6705e28d1de47cc8a11b3271cefaad714aa313d9
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+3 -1
View File
@@ -32,4 +32,6 @@ packages/api/src/packagedPluginsContent.js
.VSCodeCounter
packages/web/public/*.html
e2e-tests/screenshots/*.png
e2e-tests/screenshots/*.png
my_guitar_shop.db
.aider*
+43
View File
@@ -0,0 +1,43 @@
//@ts-check
const { getDefaultTranslations, getLanguageTranslations } = require('./helpers');
/**
* @param {string} language
*/
function getMissingTranslations(language) {
const source = getDefaultTranslations();
/** @type {Record<string, string>} */
let target;
try {
target = getLanguageTranslations(language);
} catch {
console.log(`Language ${language} not found, creating a new one`);
target = {};
}
let added = 0;
let removed = 0;
for (const key in source) {
if (!target[key]) {
target[key] = `*** ${source[key]}`;
added++;
}
}
for (const key in target) {
if (!source[key]) {
delete target[key];
removed++;
}
}
const newLength = Object.keys(target).length;
return { result: target, stats: { added, removed, newLength } };
}
module.exports = {
getMissingTranslations,
};
+16
View File
@@ -0,0 +1,16 @@
// @ts-check
//
const defaultLanguage = 'en';
/** @typedef {{ extensions: string[], directories: string[] }} ExtractConfig
/** @type {ExtractConfig} */
const defaultExtractConfig = {
extensions: ['.js', '.ts', '.svelte'],
directories: ['app', 'packages/web'],
};
module.exports = {
defaultLanguage,
defaultExtractConfig,
};
+84
View File
@@ -0,0 +1,84 @@
//@ts-check
const fs = require('fs');
const { promisify } = require('util');
const { getFiles } = require('./helpers');
const readFilePromise = promisify(fs.readFile);
const translationRegex = /_t\(\s*['"]([^'"]+)['"]\s*,\s*\{\s*defaultMessage\s*:\s*['"]([^'"]+)['"]\s*\}/g;
/**
* @param {string} file
*
* @returns {Promise<Record<string, string>>}
*/
async function extractTranslationsFromFile(file) {
/** @type {Record<string, string>} */
const translations = {};
const content = await readFilePromise(file, 'utf-8');
let match;
while ((match = translationRegex.exec(content)) !== null) {
const [_, key, defaultText] = match;
translations[key] = defaultText;
}
return translations;
}
/** @typedef {{ ignoreDuplicates?: boolean }} ExtractOptions */
/**
* @param {string[]} directories
* @param {string[]} extensions
* @param {ExtractOptions} options
*
* @returns {Promise<Record<string, string>>}
*/
async function extractAllTranslations(directories, extensions, options = {}) {
const { ignoreDuplicates } = options;
try {
/** @type {Record<string, string>} */
const allTranslations = {};
/** @type {Record<string, string[]>} */
const translationKeyToFiles = {};
for (const dir of directories) {
const files = await getFiles(dir, extensions);
for (const file of files) {
const fileTranslations = await extractTranslationsFromFile(file);
for (const key in fileTranslations) {
if (!translationKeyToFiles[key]) {
translationKeyToFiles[key] = [];
}
translationKeyToFiles[key].push(file);
if (!ignoreDuplicates && allTranslations[key] && allTranslations[key] !== fileTranslations[key]) {
console.error(
`Different translations for the same key [${key}] found. ${file}: ${
fileTranslations[key]
}. Previous value: ${allTranslations[key]} was found in ${translationKeyToFiles[key].join(', ')}`
);
throw new Error(`Duplicate translation key found: ${key}`);
}
allTranslations[key] = fileTranslations[key];
}
}
}
return allTranslations;
} catch (error) {
console.error('Error extracting translations:', error);
throw error;
}
}
module.exports = {
extractTranslationsFromFile,
extractAllTranslations,
};
+198
View File
@@ -0,0 +1,198 @@
//@ts-check
const path = require('path');
const fs = require('fs');
const { defaultLanguage } = require('./constants');
const sortJsonKeysAlphabetically = require('./sortJsonKeysAlphabetically');
/**
* @param {string} file
* @param {string[]} extensions
*
* @returns {boolean}
*/
function hasValidExtension(file, extensions) {
return extensions.includes(path.extname(file).toLowerCase());
}
/**
* @param {string} dir
* @param {string[]} extensions
*
* @returns {Promise<string[]>}
*/
async function getFiles(dir, extensions) {
const files = await fs.promises.readdir(dir);
const allFiles = await Promise.all(
files.map(async file => {
const filePath = path.join(dir, file);
const stats = await fs.promises.stat(filePath);
if (stats.isDirectory()) {
return getFiles(filePath, extensions);
} else if (stats.isFile() && hasValidExtension(file, extensions)) {
const slashPath = filePath.replace(/\\/g, '/');
if (slashPath.includes('/node_modules/') || slashPath.includes('/build/') || slashPath.includes('/dist/')) {
return null;
}
return filePath;
}
return null;
})
);
const validFiles = /** @type {string[]} */ (allFiles.flat().filter(file => file !== null));
return validFiles;
}
/**
* @param {string | string[]} value
*
* @returns {string}
*/
function formatDefaultValue(value) {
if (Array.isArray(value)) {
return value.join(', ');
}
return value;
}
const scriptDir = getScriptDir();
/** @param {string} file
*
* @returns {string}
*/
function resolveFile(file) {
if (path.isAbsolute(file)) {
return file;
}
return path.resolve(scriptDir, '..', '..', file);
}
/** @param {string[]} dirs
*
* @returns {string[]}
*/
function resolveDirs(dirs) {
return dirs.map(resolveFile);
}
/**
* @param {string[]} extensions
*
* @returns {string[]}
*/
function resolveExtensions(extensions) {
return extensions.map(ext => (ext.startsWith('.') ? ext : `.${ext}`));
}
function getScriptDir() {
if (require.main?.filename) {
return path.dirname(require.main.filename);
}
if ('pkg' in process && process.pkg) {
return path.dirname(process.execPath);
}
return __dirname;
}
/**
* @param {string} file
*/
function ensureFileDirExists(file) {
const dir = path.dirname(file);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}
/**
* @param {Record<string, string>} existingTranslations - Previously extracted translations
* @param {Record<string, string>} newTranslations - Newly extracted translations
* @returns {{ added: string[], removed: string[], updated: string[] }} Translation changes
*/
const getTranslationChanges = (existingTranslations, newTranslations) => {
const existingKeys = new Set(Object.keys(existingTranslations || {}));
const newKeys = new Set(Object.keys(newTranslations));
const added = [...newKeys].filter(key => !existingKeys.has(key));
const removed = [...existingKeys].filter(key => !newKeys.has(key));
const updated = [...newKeys].filter(
key => existingKeys.has(key) && existingTranslations[key] !== newTranslations[key]
);
return { added, removed, updated };
};
function getDefaultTranslations() {
return getLanguageTranslations(defaultLanguage);
}
/**
* @param {string} language
*
* @returns {Record<string, string>}
*/
function getLanguageTranslations(language) {
const file = resolveFile(`translations/${language}.json`);
const content = fs.readFileSync(file, 'utf-8');
return JSON.parse(content);
}
/**
* @param {string} language
* @param {Record<string, string>} translations
*/
function setLanguageTranslations(language, translations) {
const file = resolveFile(`translations/${language}.json`);
const sorted = sortJsonKeysAlphabetically(translations);
fs.writeFileSync(file, JSON.stringify(sorted, null, 2));
}
/**
* @param {string} language
* @param {Record<string, string>} newTranslations
*/
function updateLanguageTranslations(language, newTranslations) {
const translations = getLanguageTranslations(language);
const updatedTranslations = { ...translations, ...newTranslations };
const sorted = sortJsonKeysAlphabetically(updatedTranslations);
setLanguageTranslations(language, sorted);
}
function getAllLanguages() {
const dir = resolveFile('translations');
const files = fs.readdirSync(dir);
const languages = files.filter(file => file.endsWith('.json')).map(file => file.replace('.json', ''));
return languages;
}
function getAllNonDefaultLanguages() {
return getAllLanguages().filter(language => language !== defaultLanguage);
}
module.exports = {
hasValidExtension,
getFiles,
formatDefaultValue,
resolveFile,
resolveDirs,
resolveExtensions,
ensureFileDirExists,
getTranslationChanges,
getDefaultTranslations,
getLanguageTranslations,
setLanguageTranslations,
updateLanguageTranslations,
getAllLanguages,
getAllNonDefaultLanguages,
};
+3
View File
@@ -0,0 +1,3 @@
const { program } = require('./program');
program.parse();
+163
View File
@@ -0,0 +1,163 @@
//@ts-check
const fs = require('fs');
const { program } = require('commander');
const {
resolveDirs,
resolveExtensions,
getTranslationChanges,
setLanguageTranslations,
getAllNonDefaultLanguages,
updateLanguageTranslations,
getDefaultTranslations,
} = require('./helpers');
const { extractAllTranslations } = require('./extract');
const { getMissingTranslations } = require('./addMissing');
const { defaultLanguage, defaultExtractConfig } = require('./constants');
const { removeUnusedAllTranslations, removeUnusedForSignelLanguage } = require('./removeUnused');
/**
* @typedef {import('./constants').ExtractConfig & { verbose?: boolean, ignoreUnused?: boolean }} ExtractOptions
*/
program.name('dbgate-translations-cli').description('CLI tool for managing translation').version('1.0.0');
program
.command('extract')
.description('Extract translation keys from source files')
.option('-d, --directories <directories...>', 'directories to search', defaultExtractConfig.directories)
.option('-e, --extensions <extensions...>', 'file extensions to process', defaultExtractConfig.extensions)
.option('-r, --ignoreUnused', 'Ignore unused keys in the output file')
.option('-v, --verbose', 'verbose mode')
.action(async (/** @type {ExtractOptions} */ options) => {
try {
const { directories, extensions, verbose, ignoreUnused } = options;
const resolvedRirectories = resolveDirs(directories);
const resolvedExtensions = resolveExtensions(extensions);
const extractedTranslations = await extractAllTranslations(resolvedRirectories, resolvedExtensions);
const defaultTranslations = getDefaultTranslations();
const { added, removed, updated } = getTranslationChanges(defaultTranslations, extractedTranslations);
console.log('\nTranslation changes:');
console.log(`- Added: ${added.length} keys`);
console.log(`- ${ignoreUnused ? 'Unused' : 'Removed'}: ${removed.length} keys`);
console.log(`- Updated: ${updated.length} keys`);
console.log(`- Total: ${Object.keys(extractedTranslations).length} keys`);
if (verbose) {
if (added.length > 0) {
console.log('\nNew keys:');
added.forEach(key => console.log(` + ${key}`));
}
if (removed.length > 0) {
console.log('\nRemoved keys:');
removed.forEach(key => console.log(` - ${key}`));
}
if (updated.length > 0) {
console.log('\nUpdated keys:');
updated.forEach(key => {
console.log(` ~ ${key}`);
console.log(` Old: ${defaultLanguage[key]}`);
console.log(` New: ${extractedTranslations[key]}`);
});
}
}
if (ignoreUnused) {
console.log('New translations were saved. Unused keys are kept.\n');
updateLanguageTranslations(defaultLanguage, extractedTranslations);
if (verbose) {
console.log('\nUnused keys:');
for (const key of removed) {
console.log(`${key}: "${defaultTranslations[key]}"`);
}
}
} else {
console.log('Unused keys were removed.\n');
setLanguageTranslations(defaultLanguage, extractedTranslations);
}
} catch (error) {
console.error(error);
console.error('Error during extraction:', error.message);
process.exit(1);
}
});
const ALL_LANGUAGES = 'all';
/**
* @param {string} target
*/
function addMissingTranslations(target) {
console.log(`Adding missing keys for language: ${target}`);
const { result, stats } = getMissingTranslations(target);
console.log(`Added: ${stats.added}, Removed: ${stats.removed}, Total: ${stats.newLength}`);
setLanguageTranslations(target, result);
console.log(`New translations for ${target} were saved.`);
}
program
.command('add-missing')
.description('Add missing keys for a langauge to the translation file')
.option('-t, --target <target>', 'language to add missing translations to', ALL_LANGUAGES)
.action(options => {
try {
const { target } = options;
const languages = getAllNonDefaultLanguages();
if (target === ALL_LANGUAGES) {
console.log('Adding missing keys for all languages\n');
for (const language of languages) {
addMissingTranslations(language);
console.log();
}
} else {
addMissingTranslations(target);
}
} catch (error) {
console.error(error);
console.error('Error during add-missing:', error.message);
process.exit(1);
}
});
program
.command('remove-unused')
.description('Remove unused keys from the translation files')
.option('-t, --target <target>', 'language to add missing translations to', ALL_LANGUAGES)
.action(async options => {
try {
const { target } = options;
if (target === ALL_LANGUAGES) {
console.log('Removing unused keys from all languages\n');
await removeUnusedAllTranslations();
} else {
await removeUnusedForSignelLanguage(target);
}
} catch (error) {
console.error(error);
console.error('Error during add-missing:', error.message);
process.exit(1);
}
});
program
.command('check')
.description('Check if there are multiple default values for the same key')
.action(async () => {
try {
await extractAllTranslations(defaultExtractConfig.directories, defaultExtractConfig.extensions);
console.log('No problems found while extracting translations.');
} catch (error) {
console.error(error);
console.error('Error during check:', error.message);
process.exit(1);
}
});
module.exports = { program };
+46
View File
@@ -0,0 +1,46 @@
// @ts-check
const { defaultExtractConfig } = require('./constants');
const { extractAllTranslations } = require('./extract');
const { getLanguageTranslations, getAllLanguages, setLanguageTranslations } = require('./helpers');
const { directories, extensions } = defaultExtractConfig;
/**
* @param {string} language
* @param {Record<string, string>} source
*/
function getUsedTranslations(language, source) {
const languageTranslations = getLanguageTranslations(language);
for (const key in languageTranslations) {
if (!(key in source)) {
delete languageTranslations[key];
}
}
return languageTranslations;
}
async function removeUnusedAllTranslations() {
const source = await extractAllTranslations(directories, extensions);
const languages = getAllLanguages();
for (const language of languages) {
const newTranslations = getUsedTranslations(language, source);
setLanguageTranslations(language, newTranslations);
}
}
/**
* @param {string} language
*/
async function removeUnusedForSignelLanguage(language) {
const source = await extractAllTranslations(directories, extensions);
const newTranslations = getUsedTranslations(language, source);
setLanguageTranslations(language, newTranslations);
}
module.exports = {
removeUnusedAllTranslations,
removeUnusedForSignelLanguage,
};
@@ -0,0 +1,24 @@
// @ts-check
/**
* @param {object|string} json
* @returns {object}
*/
function sortJsonKeysAlphabetically(json) {
const obj = typeof json === 'string' ? JSON.parse(json) : json;
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
return obj;
}
const sortedObj = Object.keys(obj)
.sort()
.reduce((result, key) => {
result[key] = obj[key];
return result;
}, {});
return sortedObj;
}
module.exports = sortJsonKeysAlphabetically;
+1
View File
@@ -4,4 +4,5 @@ module.exports = {
mssql: true,
oracle: true,
sqlite: true,
mongo: true
};
+2 -2
View File
@@ -17,8 +17,8 @@ function clearTestingData() {
if (fs.existsSync(path.join(baseDir, 'archive-e2etests'))) {
fs.rmdirSync(path.join(baseDir, 'archive-e2etests'), { recursive: true });
}
if (fs.existsSync(path.join(__dirname, '../my_guitar_shop.db'))) {
fs.unlinkSync(path.join(__dirname, '../my_guitar_shop.db'));
if (fs.existsSync(path.join(__dirname, 'tmpdata', 'my_guitar_shop.db'))) {
fs.unlinkSync(path.join(__dirname, 'tmpdata', 'my_guitar_shop.db'));
}
}
-44
View File
@@ -403,48 +403,4 @@ describe('Data browser data', () => {
cy.contains('Novak');
cy.contains('Rows: 8');
});
// it('Import', () => {
// TBC after Import FIX
// cy.contains('MySql-connection').click();
// cy.contains('MyChinook').click();
// cy.contains('Customer').rightclickclick();
// cy.contains('Import').click();
// cy.get('input[type=file]').selectFile('cypress/fixtures/Customer_add.csv');
// cy.get('table tbody tr').eq(1).within(() => {
// cy.get('select').select('Append data');
// });
// });
it('Backup table', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.contains('Customer').rightclick();
cy.contains('backup').click();
cy.testid('ConfirmSqlModal_okButton').click();
cy.contains ('_Customer').should('be.visible');
});
it('Truncate table', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.contains('_Customer').click();
cy.contains('Leonie').click();
cy.contains('_Customer').rightclick();
cy.contains('Truncate table').click();
cy.testid('ConfirmSqlModal_okButton').click();
cy.contains('Leonie').click();
cy.testid ('TableDataTab_refreshGrid').click();
cy.contains('No rows loaded')
});
it('Drop table', () => {
cy.contains('MySql-connection').click();
cy.contains('MyChinook').click();
cy.contains('_Customer').rightclick();
cy.contains('Drop table').click();
cy.testid('ConfirmSqlModal_okButton').click();
cy.contains('_Customer').should('not.exist');
});
});
+115 -14
View File
@@ -28,37 +28,37 @@ beforeEach(() => {
cy.viewport(1250, 900);
});
function multiTest(testName, testDefinition) {
function multiTest(testProps, testDefinition) {
if (localconfig.mysql) {
it(testName + ' MySQL', () => testDefinition('MySql-connection', 'mysql@dbgate-plugin-mysql'));
it('MySQL', () => testDefinition('MySql-connection', 'my_guitar_shop', 'mysql@dbgate-plugin-mysql'));
}
if (localconfig.postgres) {
it(testName + ' Postgres', () => testDefinition('Postgres-connection', 'postgres@dbgate-plugin-postgres'));
it('Postgres', () => testDefinition('Postgres-connection', 'my_guitar_shop', 'postgres@dbgate-plugin-postgres'));
}
if (localconfig.mssql) {
it(testName + ' Mssql', () => testDefinition('Mssql-connection', 'mssql@dbgate-plugin-mssql'));
it('Mssql', () => testDefinition('Mssql-connection', 'my_guitar_shop', 'mssql@dbgate-plugin-mssql'));
}
if (localconfig.oracle) {
it(testName + ' Oracle', () =>
testDefinition('Oracle-connection', 'oracle@dbgate-plugin-oracle', {
databaseName: 'C##MY_GUITAR_SHOP',
it('Oracle', () =>
testDefinition('Oracle-connection', 'C##MY_GUITAR_SHOP', 'oracle@dbgate-plugin-oracle', {
implicitTransactions: true,
})
);
}));
}
if (localconfig.sqlite) {
it(testName + ' Sqlite', () => testDefinition('Sqlite-connection', 'sqlite@dbgate-plugin-sqlite'));
it('Sqlite', () => testDefinition('Sqlite-connection', null, 'sqlite@dbgate-plugin-sqlite'));
}
if (localconfig.mongo && !testProps.skipMongo) {
it('MongoDB', () => testDefinition('Mongo-connection', 'my_guitar_shop', 'mongo@dbgate-plugin-mongo'));
}
}
describe('Mutli-sql tests', () => {
multiTest('Transactions', (connectionName, engine, options = {}) => {
describe('Transactions', () => {
multiTest({ skipMongo: true }, (connectionName, databaseName, engine, options = {}) => {
const driver = requireEngineDriver(engine);
const databaseName = options.databaseName ?? 'my_guitar_shop';
const implicitTransactions = options.implicitTransactions ?? false;
cy.contains(connectionName).click();
cy.contains(databaseName).click();
if (databaseName) cy.contains(databaseName).click();
cy.testid('TabsPanel_buttonNewQuery').click();
cy.wait(1000);
cy.get('body').type(
@@ -99,3 +99,104 @@ describe('Mutli-sql tests', () => {
cy.contains('Rows: 5');
});
});
describe('Backup table', () => {
multiTest({ skipMongo: true }, (connectionName, databaseName, engine, options = {}) => {
cy.contains(connectionName).click();
if (databaseName) cy.contains(databaseName).click();
cy.contains('customers').rightclick();
cy.contains('Create table backup').click();
cy.testid('ConfirmSqlModal_okButton').click();
cy.contains('_customers').click();
cy.contains('Rows: 8').should('be.visible');
});
});
describe('Truncate table', () => {
multiTest({ skipMongo: true }, (connectionName, databaseName, engine, options = {}) => {
cy.contains(connectionName).click();
if (databaseName) cy.contains(databaseName).click();
cy.contains('order_items').rightclick();
cy.contains('Truncate table').click();
cy.testid('ConfirmSqlModal_okButton').click();
cy.contains('order_items').click();
cy.contains('No rows loaded').should('be.visible');
});
});
describe('Drop table', () => {
multiTest({ skipMongo: true }, (connectionName, databaseName, engine, options = {}) => {
cy.contains(connectionName).click();
if (databaseName) cy.contains(databaseName).click();
cy.contains('order_items').rightclick();
cy.contains('Drop table').click();
cy.testid('ConfirmSqlModal_okButton').click();
cy.contains('order_items').should('not.exist');
});
});
describe('Import CSV', () => {
multiTest({}, (connectionName, databaseName, engine, options = {}) => {
cy.contains(connectionName).click();
if (databaseName) cy.contains(databaseName).click();
cy.testid('ConnectionList_container')
.contains(databaseName ?? connectionName)
.rightclick();
cy.contains('Import').click();
cy.get('input[type=file]').selectFile('cypress/fixtures/customers-20.csv', { force: true });
cy.contains('customers-20');
cy.testid('ImportExportTab_preview_content').contains('50ddd99fAdF48B3').should('be.visible');
cy.testid('ImportExportTab_executeButton').click();
cy.contains('20 rows written').should('be.visible');
cy.testid('SqlObjectList_refreshButton').click();
cy.testid('SqlObjectList_container').contains('customers-20').click();
cy.contains('Rows: 20').should('be.visible');
// cy.get('table tbody tr')
// .eq(1)
// .within(() => {
// cy.get('select').select('Append data');
// });
});
});
describe('Import CSV - source error', () => {
multiTest({}, (connectionName, databaseName, engine, options = {}) => {
cy.contains(connectionName).click();
if (databaseName) cy.contains(databaseName).click();
cy.testid('ConnectionList_container')
.contains(databaseName ?? connectionName)
.rightclick();
cy.contains('Import').click();
cy.get('input[type=file]').selectFile('cypress/fixtures/customers-20-err.csv', { force: true });
cy.contains('customers-20-err');
cy.testid('ImportExportTab_preview_content').contains('Invalid Closing Quote').should('be.visible');
cy.testid('ImportExportTab_executeButton').click();
cy.testid('ImportExportConfigurator_errorInfoIcon_customers-20-err').click();
cy.testid('ErrorMessageModal_message').contains('Invalid Closing Quote').should('be.visible');
});
});
describe('Import CSV - target error', () => {
multiTest({}, (connectionName, databaseName, engine, options = {}) => {
cy.contains(connectionName).click();
if (databaseName) cy.contains(databaseName).click();
cy.testid('ConnectionList_container')
.contains(databaseName ?? connectionName)
.rightclick();
cy.contains('Import').click();
cy.get('input[type=file]').selectFile('cypress/fixtures/customers-20.csv', { force: true });
cy.contains('customers-20');
cy.testid('ImportExportConfigurator_targetName_customers-20').clear().type('system."]`');
cy.testid('ImportExportTab_executeButton').click();
cy.testid('ImportExportConfigurator_errorInfoIcon_customers-20').click();
cy.testid('ErrorMessageModal_message').should('be.visible');
});
});
@@ -0,0 +1,21 @@
Index,Customer Id,First Name,Last Name,Company,City,Country,Phone 1,Phone 2,Email,Subscription Date,Website
1,50ddd99fAdF48B3,Jessica,Navarro,"Tran, Odom and Graham",Port Terranceview,Jersey,(883)287-7947,479-044-3187,wmcintyre@myers.net,2023-03-11,http://pennington.com/
"2,BD1AB97979DDcDe,Preston,Andrews,"Townsend, Lawrence and Davenport",Harringtonmouth,Croatia,188-270-5676x63873,001-428-148-4270,bradley63@jacobs.net,2023-11-15,http://burke.com/
3,0FDDD9aCa501acE,Melissa,Mckay,Rivas-Cooke,Oliviabury,Cook Islands,8257631531,001-650-689-5600x927,ugalloway@maxwell-mcclain.com,2025-01-25,https://patrick.com/
4,A7dA284B7c4AfaD,Frank,Livingston,"Lambert, Garner and Mathews",Mallorytown,Faroe Islands,766.182.6201x689,+1-476-912-2027x4370,tracie00@webster.biz,2021-03-27,https://www.hart.com/
5,38bb1624e4f8211,Ross,Mendoza,Cook and Sons,New Perry,Bolivia,001-440-468-2236,165.562.4328x9274,austincarmen@abbott.com,2024-12-12,http://harmon.com/
6,55c9Fda2BABFE1a,Jane,Tate,Knapp PLC,West Kathyshire,Morocco,001-606-447-0584x07975,(003)872-1307x285,carlosgomez@mcpherson-ramos.biz,2021-09-13,http://www.yu.com/
7,A15eccAc67cCc5b,Tanya,Chung,Frost Inc,South Julieburgh,United States of America,778-360-8301x268,+1-034-169-4237,onewton@crosby.com,2024-10-01,http://www.gill-velez.biz/
8,aFdab04acACaf90,Jackson,Moyer,Bowen Ltd,Port Triciabury,Bahrain,001-187-882-2891x169,154.903.2525,gabriela16@mejia.org,2023-06-20,http://www.david.biz/
9,A2EaeAd3c6529B9,Kelly,Douglas,Phillips PLC,Weissberg,Qatar,767.177.3199x5576,5156730935,wigginsmercedes@tanner.com,2021-06-30,https://gonzalez.net/
10,188175A5641d080,Mariah,Riddle,"Mata, Fuller and Good",Choiton,Namibia,092-993-3559x342,190.937.3937,eileen32@greer.biz,2021-04-17,http://www.bowen.info/
11,B2BA817C7bC09f0,Kristine,Pennington,"Koch, Diaz and Valdez",Port Rachael,Suriname,877.152.6488x921,790-804-9979x3290,tonya00@flynn.com,2024-03-28,https://www.rocha.net/
12,fFAD70B5Febc43a,Robert,Adams,Bender-Wolfe,Billfort,Tanzania,219-696-1912x93626,518-969-4058,jeffersoncolton@moss-ashley.com,2022-07-31,https://www.alvarez.com/
13,9a44524E1261Ed6,Franklin,Costa,Werner Group,North Bradley,Cook Islands,(895)448-4681x1999,648-345-3083x88242,marksmith@novak.com,2024-12-03,http://thornton.com/
14,0C360EfE17D06cc,Dan,Ballard,Sampson-Huff,Michaelchester,Sao Tome and Principe,+1-803-469-5237x2580,(729)574-6101x0605,molly54@acevedo.info,2022-10-24,https://www.sloan-gonzales.biz/
15,fb5cE8cb1eF2954,Clifford,Moyer,Fry-Whitehead,Lake Gary,Japan,(504)263-0450,247.514.1614x551,rhonda34@orr.com,2020-03-08,https://norton.com/
16,3e53DcaD1cB0054,Tonya,Durham,"Lowe, West and Reynolds",South Sylviaton,Brunei Darussalam,(613)893-8183,(461)399-6120,kiara96@meadows.biz,2022-08-04,https://www.reeves.com/
17,83fDDE5812EbEde,Dale,Bishop,Shaw-Ray,Lake Miranda,Congo,(082)202-9241,+1-413-610-1854x79378,bhoward@hodges.biz,2023-05-08,http://www.escobar.com/
18,8dB7ACe2C5758cE,Priscilla,Mills,Pope PLC,North Cliffordshire,South Georgia and the South Sandwich Islands,+1-465-802-4573x30191,044-393-6049x598,nathaniel28@hess.com,2020-09-08,https://www.watts.org/
19,c1092ebDaF2ceED,Alice,Bond,Chan-Liu,West Wesley,Lithuania,+1-333-285-8515,(715)759-3158x77103,jose92@peters.com,2024-05-30,http://www.marquez.com/
20,4d0c95579D095aF,Cole,Compton,Calderon PLC,Robertaville,Saudi Arabia,029.862.3387x470,001-319-474-2394,sspence@dorsey.com,2020-08-02,http://hamilton.net/
Can't render this file because it contains an unexpected character in line 3 and column 36.
@@ -0,0 +1,21 @@
Index,Customer Id,First Name,Last Name,Company,City,Country,Phone 1,Phone 2,Email,Subscription Date,Website
1,50ddd99fAdF48B3,Jessica,Navarro,"Tran, Odom and Graham",Port Terranceview,Jersey,(883)287-7947,479-044-3187,wmcintyre@myers.net,2023-03-11,http://pennington.com/
2,BD1AB97979DDcDe,Preston,Andrews,"Townsend, Lawrence and Davenport",Harringtonmouth,Croatia,188-270-5676x63873,001-428-148-4270,bradley63@jacobs.net,2023-11-15,http://burke.com/
3,0FDDD9aCa501acE,Melissa,Mckay,Rivas-Cooke,Oliviabury,Cook Islands,8257631531,001-650-689-5600x927,ugalloway@maxwell-mcclain.com,2025-01-25,https://patrick.com/
4,A7dA284B7c4AfaD,Frank,Livingston,"Lambert, Garner and Mathews",Mallorytown,Faroe Islands,766.182.6201x689,+1-476-912-2027x4370,tracie00@webster.biz,2021-03-27,https://www.hart.com/
5,38bb1624e4f8211,Ross,Mendoza,Cook and Sons,New Perry,Bolivia,001-440-468-2236,165.562.4328x9274,austincarmen@abbott.com,2024-12-12,http://harmon.com/
6,55c9Fda2BABFE1a,Jane,Tate,Knapp PLC,West Kathyshire,Morocco,001-606-447-0584x07975,(003)872-1307x285,carlosgomez@mcpherson-ramos.biz,2021-09-13,http://www.yu.com/
7,A15eccAc67cCc5b,Tanya,Chung,Frost Inc,South Julieburgh,United States of America,778-360-8301x268,+1-034-169-4237,onewton@crosby.com,2024-10-01,http://www.gill-velez.biz/
8,aFdab04acACaf90,Jackson,Moyer,Bowen Ltd,Port Triciabury,Bahrain,001-187-882-2891x169,154.903.2525,gabriela16@mejia.org,2023-06-20,http://www.david.biz/
9,A2EaeAd3c6529B9,Kelly,Douglas,Phillips PLC,Weissberg,Qatar,767.177.3199x5576,5156730935,wigginsmercedes@tanner.com,2021-06-30,https://gonzalez.net/
10,188175A5641d080,Mariah,Riddle,"Mata, Fuller and Good",Choiton,Namibia,092-993-3559x342,190.937.3937,eileen32@greer.biz,2021-04-17,http://www.bowen.info/
11,B2BA817C7bC09f0,Kristine,Pennington,"Koch, Diaz and Valdez",Port Rachael,Suriname,877.152.6488x921,790-804-9979x3290,tonya00@flynn.com,2024-03-28,https://www.rocha.net/
12,fFAD70B5Febc43a,Robert,Adams,Bender-Wolfe,Billfort,Tanzania,219-696-1912x93626,518-969-4058,jeffersoncolton@moss-ashley.com,2022-07-31,https://www.alvarez.com/
13,9a44524E1261Ed6,Franklin,Costa,Werner Group,North Bradley,Cook Islands,(895)448-4681x1999,648-345-3083x88242,marksmith@novak.com,2024-12-03,http://thornton.com/
14,0C360EfE17D06cc,Dan,Ballard,Sampson-Huff,Michaelchester,Sao Tome and Principe,+1-803-469-5237x2580,(729)574-6101x0605,molly54@acevedo.info,2022-10-24,https://www.sloan-gonzales.biz/
15,fb5cE8cb1eF2954,Clifford,Moyer,Fry-Whitehead,Lake Gary,Japan,(504)263-0450,247.514.1614x551,rhonda34@orr.com,2020-03-08,https://norton.com/
16,3e53DcaD1cB0054,Tonya,Durham,"Lowe, West and Reynolds",South Sylviaton,Brunei Darussalam,(613)893-8183,(461)399-6120,kiara96@meadows.biz,2022-08-04,https://www.reeves.com/
17,83fDDE5812EbEde,Dale,Bishop,Shaw-Ray,Lake Miranda,Congo,(082)202-9241,+1-413-610-1854x79378,bhoward@hodges.biz,2023-05-08,http://www.escobar.com/
18,8dB7ACe2C5758cE,Priscilla,Mills,Pope PLC,North Cliffordshire,South Georgia and the South Sandwich Islands,+1-465-802-4573x30191,044-393-6049x598,nathaniel28@hess.com,2020-09-08,https://www.watts.org/
19,c1092ebDaF2ceED,Alice,Bond,Chan-Liu,West Wesley,Lithuania,+1-333-285-8515,(715)759-3158x77103,jose92@peters.com,2024-05-30,http://www.marquez.com/
20,4d0c95579D095aF,Cole,Compton,Calderon PLC,Robertaville,Saudi Arabia,029.862.3387x470,001-319-474-2394,sspence@dorsey.com,2020-08-02,http://hamilton.net/
1 Index Customer Id First Name Last Name Company City Country Phone 1 Phone 2 Email Subscription Date Website
2 1 50ddd99fAdF48B3 Jessica Navarro Tran, Odom and Graham Port Terranceview Jersey (883)287-7947 479-044-3187 wmcintyre@myers.net 2023-03-11 http://pennington.com/
3 2 BD1AB97979DDcDe Preston Andrews Townsend, Lawrence and Davenport Harringtonmouth Croatia 188-270-5676x63873 001-428-148-4270 bradley63@jacobs.net 2023-11-15 http://burke.com/
4 3 0FDDD9aCa501acE Melissa Mckay Rivas-Cooke Oliviabury Cook Islands 8257631531 001-650-689-5600x927 ugalloway@maxwell-mcclain.com 2025-01-25 https://patrick.com/
5 4 A7dA284B7c4AfaD Frank Livingston Lambert, Garner and Mathews Mallorytown Faroe Islands 766.182.6201x689 +1-476-912-2027x4370 tracie00@webster.biz 2021-03-27 https://www.hart.com/
6 5 38bb1624e4f8211 Ross Mendoza Cook and Sons New Perry Bolivia 001-440-468-2236 165.562.4328x9274 austincarmen@abbott.com 2024-12-12 http://harmon.com/
7 6 55c9Fda2BABFE1a Jane Tate Knapp PLC West Kathyshire Morocco 001-606-447-0584x07975 (003)872-1307x285 carlosgomez@mcpherson-ramos.biz 2021-09-13 http://www.yu.com/
8 7 A15eccAc67cCc5b Tanya Chung Frost Inc South Julieburgh United States of America 778-360-8301x268 +1-034-169-4237 onewton@crosby.com 2024-10-01 http://www.gill-velez.biz/
9 8 aFdab04acACaf90 Jackson Moyer Bowen Ltd Port Triciabury Bahrain 001-187-882-2891x169 154.903.2525 gabriela16@mejia.org 2023-06-20 http://www.david.biz/
10 9 A2EaeAd3c6529B9 Kelly Douglas Phillips PLC Weissberg Qatar 767.177.3199x5576 5156730935 wigginsmercedes@tanner.com 2021-06-30 https://gonzalez.net/
11 10 188175A5641d080 Mariah Riddle Mata, Fuller and Good Choiton Namibia 092-993-3559x342 190.937.3937 eileen32@greer.biz 2021-04-17 http://www.bowen.info/
12 11 B2BA817C7bC09f0 Kristine Pennington Koch, Diaz and Valdez Port Rachael Suriname 877.152.6488x921 790-804-9979x3290 tonya00@flynn.com 2024-03-28 https://www.rocha.net/
13 12 fFAD70B5Febc43a Robert Adams Bender-Wolfe Billfort Tanzania 219-696-1912x93626 518-969-4058 jeffersoncolton@moss-ashley.com 2022-07-31 https://www.alvarez.com/
14 13 9a44524E1261Ed6 Franklin Costa Werner Group North Bradley Cook Islands (895)448-4681x1999 648-345-3083x88242 marksmith@novak.com 2024-12-03 http://thornton.com/
15 14 0C360EfE17D06cc Dan Ballard Sampson-Huff Michaelchester Sao Tome and Principe +1-803-469-5237x2580 (729)574-6101x0605 molly54@acevedo.info 2022-10-24 https://www.sloan-gonzales.biz/
16 15 fb5cE8cb1eF2954 Clifford Moyer Fry-Whitehead Lake Gary Japan (504)263-0450 247.514.1614x551 rhonda34@orr.com 2020-03-08 https://norton.com/
17 16 3e53DcaD1cB0054 Tonya Durham Lowe, West and Reynolds South Sylviaton Brunei Darussalam (613)893-8183 (461)399-6120 kiara96@meadows.biz 2022-08-04 https://www.reeves.com/
18 17 83fDDE5812EbEde Dale Bishop Shaw-Ray Lake Miranda Congo (082)202-9241 +1-413-610-1854x79378 bhoward@hodges.biz 2023-05-08 http://www.escobar.com/
19 18 8dB7ACe2C5758cE Priscilla Mills Pope PLC North Cliffordshire South Georgia and the South Sandwich Islands +1-465-802-4573x30191 044-393-6049x598 nathaniel28@hess.com 2020-09-08 https://www.watts.org/
20 19 c1092ebDaF2ceED Alice Bond Chan-Liu West Wesley Lithuania +1-333-285-8515 (715)759-3158x77103 jose92@peters.com 2024-05-30 http://www.marquez.com/
21 20 4d0c95579D095aF Cole Compton Calderon PLC Robertaville Saudi Arabia 029.862.3387x470 001-319-474-2394 sspence@dorsey.com 2020-08-02 http://hamilton.net/
+2 -2
View File
@@ -22,7 +22,7 @@ services:
restart: always
ports:
- 16005:3306
- "16014:22"
- "16015:22"
mysql-ssh-keyfile:
build: containers/mysql-ssh-keyfile
@@ -54,7 +54,7 @@ services:
image: mcr.microsoft.com/mssql/server
restart: always
ports:
- 16012:1433
- 16014:1433
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=Pwd2020Db
+11 -2
View File
@@ -1,4 +1,5 @@
CONNECTIONS=mysql,postgres,mssql,oracle,sqlite
CONNECTIONS=mysql,postgres,mssql,oracle,sqlite,mongo
LOG_CONNECTION_SENSITIVE_VALUES=true
LABEL_mysql=MySql-connection
SERVER_mysql=localhost
@@ -32,5 +33,13 @@ PORT_mssql=16014
ENGINE_mssql=mssql@dbgate-plugin-mssql
LABEL_sqlite=Sqlite-connection
FILE_sqlite=my_guitar_shop.db
FILE_sqlite=%%E2E_TEST_DATA_DIRECTORY%%/my_guitar_shop.db
ENGINE_sqlite=sqlite@dbgate-plugin-sqlite
LABEL_mongo=Mongo-connection
SERVER_mongo=localhost
USER_mongo=root
PASSWORD_mongo=Pwd2020Db
PORT_mongo=16010
ENGINE_mongo=mongo@dbgate-plugin-mongo
+22 -2
View File
@@ -8,12 +8,13 @@ dbgateApi.registerPlugins(dbgatePluginMysql);
const dbgatePluginPostgres = require('dbgate-plugin-postgres');
dbgateApi.registerPlugins(dbgatePluginPostgres);
async function createDb(connection, dropDbSql, createDbSql, database = 'my_guitar_shop') {
async function createDb(connection, dropDbSql, createDbSql, database = 'my_guitar_shop', { dropDatabaseName } = {}) {
if (dropDbSql) {
try {
await dbgateApi.executeQuery({
connection,
sql: dropDbSql,
database: dropDatabaseName,
});
} catch (err) {
console.error('Failed to drop database', err);
@@ -97,7 +98,10 @@ async function run() {
if (localconfig.sqlite) {
await createDb(
{
databaseFile: process.env.FILE_sqlite,
databaseFile: process.env.FILE_sqlite.replace(
'%%E2E_TEST_DATA_DIRECTORY%%',
path.join(path.dirname(__dirname), 'tmpdata')
),
singleDatabase: true,
engine: 'sqlite@dbgate-plugin-sqlite',
},
@@ -105,6 +109,22 @@ async function run() {
null
);
}
if (localconfig.mongo) {
await createDb(
{
server: process.env.SERVER_mongo,
user: process.env.USER_mongo,
password: process.env.PASSWORD_mongo,
port: process.env.PORT_mongo,
engine: 'mongo@dbgate-plugin-mongo',
},
'db.dropDatabase()',
null,
'my_guitar_shop',
{ dropDatabaseName: 'my_guitar_shop' }
);
}
}
dbgateApi.runScript(run);
View File
+35 -26
View File
@@ -27,11 +27,11 @@ services:
# - MYSQL_ROOT_PASSWORD=Pwd2020Db
#
cassandradb:
image: cassandra:5.0.2
ports:
- 15942:9042
# cassandradb:
# image: cassandra:5.0.2
# ports:
# - 15942:9042
#
# clickhouse:
# image: bitnami/clickhouse:24.8.4
# restart: always
@@ -55,28 +55,37 @@ services:
# ports:
# - 15003:26257
# command: start-single-node --insecure
# mongodb:
# image: mongo:4.0.12
# restart: always
# volumes:
# - mongo-data:/data/db
# - mongo-config:/data/configdb
# ports:
# - 27017:27017
# mongodb:
# image: mongo:4.0.12
# restart: always
# volumes:
# - mongo-data:/data/db
# - mongo-config:/data/configdb
# ports:
# - 27017:27017
# cockroachdb-init:
# image: cockroachdb/cockroach
# # build: cockroach
# # entrypoint: /cockroach/init.sh
# entrypoint: ./cockroach sql --insecure --host="cockroachdb" --execute="CREATE DATABASE IF NOT EXISTS test;"
# cockroachdb-init:
# image: cockroachdb/cockroach
# # build: cockroach
# # entrypoint: /cockroach/init.sh
# entrypoint: ./cockroach sql --insecure --host="cockroachdb" --execute="CREATE DATABASE IF NOT EXISTS test;"
# depends_on:
# - cockroachdb
# restart: on-failure
# depends_on:
# - cockroachdb
# restart: on-failure
oracle:
image: gvenzl/oracle-xe:21-slim
environment:
ORACLE_PASSWORD: Pwd2020Db
# oracle:
# image: gvenzl/oracle-xe:21-slim
# environment:
# ORACLE_PASSWORD: Pwd2020Db
# ports:
# - 15006:1521
libsql:
image: ghcr.io/tursodatabase/libsql-server:latest
platform: linux/amd64
ports:
- 15006:1521
- '8080:8080'
- '5002:5001'
volumes:
- ./data/libsql:/var/lib/sqld
+22 -1
View File
@@ -506,6 +506,23 @@ const sqliteEngine = {
],
};
const libsqlFileEngine = {
...sqliteEngine,
label: 'LibSQL FILE',
connection: {
engine: 'libsql@dbgate-plugin-sqlite',
},
};
const libsqlWsEngine = {
...sqliteEngine,
label: 'LibSQL WS',
connection: {
engine: 'libsql@dbgate-plugin-sqlite',
databaseUrl: 'ws://localhost:8080',
},
};
/** @type {import('dbgate-types').TestEngineInfo} */
const cockroachDbEngine = {
label: 'CockroachDB',
@@ -644,6 +661,8 @@ const enginesOnCi = [
postgreSqlEngine,
sqlServerEngine,
sqliteEngine,
libsqlFileEngine,
libsqlWsEngine,
// cockroachDbEngine,
clickhouseEngine,
oracleEngine,
@@ -659,7 +678,9 @@ const enginesOnLocal = [
// sqlServerEngine,
// sqliteEngine,
// cockroachDbEngine,
clickhouseEngine,
// clickhouseEngine,
// libsqlFileEngine,
libsqlWsEngine,
// oracleEngine,
];
+5 -1
View File
@@ -67,7 +67,11 @@
"postinstall": "yarn resetPackagedPlugins && yarn build:lib && patch-package && yarn build:plugins:frontend",
"dbgate-serve": "node packages/dbgate/bin/dbgate-serve.js",
"workflows": "node common/processWorkflows.js",
"cy:open": "cd e2e-tests && yarn cy:open"
"cy:open": "cd e2e-tests && yarn cy:open",
"translations:extract": "node common/translations-cli/index.js extract",
"translations:add-missing": "node common/translations-cli/index.js add-missing",
"translations:remove-unused": "node common/translations-cli/index.js remove-unused",
"translations:check": "node common/translations-cli/index.js check"
},
"dependencies": {
"concurrently": "^5.1.0",
+5 -1
View File
@@ -62,7 +62,10 @@ function getPortalCollections() {
port: process.env[`PORT_${id}`],
databaseUrl: process.env[`URL_${id}`],
useDatabaseUrl: !!process.env[`URL_${id}`],
databaseFile: process.env[`FILE_${id}`],
databaseFile: process.env[`FILE_${id}`]?.replace(
'%%E2E_TEST_DATA_DIRECTORY%%',
path.join(path.dirname(path.dirname(__dirname)), 'e2e-tests', 'tmpdata')
),
socketPath: process.env[`SOCKET_PATH_${id}`],
serviceName: process.env[`SERVICE_NAME_${id}`],
authType: process.env[`AUTH_TYPE_${id}`] || (process.env[`SOCKET_PATH_${id}`] ? 'socket' : undefined),
@@ -77,6 +80,7 @@ function getPortalCollections() {
allowedDatabasesRegex: process.env[`ALLOWED_DATABASES_REGEX_${id}`],
parent: process.env[`PARENT_${id}`] || undefined,
useSeparateSchemas: !!process.env[`USE_SEPARATE_SCHEMAS_${id}`],
localDataCenter: process.env[`LOCAL_DATA_CENTER_${id}`],
// SSH tunnel
useSshTunnel: process.env[`USE_SSH_${id}`],
@@ -233,6 +233,7 @@ module.exports = {
dispatchDatabaseChangedEvent_meta: true,
dispatchDatabaseChangedEvent({ event, conid, database }) {
socket.emitChanged(event, { conid, database });
return null;
},
loadKeys_meta: true,
+6 -4
View File
@@ -1,7 +1,7 @@
const EnsureStreamHeaderStream = require('../utility/EnsureStreamHeaderStream');
const ColumnMapTransformStream = require('../utility/ColumnMapTransformStream');
const streamPipeline = require('../utility/streamPipeline');
const { getLogger, extractErrorLogData, RowProgressReporter } = require('dbgate-tools');
const { getLogger, extractErrorLogData, RowProgressReporter, extractErrorMessage } = require('dbgate-tools');
const logger = getLogger('copyStream');
const stream = require('stream');
@@ -11,7 +11,9 @@ class ReportingTransform extends stream.Transform {
this.reporter = reporter;
}
_transform(chunk, encoding, callback) {
this.reporter.add(1);
if (!chunk?.__isStreamHeader) {
this.reporter.add(1);
}
this.push(chunk);
callback();
}
@@ -66,7 +68,7 @@ async function copyStream(input, output, options) {
process.send({
msgtype: 'copyStreamError',
copyStreamError: {
message: err.message,
message: extractErrorMessage(err),
...err,
},
});
@@ -76,7 +78,7 @@ async function copyStream(input, output, options) {
msgtype: 'progress',
progressName,
status: 'error',
errorMessage: err.message,
errorMessage: extractErrorMessage(err),
});
}
+84 -69
View File
@@ -23,82 +23,97 @@ async function importDbFromFolder({ connection, systemConnection, driver, folder
const dbhan = systemConnection || (await connectUtility(driver, connection, 'read'));
try {
const model = await importDbModel(folder);
if (driver?.databaseEngineTypes?.includes('sql')) {
const model = await importDbModel(folder);
let modelAdapted = {
...model,
tables: model.tables.map(table => driver.adaptTableInfo(table)),
};
for (const transform of modelTransforms || []) {
modelAdapted = transform(modelAdapted);
}
const modelNoFk = {
...modelAdapted,
tables: modelAdapted.tables.map(table => ({
...table,
foreignKeys: [],
})),
};
// const plan = createAlterDatabasePlan(
// DatabaseAnalyser.createEmptyStructure(),
// driver.dialect.enableAllForeignKeys ? modelAdapted : modelNoFk,
// {},
// DatabaseAnalyser.createEmptyStructure(),
// driver.dialect.enableAllForeignKeys ? modelAdapted : modelNoFk,
// driver
// );
// const dmp1 = driver.createDumper({ useHardSeparator: true });
// if (driver.dialect.enableAllForeignKeys) {
// dmp1.enableAllForeignKeys(false);
// }
// plan.run(dmp1);
// if (driver.dialect.enableAllForeignKeys) {
// dmp1.enableAllForeignKeys(true);
// }
const { sql } = getAlterDatabaseScript(
DatabaseAnalyser.createEmptyStructure(),
driver.dialect.enableAllForeignKeys ? modelAdapted : modelNoFk,
{},
DatabaseAnalyser.createEmptyStructure(),
driver.dialect.enableAllForeignKeys ? modelAdapted : modelNoFk,
driver
);
// console.log('CREATING STRUCTURE:', sql);
await executeQuery({ connection, systemConnection: dbhan, driver, sql, logScriptItems: true });
if (driver.dialect.enableAllForeignKeys) {
await runCommandOnDriver(dbhan, driver, dmp => dmp.enableAllForeignKeys(false));
}
for (const table of modelAdapted.tables) {
const fileName = path.join(folder, `${table.pureName}.jsonl`);
if (await fs.exists(fileName)) {
const src = await jsonLinesReader({ fileName });
const dst = await tableWriter({
systemConnection: dbhan,
pureName: table.pureName,
driver,
targetTableStructure: table,
});
await copyStream(src, dst);
let modelAdapted = {
...model,
tables: model.tables.map(table => driver.adaptTableInfo(table)),
};
for (const transform of modelTransforms || []) {
modelAdapted = transform(modelAdapted);
}
const modelNoFk = {
...modelAdapted,
tables: modelAdapted.tables.map(table => ({
...table,
foreignKeys: [],
})),
};
// const plan = createAlterDatabasePlan(
// DatabaseAnalyser.createEmptyStructure(),
// driver.dialect.enableAllForeignKeys ? modelAdapted : modelNoFk,
// {},
// DatabaseAnalyser.createEmptyStructure(),
// driver.dialect.enableAllForeignKeys ? modelAdapted : modelNoFk,
// driver
// );
// const dmp1 = driver.createDumper({ useHardSeparator: true });
// if (driver.dialect.enableAllForeignKeys) {
// dmp1.enableAllForeignKeys(false);
// }
// plan.run(dmp1);
// if (driver.dialect.enableAllForeignKeys) {
// dmp1.enableAllForeignKeys(true);
// }
const { sql } = getAlterDatabaseScript(
DatabaseAnalyser.createEmptyStructure(),
driver.dialect.enableAllForeignKeys ? modelAdapted : modelNoFk,
{},
DatabaseAnalyser.createEmptyStructure(),
driver.dialect.enableAllForeignKeys ? modelAdapted : modelNoFk,
driver
);
// console.log('CREATING STRUCTURE:', sql);
await executeQuery({ connection, systemConnection: dbhan, driver, sql, logScriptItems: true });
if (driver.dialect.enableAllForeignKeys) {
await runCommandOnDriver(dbhan, driver, dmp => dmp.enableAllForeignKeys(false));
}
}
if (driver.dialect.enableAllForeignKeys) {
await runCommandOnDriver(dbhan, driver, dmp => dmp.enableAllForeignKeys(true));
} else if (driver.dialect.createForeignKey) {
const dmp = driver.createDumper();
for (const table of modelAdapted.tables) {
for (const fk of table.foreignKeys) {
dmp.createForeignKey(fk);
const fileName = path.join(folder, `${table.pureName}.jsonl`);
if (await fs.exists(fileName)) {
const src = await jsonLinesReader({ fileName });
const dst = await tableWriter({
systemConnection: dbhan,
pureName: table.pureName,
driver,
targetTableStructure: table,
});
await copyStream(src, dst);
}
}
// create foreign keys
await executeQuery({ connection, systemConnection: dbhan, driver, sql: dmp.s, logScriptItems: true });
if (driver.dialect.enableAllForeignKeys) {
await runCommandOnDriver(dbhan, driver, dmp => dmp.enableAllForeignKeys(true));
} else if (driver.dialect.createForeignKey) {
const dmp = driver.createDumper();
for (const table of modelAdapted.tables) {
for (const fk of table.foreignKeys) {
dmp.createForeignKey(fk);
}
}
// create foreign keys
await executeQuery({ connection, systemConnection: dbhan, driver, sql: dmp.s, logScriptItems: true });
}
} else if (driver?.databaseEngineTypes?.includes('document')) {
for (const file of fs.readdirSync(folder)) {
if (!file.endsWith('.jsonl')) continue;
const pureName = path.parse(file).name;
const src = await jsonLinesReader({ fileName: path.join(folder, file) });
const dst = await tableWriter({
systemConnection: dbhan,
pureName,
driver,
createIfNotExists: true,
});
await copyStream(src, dst);
}
}
} finally {
if (!systemConnection) {
+15 -1
View File
@@ -15,6 +15,7 @@ const logger = getLogger('tableWriter');
* @param {boolean} options.truncate - truncate table before insert
* @param {boolean} options.createIfNotExists - create table if not exists
* @param {boolean} options.commitAfterInsert - commit transaction after insert
* @param {string} options.progressName - name for reporting progress
* @param {any} options.targetTableStructure - target table structure (don't analyse if given)
* @returns {Promise<writerType>} - writer object
*/
@@ -26,7 +27,20 @@ async function tableWriter({ connection, schemaName, pureName, driver, systemCon
}
const dbhan = systemConnection || (await connectUtility(driver, connection, 'write'));
return await driver.writeTable(dbhan, { schemaName, pureName }, options);
try {
return await driver.writeTable(dbhan, { schemaName, pureName }, options);
} catch (err) {
if (options.progressName) {
process.send({
msgtype: 'progress',
progressName: options.progressName,
status: 'error',
errorMessage: err.message,
});
}
throw err;
}
}
module.exports = tableWriter;
+3
View File
@@ -88,6 +88,9 @@ function decryptConnection(connection) {
}
function pickSafeConnectionInfo(connection) {
if (process.env.LOG_CONNECTION_SENSITIVE_VALUES) {
return connection;
}
return _.mapValues(connection, (v, k) => {
if (k == 'engine' || k == 'port' || k == 'authType' || k == 'sshMode' || k == 'passwordMode') return v;
if (v === null || v === true || v === false) return v;
+1 -1
View File
@@ -128,7 +128,7 @@ export class ScriptDrivedDeployer {
logger.debug(`Running ${category} script ${file.name}`);
try {
await this.driver.script(this.dbhan, file.text);
await this.driver.script(this.dbhan, file.text, { useTransaction: false });
await this.saveToJournal(file, category, hash);
} catch (err) {
logger.error(extractErrorLogData(err), `Error running ${category} script ${file.name}`);
@@ -4,6 +4,7 @@ import _fromPairs from 'lodash/fromPairs';
import { getLogger } from './getLogger';
import { prepareTableForImport } from './tableTransforms';
import { RowProgressReporter } from './rowProgressReporter';
import { extractErrorLogData } from './stringTools';
const logger = getLogger('bulkStreamBase');
@@ -34,88 +35,100 @@ export function createBulkInsertStreamBase(driver: EngineDriver, stream, dbhan,
};
writable.checkStructure = async () => {
let structure = options.targetTableStructure ?? (await driver.analyseSingleTable(dbhan, name));
if (structure) {
writable.structure = structure;
}
if (structure && options.dropIfExists) {
logger.info(`Dropping table ${fullNameQuoted}`);
await driver.script(dbhan, `DROP TABLE ${fullNameQuoted}`);
}
if (options.createIfNotExists && (!structure || options.dropIfExists)) {
const dmp = driver.createDumper();
const createdTableInfo = driver.adaptTableInfo(prepareTableForImport({ ...writable.structure, ...name }));
dmp.createTable(createdTableInfo);
logger.info({ sql: dmp.s }, `Creating table ${fullNameQuoted}`);
await driver.script(dbhan, dmp.s);
structure = await driver.analyseSingleTable(dbhan, name);
writable.structure = structure;
}
if (!writable.structure) {
throw new Error(`Error importing table - ${fullNameQuoted} not found`);
}
if (options.truncate) {
await driver.script(dbhan, `TRUNCATE TABLE ${fullNameQuoted}`);
}
try {
let structure = options.targetTableStructure ?? (await driver.analyseSingleTable(dbhan, name));
if (structure) {
writable.structure = structure;
}
if (structure && options.dropIfExists) {
logger.info(`Dropping table ${fullNameQuoted}`);
await driver.script(dbhan, `DROP TABLE ${fullNameQuoted}`);
}
if (options.createIfNotExists && (!structure || options.dropIfExists)) {
const dmp = driver.createDumper();
const createdTableInfo = driver.adaptTableInfo(prepareTableForImport({ ...writable.structure, ...name }));
dmp.createTable(createdTableInfo);
logger.info({ sql: dmp.s }, `Creating table ${fullNameQuoted}`);
await driver.script(dbhan, dmp.s);
structure = await driver.analyseSingleTable(dbhan, name);
writable.structure = structure;
}
if (!writable.structure) {
throw new Error(`Error importing table - ${fullNameQuoted} not found`);
}
if (options.truncate) {
await driver.script(dbhan, `TRUNCATE TABLE ${fullNameQuoted}`);
}
writable.columnNames = _intersection(
structure.columns.map(x => x.columnName),
writable.structure.columns.map(x => x.columnName)
);
writable.columnDataTypes = _fromPairs(
writable.columnNames.map(colName => [
colName,
writable.structure.columns.find(x => x.columnName == colName)?.dataType,
])
);
writable.columnNames = _intersection(
structure.columns.map(x => x.columnName),
writable.structure.columns.map(x => x.columnName)
);
writable.columnDataTypes = _fromPairs(
writable.columnNames.map(colName => [
colName,
writable.structure.columns.find(x => x.columnName == colName)?.dataType,
])
);
} catch (err) {
logger.error(extractErrorLogData(err), 'Error during preparing bulk insert table, stopped');
writable.destroy(err);
}
};
writable.send = async () => {
const rows = writable.buffer;
writable.buffer = [];
if (driver.dialect.allowMultipleValuesInsert) {
const dmp = driver.createDumper();
dmp.putRaw(`INSERT INTO ${fullNameQuoted} (`);
dmp.putCollection(',', writable.columnNames, col => dmp.putRaw(driver.dialect.quoteIdentifier(col as string)));
dmp.putRaw(')\n VALUES\n');
let wasRow = false;
for (const row of rows) {
if (wasRow) dmp.putRaw(',\n');
dmp.putRaw('(');
dmp.putCollection(',', writable.columnNames, col =>
dmp.putValue(row[col as string], writable.columnDataTypes?.[col as string])
);
dmp.putRaw(')');
wasRow = true;
}
dmp.putRaw(';');
// require('fs').writeFileSync('/home/jena/test.sql', dmp.s);
// console.log(dmp.s);
await driver.query(dbhan, dmp.s, { discardResult: true });
writable.rowsReporter.add(rows.length);
} else {
for (const row of rows) {
try {
if (driver.dialect.allowMultipleValuesInsert) {
const dmp = driver.createDumper();
dmp.putRaw(`INSERT INTO ${fullNameQuoted} (`);
dmp.putCollection(',', writable.columnNames, col => dmp.putRaw(driver.dialect.quoteIdentifier(col as string)));
dmp.putRaw(')\n VALUES\n');
dmp.putRaw('(');
dmp.putCollection(',', writable.columnNames, col =>
dmp.putValue(row[col as string], writable.columnDataTypes?.[col as string])
);
dmp.putRaw(')');
let wasRow = false;
for (const row of rows) {
if (wasRow) dmp.putRaw(',\n');
dmp.putRaw('(');
dmp.putCollection(',', writable.columnNames, col =>
dmp.putValue(row[col as string], writable.columnDataTypes?.[col as string])
);
dmp.putRaw(')');
wasRow = true;
}
dmp.putRaw(';');
// require('fs').writeFileSync('/home/jena/test.sql', dmp.s);
// console.log(dmp.s);
await driver.query(dbhan, dmp.s, { discardResult: true });
writable.rowsReporter.add(1);
writable.rowsReporter.add(rows.length);
} else {
for (const row of rows) {
const dmp = driver.createDumper();
dmp.putRaw(`INSERT INTO ${fullNameQuoted} (`);
dmp.putCollection(',', writable.columnNames, col =>
dmp.putRaw(driver.dialect.quoteIdentifier(col as string))
);
dmp.putRaw(')\n VALUES\n');
dmp.putRaw('(');
dmp.putCollection(',', writable.columnNames, col =>
dmp.putValue(row[col as string], writable.columnDataTypes?.[col as string])
);
dmp.putRaw(')');
// console.log(dmp.s);
await driver.query(dbhan, dmp.s, { discardResult: true });
writable.rowsReporter.add(1);
}
}
}
if (options.commitAfterInsert) {
const dmp = driver.createDumper();
dmp.commitTransaction();
await driver.query(dbhan, dmp.s, { discardResult: true });
if (options.commitAfterInsert) {
const dmp = driver.createDumper();
dmp.commitTransaction();
await driver.query(dbhan, dmp.s, { discardResult: true });
}
} catch (err) {
logger.error(extractErrorLogData(err), 'Error during base bulk insert, insert stopped');
writable.destroy(err);
}
};
+1
View File
@@ -17,6 +17,7 @@ export interface SqlDialect {
defaultSchemaName?: string;
enableConstraintsPerTable?: boolean;
enableAllForeignKeys?: boolean;
enableForeignKeyChecks?: boolean;
requireStandaloneSelectForScopeIdentity?: boolean;
allowMultipleValuesInsert?: boolean;
+1
View File
@@ -186,6 +186,7 @@ export interface EngineDriver<TClient = any> extends FilterBehaviourProvider {
beforeConnectionSave?: (values: any) => any;
databaseUrlPlaceholder?: string;
defaultAuthTypeName?: string;
authTypeFirst?: boolean;
defaultLocalDataCenter?: string;
defaultSocketPath?: string;
authTypeLabel?: string;
-1
View File
@@ -115,7 +115,6 @@
<div></div>
<div></div>
</div>
<div>Loading DbGate App</div>
</div>
</div>
</body>
+2
View File
@@ -16,6 +16,7 @@
"@ant-design/colors": "^5.0.0",
"@mdi/font": "^7.1.96",
"@rollup/plugin-commonjs": "^20.0.0",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^13.0.5",
"@rollup/plugin-replace": "^3.0.0",
"@rollup/plugin-typescript": "^8.2.5",
@@ -57,6 +58,7 @@
"uuid": "^3.4.0"
},
"dependencies": {
"@messageformat/core": "^3.4.0",
"chartjs-plugin-zoom": "^1.2.0",
"date-fns": "^4.1.0",
"debug": "^4.3.4",
+2
View File
@@ -8,6 +8,7 @@ import sveltePreprocess from 'svelte-preprocess';
import typescript from '@rollup/plugin-typescript';
import replace from '@rollup/plugin-replace';
import css from 'rollup-plugin-css-only';
import json from '@rollup/plugin-json';
const production = !process.env.ROLLUP_WATCH;
@@ -121,6 +122,7 @@ export default [
sourceMap: !production,
inlineSources: !production,
}),
json(),
// In dev mode, call `npm run start` once
// the bundle has been generated
+7 -3
View File
@@ -22,6 +22,7 @@
import SettingsListener from './utility/SettingsListener.svelte';
import { handleAuthOnStartup } from './clientAuth';
import { initializeAppUpdates } from './utility/appUpdate';
import { _t } from './translations';
export let isAdminPage = false;
@@ -95,10 +96,13 @@
{:else}
<AppStartInfo
message={$loadingPluginStore.loadingPackageName
? `Loading plugin ${$loadingPluginStore.loadingPackageName} ...`
: 'Preparing plugins ...'}
? _t('app.loading_plugin', {
defaultMessage: `Loading plugin {plugin} ...`,
values: { plugin: $loadingPluginStore.loadingPackageName },
})
: _t('app.preparingPlugins', { defaultMessage: 'Preparing plugins ...' })}
/>
{/if}
{:else}
<AppStartInfo message="Starting DbGate" />
<AppStartInfo message={_t('app.starting', { defaultMessage: 'Starting DbGate' })} />
{/if}
@@ -67,7 +67,10 @@
const count = getOpenedTabs().filter(closeCondition).length;
if (count > 0) {
showModal(ConfirmModal, {
message: `Closing connection will close ${count} opened tabs, continue?`,
message: _t('connection.closeConfirm', {
defaultMessage: 'Closing connection will close {count} opened tabs, continue?',
values: { count },
}),
onConfirm: () => disconnectServerConnection(conid, false),
});
return;
@@ -137,6 +140,7 @@
import hasPermission from '../utility/hasPermission';
import { switchCurrentDatabase } from '../utility/common';
import { getConnectionClickActionSetting } from '../settings/settingsTools';
import { _t } from '../translations';
export let data;
export let passProps;
@@ -244,7 +248,10 @@
};
const handleDelete = () => {
showModal(ConfirmModal, {
message: `Really delete connection ${getConnectionLabel(data)}?`,
message: _t('connection.deleteConfirm', {
defaultMessage: 'Really delete connection {name}?',
values: { name: getConnectionLabel(data) },
}),
onConfirm: () => apiCall('connections/delete', data),
});
};
@@ -257,9 +264,9 @@
};
const handleCreateDatabase = () => {
showModal(InputTextModal, {
header: 'Create database',
header: _t('connection.createDatabase', { defaultMessage: 'Create database' }),
value: 'newdb',
label: 'Database name',
label: _t('connection.databaseName', { defaultMessage: 'Database name' }),
onConfirm: name =>
apiCall('server-connections/create-database', {
conid: data._id,
@@ -294,12 +301,12 @@
return [
!data.singleDatabase && [
!$openedConnections.includes(data._id) && {
text: 'Connect',
text: _t('connection.connect', { defaultMessage: 'Connect' }),
onClick: handleConnect,
isBold: true,
},
$openedConnections.includes(data._id) && {
text: 'Disconnect',
text: _t('connection.disconnect', { defaultMessage: 'Disconnect' }),
onClick: handleDisconnect,
},
],
@@ -307,35 +314,41 @@
config.runAsPortal == false &&
!config.storageDatabase && [
{
text: $openedConnections.includes(data._id) ? 'View details' : 'Edit',
text: $openedConnections.includes(data._id)
? _t('connection.viewDetails', { defaultMessage: 'View details' })
: _t('connection.edit', { defaultMessage: 'Edit' }),
onClick: handleOpenConnectionTab,
},
!$openedConnections.includes(data._id) && {
text: 'Delete',
text: _t('connection.delete', { defaultMessage: 'Delete' }),
onClick: handleDelete,
},
{
text: 'Duplicate',
text: _t('connection.duplicate', { defaultMessage: 'Duplicate' }),
onClick: handleDuplicate,
},
],
{ divider: true },
!data.singleDatabase && [
hasPermission(`dbops/query`) && { onClick: handleNewQuery, text: 'New Query (server)', isNewQuery: true },
hasPermission(`dbops/query`) && {
onClick: handleNewQuery,
text: _t('connection.newQuery', { defaultMessage: 'New Query (server)' }),
isNewQuery: true,
},
$openedConnections.includes(data._id) &&
data.status && {
text: 'Refresh',
text: _t('connection.refresh', { defaultMessage: 'Refresh' }),
onClick: handleRefresh,
},
hasPermission(`dbops/createdb`) &&
$openedConnections.includes(data._id) &&
driver?.supportedCreateDatabase &&
!data.isReadOnly && {
text: 'Create database',
text: _t('connection.createDatabase', { defaultMessage: 'Create database' }),
onClick: handleCreateDatabase,
},
driver?.supportsServerSummary && {
text: 'Server summary',
text: _t('connection.serverSummary', { defaultMessage: 'Server summary' }),
onClick: handleServerSummary,
},
],
@@ -352,7 +365,10 @@
],
driver?.databaseEngineTypes?.includes('sql') &&
!data.isReadOnly && { onClick: handleSqlRestore, text: 'Restore/import SQL dump' },
!data.isReadOnly && {
onClick: handleSqlRestore,
text: _t('connection.sqlRestore', { defaultMessage: 'Restore/import SQL dump' }),
},
];
};
@@ -365,7 +381,11 @@
} else {
extInfo = data.engine;
engineStatusIcon = 'img warn';
engineStatusTitle = `Engine driver ${data.engine} not found, review installed plugins and change engine in edit connection dialog`;
engineStatusTitle = _t('connection.engineDriverNotFound', {
defaultMessage:
'Engine driver {engine} not found, review installed plugins and change engine in edit connection dialog',
values: { engine: data.engine },
});
}
}
+29 -41
View File
@@ -4,7 +4,7 @@
registerCommand({
id: 'dataGrid.refresh',
category: 'Data grid',
name: 'Refresh',
name: _t('common.refresh', { defaultMessage: 'Refresh' }),
keyText: 'F5 | CtrlOrCommand+R',
toolbar: true,
isRelatedToTab: true,
@@ -28,7 +28,7 @@
registerCommand({
id: 'dataGrid.revertRowChanges',
category: 'Data grid',
name: 'Revert row changes',
name: _t('command.datagrid.revertRowChanges', { defaultMessage: 'Revert row changes' }),
keyText: 'CtrlOrCommand+U',
testEnabled: () => getCurrentDataGrid()?.getGrider()?.containsChanges,
onClick: () => getCurrentDataGrid().revertRowChanges(),
@@ -37,8 +37,8 @@
registerCommand({
id: 'dataGrid.revertAllChanges',
category: 'Data grid',
name: 'Revert all changes',
toolbarName: 'Revert all',
name: _t('command.datagrid.revertAllChanges', { defaultMessage: 'Revert all changes' }),
toolbarName: _t('command.datagrid.revertAllChanges.toolbar', { defaultMessage: 'Revert all' }),
icon: 'icon undo',
testEnabled: () => getCurrentDataGrid()?.getGrider()?.containsChanges,
onClick: () => getCurrentDataGrid().revertAllChanges(),
@@ -47,8 +47,8 @@
registerCommand({
id: 'dataGrid.deleteSelectedRows',
category: 'Data grid',
name: 'Delete selected rows',
toolbarName: 'Delete row(s)',
name: _t('command.datagrid.deleteSelectedRows', { defaultMessage: 'Delete selected rows' }),
toolbarName: _t('command.datagrid.deleteSelectedRows.toolbar', { defaultMessage: 'Delete row(s)' }),
keyText: isMac() ? 'Command+Backspace' : 'CtrlOrCommand+Delete',
icon: 'icon minus',
testEnabled: () => getCurrentDataGrid()?.getGrider()?.editable,
@@ -58,8 +58,8 @@
registerCommand({
id: 'dataGrid.insertNewRow',
category: 'Data grid',
name: 'Insert new row',
toolbarName: 'New row',
name: _t('command.datagrid.insertNewRow', { defaultMessage: 'Insert new row' }),
toolbarName: _t('command.datagrid.insertNewRow.toolbar', { defaultMessage: 'New row' }),
icon: 'icon add',
keyText: isMac() ? 'Command+I' : 'Insert',
testEnabled: () => getCurrentDataGrid()?.getGrider()?.editable,
@@ -69,8 +69,8 @@
registerCommand({
id: 'dataGrid.addNewColumn',
category: 'Data grid',
name: 'Add new column',
toolbarName: 'New column',
name: _t('command.datagrid.addNewColumn', { defaultMessage: 'Add new column' }),
toolbarName: _t('command.datagrid.addNewColumn.toolbar', { defaultMessage: 'New column' }),
icon: 'icon add-column',
testEnabled: () => getCurrentDataGrid()?.addNewColumnEnabled(),
onClick: () => getCurrentDataGrid().addNewColumn(),
@@ -79,8 +79,8 @@
registerCommand({
id: 'dataGrid.cloneRows',
category: 'Data grid',
name: 'Clone rows',
toolbarName: 'Clone row(s)',
name: _t('command.datagrid.cloneRows', { defaultMessage: 'Clone rows' }),
toolbarName: _t('command.datagrid.cloneRows.toolbar', { defaultMessage: 'Clone row(s)' }),
keyText: 'CtrlOrCommand+Shift+C',
testEnabled: () => getCurrentDataGrid()?.getGrider()?.editable,
onClick: () => getCurrentDataGrid().cloneRows(),
@@ -89,7 +89,7 @@
registerCommand({
id: 'dataGrid.setNull',
category: 'Data grid',
name: 'Set NULL',
name: _t('command.datagrid.setNull', { defaultMessage: 'Set NULL' }),
keyText: 'CtrlOrCommand+0',
testEnabled: () =>
getCurrentDataGrid()?.getGrider()?.editable && !getCurrentDataGrid()?.getEditorTypes()?.supportFieldRemoval,
@@ -99,7 +99,7 @@
registerCommand({
id: 'dataGrid.removeField',
category: 'Data grid',
name: 'Remove field',
name: _t('command.datagrid.removeField', { defaultMessage: 'Remove field' }),
keyText: 'CtrlOrCommand+0',
testEnabled: () =>
getCurrentDataGrid()?.getGrider()?.editable && getCurrentDataGrid()?.getEditorTypes()?.supportFieldRemoval,
@@ -109,7 +109,7 @@
registerCommand({
id: 'dataGrid.undo',
category: 'Data grid',
name: 'Undo',
name: _t('command.datagrid.undo', { defaultMessage: 'Undo' }),
group: 'undo',
icon: 'icon undo',
toolbar: true,
@@ -121,7 +121,7 @@
registerCommand({
id: 'dataGrid.redo',
category: 'Data grid',
name: 'Redo',
name: _t('command.datagrid.redo', { defaultMessage: 'Redo' }),
group: 'redo',
icon: 'icon redo',
toolbar: true,
@@ -133,7 +133,7 @@
registerCommand({
id: 'dataGrid.reconnect',
category: 'Data grid',
name: 'Reconnect',
name: _t('command.datagrid.reconnect', { defaultMessage: 'Reconnect' }),
testEnabled: () => getCurrentDataGrid() != null,
onClick: () => getCurrentDataGrid().reconnect(),
});
@@ -141,7 +141,7 @@
registerCommand({
id: 'dataGrid.copyToClipboard',
category: 'Data grid',
name: 'Copy to clipboard',
name: _t('command.datagrid.copyToClipboard', { defaultMessage: 'Copy to clipboard' }),
keyText: 'CtrlOrCommand+C',
disableHandleKeyText: 'CtrlOrCommand+C',
testEnabled: () => getCurrentDataGrid() != null,
@@ -152,7 +152,7 @@
id: 'dataGrid.editJsonDocument',
category: 'Data grid',
keyText: 'CtrlOrCommand+J',
name: 'Edit row as JSON document',
name: _t('command.datagrid.editJsonDocument', { defaultMessage: 'Edit row as JSON document' }),
testEnabled: () => getCurrentDataGrid()?.editJsonEnabled(),
onClick: () => getCurrentDataGrid().editJsonDocument(),
});
@@ -160,15 +160,15 @@
registerCommand({
id: 'dataGrid.openSelectionInMap',
category: 'Data grid',
name: 'Open selection in map',
testEnabled: () => getCurrentDataGrid() != null, // ?.openSelectionInMapEnabled(),
name: _t('command.datagrid.openSelectionInMap', { defaultMessage: 'Open selection in map' }),
testEnabled: () => getCurrentDataGrid() != null,
onClick: () => getCurrentDataGrid().openSelectionInMap(),
});
registerCommand({
id: 'dataGrid.viewJsonDocument',
category: 'Data grid',
name: 'View row as JSON document',
name: _t('command.datagrid.viewJsonDocument', { defaultMessage: 'View row as JSON document' }),
testEnabled: () => getCurrentDataGrid()?.viewJsonDocumentEnabled(),
onClick: () => getCurrentDataGrid().viewJsonDocument(),
});
@@ -176,7 +176,7 @@
registerCommand({
id: 'dataGrid.viewJsonValue',
category: 'Data grid',
name: 'View cell as JSON document',
name: _t('command.datagrid.viewJsonValue', { defaultMessage: 'View cell as JSON document' }),
testEnabled: () => getCurrentDataGrid()?.viewJsonValueEnabled(),
onClick: () => getCurrentDataGrid().viewJsonValue(),
});
@@ -184,7 +184,7 @@
registerCommand({
id: 'dataGrid.openJsonArrayInSheet',
category: 'Data grid',
name: 'Open array as table',
name: _t('command.datagrid.openJsonArrayInSheet', { defaultMessage: 'Open array as table' }),
testEnabled: () => getCurrentDataGrid()?.openJsonArrayInSheetEnabled(),
onClick: () => getCurrentDataGrid().openJsonArrayInSheet(),
});
@@ -192,7 +192,7 @@
registerCommand({
id: 'dataGrid.saveCellToFile',
category: 'Data grid',
name: 'Save cell to file',
name: _t('command.datagrid.saveCellToFile', { defaultMessage: 'Save cell to file' }),
testEnabled: () => getCurrentDataGrid()?.saveCellToFileEnabled(),
onClick: () => getCurrentDataGrid().saveCellToFile(),
});
@@ -200,7 +200,7 @@
registerCommand({
id: 'dataGrid.loadCellFromFile',
category: 'Data grid',
name: 'Load cell from file',
name: _t('command.datagrid.loadCellFromFile', { defaultMessage: 'Load cell from file' }),
testEnabled: () => getCurrentDataGrid()?.loadCellFromFileEnabled(),
onClick: () => getCurrentDataGrid().loadCellFromFile(),
});
@@ -212,7 +212,8 @@
// testEnabled: () => getCurrentDataGrid()?.copyJsonEnabled(),
// onClick: () => getCurrentDataGrid().copyJsonDocument(),
// });
//
//
registerCommand({
id: 'dataGrid.filterSelected',
category: 'Data grid',
@@ -221,7 +222,6 @@
testEnabled: () => getCurrentDataGrid()?.getDisplay().filterable,
onClick: () => getCurrentDataGrid().filterSelectedValue(),
});
registerCommand({
id: 'dataGrid.findColumn',
category: 'Data grid',
@@ -230,7 +230,6 @@
testEnabled: () => getCurrentDataGrid() != null,
getSubCommands: () => getCurrentDataGrid().buildFindMenu(),
});
registerCommand({
id: 'dataGrid.hideColumn',
category: 'Data grid',
@@ -239,7 +238,6 @@
testEnabled: () => getCurrentDataGrid()?.canShowLeftPanel(),
onClick: () => getCurrentDataGrid().hideColumn(),
});
registerCommand({
id: 'dataGrid.clearFilter',
category: 'Data grid',
@@ -248,7 +246,6 @@
testEnabled: () => getCurrentDataGrid()?.clearFilterEnabled(),
onClick: () => getCurrentDataGrid().clearFilter(),
});
registerCommand({
id: 'dataGrid.generateSqlFromData',
category: 'Data grid',
@@ -257,7 +254,6 @@
testEnabled: () => getCurrentDataGrid()?.generateSqlFromDataEnabled(),
onClick: () => getCurrentDataGrid().generateSqlFromData(),
});
registerCommand({
id: 'dataGrid.openFreeTable',
category: 'Data grid',
@@ -265,7 +261,6 @@
testEnabled: () => getCurrentDataGrid() != null,
onClick: () => getCurrentDataGrid().openFreeTable(),
});
registerCommand({
id: 'dataGrid.openChartFromSelection',
category: 'Data grid',
@@ -273,7 +268,6 @@
testEnabled: () => getCurrentDataGrid() != null,
onClick: () => getCurrentDataGrid().openChartFromSelection(),
});
registerCommand({
id: 'dataGrid.newJson',
category: 'Data grid',
@@ -281,7 +275,6 @@
testEnabled: () => getCurrentDataGrid()?.addJsonDocumentEnabled(),
onClick: () => getCurrentDataGrid().addJsonDocument(),
});
registerCommand({
id: 'dataGrid.editCellValue',
category: 'Data grid',
@@ -289,7 +282,6 @@
testEnabled: () => getCurrentDataGrid()?.editCellValueEnabled(),
onClick: () => getCurrentDataGrid().editCellValue(),
});
registerCommand({
id: 'dataGrid.mergeSelectedCellsIntoMirror',
category: 'Data grid',
@@ -297,7 +289,6 @@
testEnabled: () => getCurrentDataGrid()?.mirrorWriteEnabled(true),
onClick: () => getCurrentDataGrid().mergeSelectionIntoMirror({ mergeMode: 'merge', fullRows: false }),
});
registerCommand({
id: 'dataGrid.mergeSelectedRowsIntoMirror',
category: 'Data grid',
@@ -305,7 +296,6 @@
testEnabled: () => getCurrentDataGrid()?.mirrorWriteEnabled(true),
onClick: () => getCurrentDataGrid().mergeSelectionIntoMirror({ mergeMode: 'merge', fullRows: true }),
});
registerCommand({
id: 'dataGrid.appendSelectedCellsIntoMirror',
category: 'Data grid',
@@ -313,7 +303,6 @@
testEnabled: () => getCurrentDataGrid()?.mirrorWriteEnabled(true),
onClick: () => getCurrentDataGrid().mergeSelectionIntoMirror({ mergeMode: 'append', fullRows: false }),
});
registerCommand({
id: 'dataGrid.appendSelectedRowsIntoMirror',
category: 'Data grid',
@@ -321,7 +310,6 @@
testEnabled: () => getCurrentDataGrid()?.mirrorWriteEnabled(true),
onClick: () => getCurrentDataGrid().mergeSelectionIntoMirror({ mergeMode: 'append', fullRows: true }),
});
registerCommand({
id: 'dataGrid.replaceSelectedCellsIntoMirror',
category: 'Data grid',
@@ -329,7 +317,6 @@
testEnabled: () => getCurrentDataGrid()?.mirrorWriteEnabled(true),
onClick: () => getCurrentDataGrid().mergeSelectionIntoMirror({ mergeMode: 'replace', fullRows: false }),
});
registerCommand({
id: 'dataGrid.replaceSelectedRowsIntoMirror',
category: 'Data grid',
@@ -430,6 +417,7 @@
import { openJsonLinesData } from '../utility/openJsonLinesData';
import contextMenuActivator from '../utility/contextMenuActivator';
import InputTextModal from '../modals/InputTextModal.svelte';
import { _t } from '../translations';
export let onLoadNextData = undefined;
export let grider = undefined;
@@ -7,6 +7,7 @@
import FormTextField from '../forms/FormTextField.svelte';
import FormSelectField from '../forms/FormSelectField.svelte';
import stableStringify from 'json-stable-stringify';
import { _t } from '../translations';
export let title;
export let fieldDefinitions;
@@ -42,7 +43,7 @@
<FormSelectField
isNative
name="schemaName"
label="Schema"
label={_t('common.schema', { defaultMessage: 'Schema' })}
options={schemaList.map(x => ({ label: x.schemaName, value: x.schemaName }))}
/>
{/if}
+2 -1
View File
@@ -15,7 +15,7 @@
registerCommand({
id: 'dataForm.refresh',
category: 'Data form',
name: 'Refresh',
name: _t('common.refresh', { defaultMessage: 'Refresh' }),
keyText: 'F5 | CtrlOrCommand+R',
toolbar: true,
isRelatedToTab: true,
@@ -197,6 +197,7 @@
import resizeObserver from '../utility/resizeObserver';
import openReferenceForm from './openReferenceForm';
import { useSettings } from '../utility/metadataLoaders';
import { _t } from '../translations';
export let conid;
export let database;
@@ -82,6 +82,7 @@
// export let openedFile = undefined;
export let previewReaderStore;
export let isTabActive;
export let isRunning = false;
const { values, setFieldValue } = getFormContext();
@@ -273,6 +274,7 @@
// @ts-ignore
e.target.value
)}
data-testid={`ImportExportConfigurator_targetName_${row}`}
/>
{#if $targetDbinfo}
<DropDownButton
@@ -305,7 +307,7 @@
</Link>
</svelte:fragment>
<svelte:fragment slot="3" let:row>
{#if progressHolder[row]?.status == 'running'}
{#if progressHolder[row]?.status == 'running' && isRunning}
<FontIcon icon="icon loading" />
{#if progressHolder[row]?.writtenRowCount}
{progressHolder[row]?.writtenRowCount} rows writtem
@@ -322,6 +324,7 @@
title={progressHolder[row]?.errorMessage}
on:click={() => showModal(ErrorMessageModal, { message: progressHolder[row]?.errorMessage })}
style="cursor: pointer"
data-testid={`ImportExportConfigurator_errorInfoIcon_${row}`}
/>
{/if}
{:else if progressHolder[row]?.status == 'done'}
@@ -334,7 +337,14 @@
Done
{/if}
{:else}
<FontIcon icon="icon wait" /> Queued
<FontIcon icon="icon wait" />
{#if progressHolder[row]?.writtenRowCount}
{progressHolder[row]?.writtenRowCount} rows writtem
{:else if progressHolder[row]?.readRowCount}
{progressHolder[row]?.readRowCount} rows read
{:else}
Queued
{/if}
{/if}
</svelte:fragment>
</TableControl>
@@ -22,6 +22,7 @@
import FormTablesSelect from './FormTablesSelect.svelte';
import { findEngineDriver } from 'dbgate-tools';
import AceEditor from '../query/AceEditor.svelte';
import { _t } from '../translations';
export let direction;
export let storageTypeField;
@@ -40,14 +41,22 @@
$values[storageTypeField] == 'jsldata'
? [{ value: 'jsldata', label: 'Query result data', directions: ['source'] }]
: [
{ value: 'database', label: 'Database', directions: ['source', 'target'] },
{
value: 'database',
label: _t('common.database', { defaultMessage: 'Database' }),
directions: ['source', 'target'],
},
...$extensions.fileFormats.map(format => ({
value: format.storageType,
label: `${format.name} files(s)`,
directions: getFileFormatDirections(format),
})),
{ value: 'query', label: 'Query', directions: ['source'] },
{ value: 'archive', label: 'Archive', directions: ['source', 'target'] },
{ value: 'query', label: _t('common.query', { defaultMessage: 'Query' }), directions: ['source'] },
{
value: 'archive',
label: _t('common.archive', { defaultMessage: 'Archive' }),
directions: ['source', 'target'],
},
];
$: storageType = $values[storageTypeField];
@@ -124,7 +133,7 @@
conidName={connectionIdField}
databaseName={databaseNameField}
name={schemaNameField}
label="Schema"
label={_t('common.schema', { defaultMessage: 'Schema' })}
/>
{#if tablesField}
<FormTablesSelect
@@ -132,12 +141,12 @@
schemaName={schemaNameField}
databaseName={databaseNameField}
name={tablesField}
label="Tables / views / collections"
label={_t('importExport.tablesViewsCollections', { defaultMessage: 'Tables / views / collections' })}
/>
{/if}
{/if}
{#if storageType == 'query'}
<div class="label">Query</div>
<div class="label">{_t('common.query', { defaultMessage: 'Query' })}</div>
<div class="sqlwrap">
{#if $values.sourceQueryType == 'json'}
<AceEditor value={$values.sourceQuery} on:input={e => setFieldValue('sourceQuery', e.detail)} mode="json" />
@@ -156,7 +165,11 @@
{/if}
{#if storageType == 'archive' && direction == 'source'}
<FormArchiveFilesSelect label="Source files" folderName={$values[archiveFolderField]} name={tablesField} />
<FormArchiveFilesSelect
label={_t('importExport.sourceFiles', { defaultMessage: 'Source files' })}
folderName={$values[archiveFolderField]}
name={tablesField}
/>
{/if}
{#if format && direction == 'source'}
+2 -1
View File
@@ -9,6 +9,7 @@
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal, showModal } from './modalTools';
import { _t } from '../translations';
export let onSave;
export let json;
@@ -43,7 +44,7 @@
<div slot="footer">
<FormStyledButton
value="Save"
value={_t('common.save', { defaultMessage: 'Save' })}
data-testid="EditJsonModal_saveButton"
on:click={() => {
try {
@@ -23,7 +23,7 @@
<div class="icon">
<FontIcon icon="img error" />
</div>
<div>
<div data-testid="ErrorMessageModal_message">
{message}
</div>
</div>
@@ -6,6 +6,7 @@
import FormSubmit from '../forms/FormSubmit.svelte';
import FormTextField from '../forms/FormTextField.svelte';
import { currentArchive } from '../stores';
import { _t } from '../translations';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
@@ -28,7 +29,7 @@
<FormTextField label="File name" name="file" />
<svelte:fragment slot="footer">
<FormSubmit value="Save" on:click={handleSubmit} />
<FormSubmit value={_t('common.save', { defaultMessage: 'Save' })} on:click={handleSubmit} />
</svelte:fragment>
</ModalBase>
</FormProvider>
+2 -1
View File
@@ -4,6 +4,7 @@
import FormProvider from '../forms/FormProvider.svelte';
import FormSubmit from '../forms/FormSubmit.svelte';
import FormTextField from '../forms/FormTextField.svelte';
import { _t } from '../translations';
import { apiCall } from '../utility/api';
import getElectron from '../utility/getElectron';
@@ -56,7 +57,7 @@
<svelte:fragment slot="header">Save file</svelte:fragment>
<FormTextField label="File name" name="name" focused />
<svelte:fragment slot="footer">
<FormSubmit value="Save" on:click={handleSubmit} />
<FormSubmit value={_t('common.save', { defaultMessage: 'Save' })} on:click={handleSubmit} />
{#if electron}
<FormStyledButton
type="button"
@@ -17,6 +17,7 @@
import FontIcon from '../icons/FontIcon.svelte';
import FormDropDownTextField from '../forms/FormDropDownTextField.svelte';
import { getConnectionLabel } from 'dbgate-tools';
import { _t } from '../translations';
export let getDatabaseList;
export let currentConnection;
@@ -153,6 +154,15 @@
/>
{/if}
{#if driver?.showConnectionField('authToken', $values, showConnectionFieldArgs)}
<FormTextField
label={_t('authToken', { defaultMessage: 'Auth token' })}
name="authToken"
data-testid="ConnectionDriverFields_authToken"
disabled={isConnected || disabledFields.includes('authToken')}
/>
{/if}
{#if $authTypes && driver?.showConnectionField('authType', $values, showConnectionFieldArgs)}
{#key $authTypes}
<FormSelectField
+30 -2
View File
@@ -16,7 +16,7 @@
import FontIcon from '../icons/FontIcon.svelte';
import ModalBase from '../modals/ModalBase.svelte';
import { closeCurrentModal } from '../modals/modalTools';
import { closeCurrentModal, showModal } from '../modals/modalTools';
import { EDITOR_KEYBINDINGS_MODES, EDITOR_THEMES, FONT_SIZES } from '../query/AceEditor.svelte';
import SqlEditor from '../query/SqlEditor.svelte';
import {
@@ -39,6 +39,9 @@
import { derived } from 'svelte/store';
import { safeFormatDate } from 'dbgate-tools';
import FormDefaultActionField from './FormDefaultActionField.svelte';
import { _t, getSelectedLanguage } from '../translations';
import { internalRedirectTo } from '../clientAuth';
import ConfirmModal from '../modals/ConfirmModal.svelte';
const electron = getElectron();
let restartWarning = false;
@@ -121,7 +124,32 @@ ORDER BY
{/if}
{/if}
<FormCheckboxField name="tabGroup.showServerName" label="Show server name alongside database name in title of the tab group" defaultValue={false} />
<FormCheckboxField
name="tabGroup.showServerName"
label="Show server name alongside database name in title of the tab group"
defaultValue={false}
/>
<div class="heading">{_t('settings.localization', { defaultMessage: 'Localization' })}</div>
<FormSelectField
label="Language"
name="localization.language"
defaultValue={getSelectedLanguage()}
isNative
options={[
{ value: 'en', label: 'English' },
{ value: 'cs', label: 'Czech' },
]}
on:change={() => {
showModal(ConfirmModal, {
message: 'Application will be reloaded to apply new language settings',
onConfirm: () => {
setTimeout(() => {
internalRedirectTo('/');
}, 100);
},
});
}}
/>
<div class="heading">Data grid</div>
<FormTextField
@@ -11,6 +11,7 @@
import { closeCurrentModal } from '../modals/modalTools';
import DataTypeEditor from './DataTypeEditor.svelte';
import { editorAddColumn, editorDeleteColumn, editorModifyColumn, fillEditorColumnInfo } from 'dbgate-tools';
import { _t } from '../translations';
export let columnInfo;
export let setTableInfo = null;
@@ -75,7 +76,7 @@
{#if !columnInfo}
<FormButton
type="button"
value="Save"
value={_t('common.save', { defaultMessage: 'Save' })}
disabled={isReadOnly}
on:click={e => {
closeCurrentModal();
@@ -17,6 +17,7 @@
import TextField from '../forms/TextField.svelte';
import SelectField from '../forms/SelectField.svelte';
import _ from 'lodash';
import { _t } from '../translations';
export let constraintInfo;
export let setTableInfo;
@@ -204,7 +205,7 @@
<svelte:fragment slot="footer">
<FormSubmit
value="Save"
value={_t('common.save', { defaultMessage: 'Save' })}
disabled={isReadOnly}
on:click={() => {
closeCurrentModal();
+12 -10
View File
@@ -1,4 +1,6 @@
<script lang="ts" context="module">
import { _t } from '../translations';
const getCurrentValueMarker: any = {};
export function shouldShowTab(tab, lockedDbModeArg = getCurrentValueMarker, currentDbArg = getCurrentValueMarker) {
@@ -184,8 +186,8 @@
};
function getTabDbName(tab, connectionList) {
if (tab.tabComponent == 'ConnectionTab') return 'Connections';
if (tab.tabComponent?.startsWith('Admin')) return 'Administration';
if (tab.tabComponent == 'ConnectionTab') return _t('common.connections', { defaultMessage: 'Connections' });
if (tab.tabComponent?.startsWith('Admin')) return _t('tab.administration', { defaultMessage: 'Administration' });
if (tab.props && tab.props.conid && tab.props.database) return tab.props.database;
if (tab.props && tab.props.conid) {
const connection = connectionList?.find(x => x._id == tab.props.conid);
@@ -232,7 +234,7 @@
registerCommand({
id: 'tabs.nextTab',
category: 'Tabs',
name: 'Next tab',
name: _t('command.tabs.nextTab', { defaultMessage: 'Next tab' }),
keyText: 'Ctrl+Tab',
testEnabled: () => getOpenedTabs().filter(x => !x.closedTime).length >= 2,
onClick: () => switchTabByOrder(false),
@@ -241,7 +243,7 @@
registerCommand({
id: 'tabs.previousTab',
category: 'Tabs',
name: 'Previous tab',
name: _t('command.tabs.previousTab', { defaultMessage: 'Previous tab' }),
keyText: 'Ctrl+Shift+Tab',
testEnabled: () => getOpenedTabs().filter(x => !x.closedTime).length >= 2,
onClick: () => switchTabByOrder(true),
@@ -250,7 +252,7 @@
registerCommand({
id: 'tabs.closeAll',
category: 'Tabs',
name: 'Close all tabs',
name: _t('command.tabs.closeAll', { defaultMessage: 'Close all tabs' }),
testEnabled: () => getOpenedTabs().filter(x => !x.closedTime).length >= 1,
onClick: closeAll,
});
@@ -258,7 +260,7 @@
registerCommand({
id: 'tabs.closeTab',
category: 'Tabs',
name: 'Close tab',
name: _t('command.tabs.closeTab', { defaultMessage: 'Close tab' }),
keyText: isElectronAvailable() ? 'CtrlOrCommand+W' : 'CtrlOrCommand+Shift+W',
testEnabled: () => {
const hasAnyOtherTab = getOpenedTabs().filter(x => !x.closedTime).length >= 1;
@@ -272,7 +274,7 @@
registerCommand({
id: 'tabs.closeTabsWithCurrentDb',
category: 'Tabs',
name: 'Close tabs with current DB',
name: _t('command.tabs.closeTabsWithCurrentDb', { defaultMessage: 'Close tabs with current DB' }),
testEnabled: () => getOpenedTabs().filter(x => !x.closedTime).length >= 1 && !!getCurrentDatabase(),
onClick: closeTabsWithCurrentDb,
});
@@ -280,7 +282,7 @@
registerCommand({
id: 'tabs.closeTabsButCurrentDb',
category: 'Tabs',
name: 'Close tabs but current DB',
name: _t('command.tabs.closeTabsButCurrentDb', { defaultMessage: 'Close tabs but current DB' }),
testEnabled: () => getOpenedTabs().filter(x => !x.closedTime).length >= 1 && !!getCurrentDatabase(),
onClick: closeTabsButCurrentDb,
});
@@ -288,7 +290,7 @@
registerCommand({
id: 'tabs.reopenClosedTab',
category: 'Tabs',
name: 'Reopen closed tab',
name: _t('command.tabs.reopenClosedTab', { defaultMessage: 'Reopen closed tab' }),
keyText: 'CtrlOrCommand+Shift+T',
testEnabled: () => getOpenedTabs().filter(x => x.closedTime).length >= 1,
onClick: reopenClosedTab,
@@ -297,7 +299,7 @@
registerCommand({
id: 'tabs.addToFavorites',
category: 'Tabs',
name: 'Add current tab to favorites',
name: _t('command.tabs.addToFavorites', { defaultMessage: 'Add current tab to favorites' }),
// icon: 'icon favorite',
// toolbar: true,
testEnabled: () =>
+6 -1
View File
@@ -35,6 +35,7 @@
import { useConfig } from '../utility/metadataLoaders';
import ConnectionAdvancedDriverFields from '../settings/ConnectionAdvancedDriverFields.svelte';
import DatabaseLoginModal from '../modals/DatabaseLoginModal.svelte';
import { _t } from '../translations';
export let connection;
export let tabid;
@@ -289,7 +290,11 @@
{:else}
<FormButton value="Test" on:click={() => handleTest(false)} data-testid="ConnectionTab_buttonTest" />
{/if}
<FormButton value="Save" on:click={handleSave} data-testid="ConnectionTab_buttonSave" />
<FormButton
value={_t('common.save', { defaultMessage: 'Save' })}
on:click={handleSave}
data-testid="ConnectionTab_buttonSave"
/>
{/if}
</div>
<div class="test-result">
+7 -2
View File
@@ -35,6 +35,7 @@
import { changeTab } from '../utility/common';
import SelectField from '../forms/SelectField.svelte';
import DbKeyValueDetail from '../dbkeyvalue/DbKeyValueDetail.svelte';
import { _t } from '../translations';
export let tabid;
export let conid;
@@ -134,12 +135,16 @@
</div>
<FormStyledButton value={`TTL:${keyInfo.ttl}`} on:click={() => handleChangeTtl(keyInfo)} />
{#if keyInfo.type == 'string'}
<FormStyledButton value="Save" on:click={saveString} disabled={!editedValue} />
<FormStyledButton
value={_t('common.save', { defaultMessage: 'Save' })}
on:click={saveString}
disabled={!editedValue}
/>
{/if}
{#if keyInfo.keyType?.addMethod && keyInfo.keyType?.showItemList}
<FormStyledButton value="Add item" on:click={() => addItem(keyInfo)} />
{/if}
<FormStyledButton value="Refresh" on:click={refresh} />
<FormStyledButton value={_t('common.refresh', { defaultMessage: 'Refresh' })} on:click={refresh} />
</div>
<div class="content">
+22 -5
View File
@@ -264,6 +264,7 @@
{previewReaderStore}
{progressHolder}
isTabActive={tabid == $activeTabId}
isRunning={busy}
/>
{#if busy}
@@ -273,7 +274,12 @@
<svelte:fragment slot="2">
<WidgetColumnBar>
<WidgetColumnBarItem title="Output files" name="output" height="20%">
<WidgetColumnBarItem
title="Output files"
name="output"
height="20%"
data-testid="ImportExportTab_outputFiles"
>
<RunnerOutputFiles {runnerId} {executeNumber} />
</WidgetColumnBarItem>
<WidgetColumnBarItem title="Messages" name="messages">
@@ -284,7 +290,12 @@
showCaller
/>
</WidgetColumnBarItem>
<WidgetColumnBarItem title="Preview" name="preview" skip={!$previewReaderStore}>
<WidgetColumnBarItem
title="Preview"
name="preview"
skip={!$previewReaderStore}
data-testid="ImportExportTab_preview"
>
<PreviewDataGrid reader={$previewReaderStore} />
</WidgetColumnBarItem>
<WidgetColumnBarItem title="Advanced configuration" name="config" collapsed>
@@ -297,11 +308,17 @@
</FormProviderCore>
<svelte:fragment slot="toolstrip">
{#if busy}
<ToolStripButton icon="icon stop" on:click={handleCancel}>Stop</ToolStripButton>
<ToolStripButton icon="icon stop" on:click={handleCancel} data-testid="ImportExportTab_stopButton"
>Stop</ToolStripButton
>
{:else}
<ToolStripButton on:click={handleExecute} icon="icon run">Run</ToolStripButton>
<ToolStripButton on:click={handleExecute} icon="icon run" data-testid="ImportExportTab_executeButton"
>Run</ToolStripButton
>
{/if}
<ToolStripButton icon="img shell" on:click={handleGenerateScript}>Generate script</ToolStripButton>
<ToolStripButton icon="img shell" on:click={handleGenerateScript} data-testid="ImportExportTab_generateScriptButton"
>Generate script</ToolStripButton
>
<ToolStripSaveButton idPrefix="job" />
</svelte:fragment>
</ToolStripContainer>
@@ -4,7 +4,7 @@
registerCommand({
id: 'serverSummary.refresh',
category: 'Server sumnmary',
name: 'Refresh',
name: _t('common.refresh', { defaultMessage: 'Refresh' }),
keyText: 'F5 | CtrlOrCommand+R',
toolbar: true,
isRelatedToTab: true,
@@ -22,6 +22,7 @@
import LoadingInfo from '../elements/LoadingInfo.svelte';
import ObjectListControl from '../elements/ObjectListControl.svelte';
import { _t } from '../translations';
import { apiCall } from '../utility/api';
import createActivator, { getActiveComponent } from '../utility/createActivator';
import formatFileSize from '../utility/formatFileSize';
+69
View File
@@ -0,0 +1,69 @@
import cs from '../../../translations/cs.json';
import MessageFormat, { MessageFunction } from '@messageformat/core';
import { getStringSettingsValue } from './settings/settingsTools';
const translations = {
en: {},
cs,
};
const supportedLanguages = Object.keys(translations);
const compiledMessages: Partial<Record<string, Record<string, MessageFunction<'string'>>>> = {};
const defaultLanguage = 'en';
export function getSelectedLanguage(): string {
const borwserLanguage = getBrowserLanguage();
const selectedLanguage = getStringSettingsValue('localization.language', borwserLanguage);
if (!supportedLanguages.includes(selectedLanguage)) return defaultLanguage;
return selectedLanguage;
}
export function getBrowserLanguage(): string {
if (typeof window !== 'undefined') {
return (
(navigator.languages && navigator.languages[0]).slice(0, 2) || navigator.language.slice(0, 2) || defaultLanguage
);
}
return defaultLanguage;
}
type TranslateOptions = {
defaultMessage: string;
values?: Record<string, unknown>;
};
function getTranslation(key: string, defaultMessage: string, language: string) {
const selectedTranslations = translations[language] ?? {};
const translation = selectedTranslations[key];
if (!translation) {
console.warn(`Translation not found for key: ${key}. For language: ${language}`);
return defaultMessage;
}
return translation;
}
export function _t(key: string, options: TranslateOptions): string {
const { defaultMessage, values } = options;
const selectedLanguage = getSelectedLanguage();
if (!compiledMessages[selectedLanguage]) {
compiledMessages[selectedLanguage] = {};
}
if (!compiledMessages[selectedLanguage][key]) {
const translation = getTranslation(key, defaultMessage, selectedLanguage);
const complied = new MessageFormat(selectedLanguage).compile(translation);
compiledMessages[selectedLanguage][key] = complied;
}
const compliledTranslation = compiledMessages[selectedLanguage][key];
return compliledTranslation(values ?? {});
}
@@ -248,6 +248,7 @@
handleDropOnGroup(data, '');
}
}}
data-testid="ConnectionList_container"
>
<AppObjectListHandler
bind:this={domListHandler}
+21 -8
View File
@@ -15,6 +15,7 @@
import SingleConnectionDatabaseList from './SingleConnectionDatabaseList.svelte';
import _ from 'lodash';
import FocusedConnectionInfoWidget from './FocusedConnectionInfoWidget.svelte';
import { _t } from '../translations';
export let hidden = false;
let domSqlObjectList = null;
@@ -29,16 +30,26 @@
<WidgetColumnBar {hidden}>
{#if $config?.singleConnection}
<WidgetColumnBarItem title="Databases" name="databases" height="35%" storageName="databasesWidget">
<WidgetColumnBarItem
title={_t('widget.databases', { defaultMessage: 'Databases' })}
name="databases"
height="35%"
storageName="databasesWidget"
>
<SingleConnectionDatabaseList connection={$config?.singleConnection} />
</WidgetColumnBarItem>
{:else if !$config?.singleDbConnection}
<WidgetColumnBarItem title="Connections" name="connections" height="35%" storageName="connectionsWidget">
<WidgetColumnBarItem
title={_t('common.connections', { defaultMessage: 'Connections' })}
name="connections"
height="35%"
storageName="connectionsWidget"
>
<ConnectionList passProps={{ onFocusSqlObjectList: () => domSqlObjectList.focus() }} />
</WidgetColumnBarItem>
{/if}
<WidgetColumnBarItem
title="Pinned"
title={_t('widget.pinned', { defaultMessage: 'Pinned' })}
name="pinned"
height="15%"
storageName="pinnedItemsWidget"
@@ -51,7 +62,7 @@
<WidgetColumnBarItem
title={driver?.databaseEngineTypes?.includes('document')
? (driver?.collectionPluralLabel ?? 'Collections/containers')
: 'Tables, views, functions'}
: _t('widget.tablesViewsFunctions', { defaultMessage: 'Tables, views, functions' })}
name="dbObjects"
storageName="dbObjectsWidget"
skip={!(
@@ -64,7 +75,7 @@
</WidgetColumnBarItem>
<WidgetColumnBarItem
title={'Keys'}
title={_t('widget.keys', { defaultMessage: 'Keys' })}
name="dbObjects"
storageName="dbObjectsWidget"
skip={!(conid && (database || singleDatabase) && driver?.databaseEngineTypes?.includes('keyvalue'))}
@@ -73,7 +84,7 @@
</WidgetColumnBarItem>
<WidgetColumnBarItem
title="Database content"
title={_t('widget.databaseContent', { defaultMessage: 'Database content' })}
name="dbObjects"
storageName="dbObjectsWidget"
skip={conid && (database || singleDatabase)}
@@ -86,7 +97,7 @@
</WidgetColumnBarItem>
<WidgetColumnBarItem
title="Database content"
title={_t('widget.databaseContent', { defaultMessage: 'Database content' })}
name="dbObjects"
storageName="dbObjectsWidget"
skip={!(conid && (database || singleDatabase) && !driver)}
@@ -94,7 +105,9 @@
<WidgetsInnerContainer>
<FocusedConnectionInfoWidget {conid} {database} connection={$connection} />
<ErrorInfo message="Invalid database connection, driver not found" />
<ErrorInfo
message={_t('error.driverNotFound', { defaultMessage: 'Invalid database connection, driver not found' })}
/>
</WidgetsInnerContainer>
</WidgetColumnBarItem>
</WidgetColumnBar>
@@ -11,6 +11,7 @@
import hasPermission from '../utility/hasPermission';
import { useFavorites } from '../utility/metadataLoaders';
import { _t } from '../translations';
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
+19 -7
View File
@@ -11,6 +11,7 @@
import { appliedCurrentSchema, currentDatabase } from '../stores';
import { switchCurrentDatabase } from '../utility/common';
import { extractDbNameFromComposite, extractSchemaNameFromComposite, findDefaultSchema } from 'dbgate-tools';
import { _t } from '../translations';
export let schemaList;
export let objectList;
@@ -58,9 +59,9 @@
function handleCreateSchema() {
showModal(InputTextModal, {
header: 'Create schema',
header: _t('schema.createSchema', { defaultMessage: 'Create schema' }),
value: 'newschema',
label: 'Schema name',
label: _t('schema.schemaName', { defaultMessage: 'Schema name' }),
onConfirm: async name => {
const dbid = { conid, database };
await runOperationOnDatabase(
@@ -105,13 +106,19 @@
{#if realSchemaList.length > 0}
<div class="wrapper" class:negativeMarginTop>
<div class="mr-1">Schema:</div>
<div class="mr-1">{_t('common.schema', { defaultMessage: 'Schema' })}:</div>
<SelectField
isNative
options={connection?.useSeparateSchemas
? (schemaList?.map(x => ({ label: x.schemaName, value: x.schemaName })) ?? [])
: [
{ label: `All schemas (${objectList?.length ?? 0})`, value: '' },
{
label: _t('schema.all_schemas', {
defaultMessage: 'All schemas ({count})',
values: { count: objectList?.length ?? 0 },
}),
value: '',
},
...realSchemaList.map(x => ({ label: `${x} (${countBySchema[x] ?? 0})`, value: x })),
]}
value={selectedSchema ?? $appliedCurrentSchema ?? ''}
@@ -135,15 +142,20 @@
selectedSchema = null;
localStorage.removeItem(valueStorageKey);
}}
title="Reset to default"
title={_t('schema.resetToDefault', { defaultMessage: 'Reset to default' })}
>
<FontIcon icon="icon close" />
</InlineButton>
{/if}
<InlineButton on:click={handleCreateSchema} title="Add new schema" square>
<InlineButton on:click={handleCreateSchema} title={_t('schema.add', { defaultMessage: 'Add new schema' })} square>
<FontIcon icon="icon plus-thick" />
</InlineButton>
<InlineButton on:click={handleDropSchema} title="Delete schema" square disabled={!$appliedCurrentSchema}>
<InlineButton
on:click={handleDropSchema}
title={_t('schema.delete', { defaultMessage: 'Delete schema' })}
square
disabled={!$appliedCurrentSchema}
>
<FontIcon icon="icon minus-thick" />
</InlineButton>
</div>
@@ -76,7 +76,11 @@
>
{#if visible}
<div class="wrapper" style={$dynamicProps.splitterVisible ? `height:${size}px` : 'flex: 1 1 0'}>
<div
class="wrapper"
style={$dynamicProps.splitterVisible ? `height:${size}px` : 'flex: 1 1 0'}
data-testid={$$props['data-testid'] ? `${$$props['data-testid']}_content` : undefined}
>
<slot />
</div>
+2 -1
View File
@@ -5,6 +5,7 @@
"exclude": ["node_modules/*", "public/*"],
"compilerOptions": {
"resolveJsonModule": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
@@ -12,7 +13,7 @@
"noImplicitAny": false,
"strictNullChecks": false,
"strict": false,
"target": "es6",
"target": "es6"
// "allowJs": true,
// "checkJs": true,
}
@@ -44,9 +44,17 @@ const driver = {
analyserClass: Analyser,
// creating connection
async connect({ server, user, password, database, localDataCenter, useDatabaseUrl, databaseUrl }) {
let credentials;
if (user && password) {
credentials = {
username: user,
password,
}
}
const client = new cassandra.Client({
// user,
// password,
credentials,
contactPoints: server.split(','),
localDataCenter: localDataCenter ?? this.defaultLocalDataCenter,
keyspace: database,
@@ -1,10 +1,9 @@
const ObjectId = require('mongodb').ObjectId;
const { getLogger } = global.DBGATE_PACKAGES['dbgate-tools'];
const { getLogger, extractErrorLogData } = global.DBGATE_PACKAGES['dbgate-tools'];
const { EJSON } = require('bson');
const logger = getLogger('mongoBulkInsert');
function createBulkInsertStream(driver, stream, dbhan, name, options) {
const collectionName = name.pureName;
const db = dbhan.getDatabase();
@@ -31,21 +30,31 @@ function createBulkInsertStream(driver, stream, dbhan, name, options) {
};
writable.checkStructure = async () => {
if (options.dropIfExists) {
logger.info(`Dropping collection ${collectionName}`);
await db.collection(collectionName).drop();
}
if (options.truncate) {
logger.info(`Truncating collection ${collectionName}`);
await db.collection(collectionName).deleteMany({});
try {
if (options.dropIfExists) {
logger.info(`Dropping collection ${collectionName}`);
await db.collection(collectionName).drop();
}
if (options.truncate) {
logger.info(`Truncating collection ${collectionName}`);
await db.collection(collectionName).deleteMany({});
}
} catch (err) {
logger.error(extractErrorLogData(err), 'Error during preparing mongo bulk insert collection, stopped');
writable.destroy(err);
}
};
writable.send = async () => {
const rows = writable.buffer;
writable.buffer = [];
try {
const rows = writable.buffer;
writable.buffer = [];
await db.collection(collectionName).insertMany(rows);
await db.collection(collectionName).insertMany(rows);
} catch (err) {
logger.error(extractErrorLogData(err), 'Error bulk insert collection, stopped');
writable.destroy(err);
}
};
writable.sendIfFull = async () => {
@@ -1,14 +1,15 @@
const { createBulkInsertStreamBase } = global.DBGATE_PACKAGES['dbgate-tools'];
const { createBulkInsertStreamBase, getLogger, extractErrorLogData } = global.DBGATE_PACKAGES['dbgate-tools'];
const tedious = require('tedious');
const getConcreteType = require('./getConcreteType');
const _ = require('lodash');
const logger = getLogger('tediousBulkInsertStream');
function runBulkInsertBatch(dbhan, tableName, writable, rows) {
return new Promise((resolve, reject) => {
var options = { keepNulls: true };
const options = { keepNulls: true };
// instantiate - provide the table where you'll be inserting to, options and a callback
var bulkLoad = dbhan.client.newBulkLoad(tableName, options, (error, rowCount) => {
const bulkLoad = dbhan.client.newBulkLoad(tableName, options, (error, rowCount) => {
if (error) reject(error);
else resolve();
});
@@ -51,7 +52,7 @@ function runBulkInsertBatch(dbhan, tableName, writable, rows) {
function createTediousBulkInsertStream(driver, stream, dbhan, name, options) {
const writable = createBulkInsertStreamBase(driver, stream, dbhan, name, options);
const fullName = name.schemaName ? `[${name.schemaName}].[${name.pureName}]` : name.pureName;
const fullName = name.schemaName ? `[${name.schemaName}].[${name.pureName}]` : `[${name.pureName}]`;
writable.send = async () => {
if (!writable.templateColumns) {
@@ -68,7 +69,13 @@ function createTediousBulkInsertStream(driver, stream, dbhan, name, options) {
const rows = writable.buffer;
writable.buffer = [];
await runBulkInsertBatch(dbhan, fullName, writable, rows);
try {
await runBulkInsertBatch(dbhan, fullName, writable, rows);
} catch (err) {
logger.error(extractErrorLogData(err), 'Error during bulk insert, insert stopped');
// writable.emit('error', err);
writable.destroy(err);
}
};
return writable;
@@ -40,6 +40,7 @@
"dbgate-query-splitter": "^4.11.3"
},
"optionalDependencies": {
"libsql": "0.5.0-pre.6",
"better-sqlite3": "11.8.1"
}
}
@@ -0,0 +1,181 @@
// @ts-check
const _ = require('lodash');
const stream = require('stream');
const sqliteDriver = require('./driver.sqlite');
const driverBases = require('../frontend/drivers');
const Analyser = require('./Analyser');
const { splitQuery, sqliteSplitterOptions } = require('dbgate-query-splitter');
const { runStreamItem, waitForDrain } = require('./helpers');
const { getLogger, createBulkInsertStreamBase, extractErrorLogData } = global.DBGATE_PACKAGES['dbgate-tools'];
const logger = getLogger('sqliteDriver');
let libsqlValue;
function getLibsql() {
if (!libsqlValue) {
libsqlValue = require('libsql');
}
return libsqlValue;
}
function extractColumns(row) {
if (!row) return [];
const columns = Object.keys(row).map((columnName) => ({ columnName }));
return columns;
}
/** @type {import('dbgate-types').EngineDriver<import('libsql').Database>} */
const libsqlDriver = {
...driverBases[1],
analyserClass: Analyser,
async connect({ databaseFile, isReadOnly, authToken, databaseUrl, ...rest }) {
console.log('connect', databaseFile, isReadOnly, authToken, databaseUrl, rest);
const Database = getLibsql();
const client = databaseFile
? new Database(databaseFile, { readonly: !!isReadOnly })
: new Database(databaseUrl, { authToken, readonly: !!isReadOnly });
return {
client,
};
},
async close(dbhan) {
// sqlite close is sync, returns this
dbhan.client.close();
},
// @ts-ignore
async query(dbhan, sql) {
const stmt = dbhan.client.prepare(sql);
const rows = stmt.all();
const stmtColumns = stmt.columns();
const columns = stmtColumns.length > 0 ? stmtColumns : extractColumns(rows[0]);
return {
rows,
columns: columns.map((col) => ({
columnName: col.name,
dataType: col.type,
})),
};
},
async stream(dbhan, sql, options) {
const sqlSplitted = splitQuery(sql, sqliteSplitterOptions);
const rowCounter = { count: 0, date: null };
console.log('#stream', sql);
const inTransaction = dbhan.client.transaction(() => {
for (const sqlItem of sqlSplitted) {
runStreamItem(dbhan, sqlItem, options, rowCounter);
}
if (rowCounter.date) {
options.info({
message: `${rowCounter.count} rows affected`,
time: new Date(),
severity: 'info',
});
}
});
try {
inTransaction();
} catch (error) {
logger.error(extractErrorLogData(error), 'Stream error');
const { message, procName } = error;
options.info({
message,
line: 0,
procedure: procName,
time: new Date(),
severity: 'error',
});
}
options.done();
// return stream;
},
async script(dbhan, sql, { useTransaction } = {}) {
const runScript = () => {
for (const sqlItem of splitQuery(sql, this.getQuerySplitterOptions('script'))) {
const stmt = dbhan.client.prepare(sqlItem);
stmt.run();
}
};
if (useTransaction) {
dbhan.client.transaction(() => {
runScript();
})();
} else {
runScript();
}
},
async readQueryTask(stmt, pass) {
// let sent = 0;
for (const row of stmt.iterate()) {
// sent++;
if (!pass.write(row)) {
// console.log('WAIT DRAIN', sent);
await waitForDrain(pass);
}
}
pass.end();
},
async readQuery(dbhan, sql, structure) {
const pass = new stream.PassThrough({
objectMode: true,
highWaterMark: 100,
});
const stmt = dbhan.client.prepare(sql);
const columns = stmt.columns();
pass.write({
__isStreamHeader: true,
...(structure || {
columns: columns.map((col) => ({
columnName: col.name,
dataType: col.type,
})),
}),
});
this.readQueryTask(stmt, pass);
return pass;
},
async writeTable(dbhan, name, options) {
return createBulkInsertStreamBase(this, stream, dbhan, name, options);
},
async getVersion(dbhan) {
const { rows } = await this.query(dbhan, 'select sqlite_version() as version');
const { version } = rows[0];
return {
version,
versionText: `SQLite ${version}`,
};
},
getAuthTypes() {
const res = [
{
title: 'File',
name: 'file',
disabledFields: ['databaseUrl', 'authToken'],
},
{
title: 'URL',
name: 'url',
disabledFields: ['databaseFile'],
},
];
return res;
},
};
module.exports = libsqlDriver;
@@ -1,9 +1,11 @@
// @ts-check
const _ = require('lodash');
const stream = require('stream');
const driverBase = require('../frontend/driver');
const Analyser = require('./Analyser');
const driverBases = require('../frontend/drivers');
const { splitQuery, sqliteSplitterOptions } = require('dbgate-query-splitter');
const { getLogger, createBulkInsertStreamBase, extractErrorLogData } = global.DBGATE_PACKAGES['dbgate-tools'];
const { runStreamItem, waitForDrain } = require('./helpers');
const logger = getLogger('sqliteDriver');
@@ -15,50 +17,9 @@ function getBetterSqlite() {
return betterSqliteValue;
}
async function waitForDrain(stream) {
return new Promise((resolve) => {
stream.once('drain', () => {
// console.log('CONTINUE DRAIN');
resolve();
});
});
}
function runStreamItem(dbhan, sql, options, rowCounter) {
const stmt = dbhan.client.prepare(sql);
if (stmt.reader) {
const columns = stmt.columns();
// const rows = stmt.all();
options.recordset(
columns.map((col) => ({
columnName: col.name,
dataType: col.type,
}))
);
for (const row of stmt.iterate()) {
options.row(row);
}
} else {
const info = stmt.run();
rowCounter.count += info.changes;
if (!rowCounter.date) rowCounter.date = new Date().getTime();
if (new Date().getTime() > rowCounter.date > 1000) {
options.info({
message: `${rowCounter.count} rows affected`,
time: new Date(),
severity: 'info',
});
rowCounter.count = 0;
rowCounter.date = null;
}
}
}
/** @type {import('dbgate-types').EngineDriver} */
const driver = {
...driverBase,
...driverBases[0],
analyserClass: Analyser,
async connect({ databaseFile, isReadOnly }) {
const Database = getBetterSqlite();
@@ -186,6 +147,4 @@ const driver = {
},
};
driver.initialize = (dbgateEnv) => {};
module.exports = driver;
@@ -0,0 +1,9 @@
//R@ts-check
const sqliteDriver = require('./driver.sqlite');
const libsqlDriver = require('./driver.libsql');
const drivers = [sqliteDriver, libsqlDriver];
drivers.initialize = (dbgateEnv) => {};
module.exports = drivers;
@@ -0,0 +1,49 @@
// @ts-check
function runStreamItem(dbhan, sql, options, rowCounter) {
const stmt = dbhan.client.prepare(sql);
console.log(stmt);
console.log(stmt.reader);
if (stmt.reader) {
const columns = stmt.columns();
// const rows = stmt.all();
options.recordset(
columns.map((col) => ({
columnName: col.name,
dataType: col.type,
}))
);
for (const row of stmt.iterate()) {
options.row(row);
}
} else {
const info = stmt.run();
rowCounter.count += info.changes;
if (!rowCounter.date) rowCounter.date = new Date().getTime();
if (new Date().getTime() - rowCounter.date > 1000) {
options.info({
message: `${rowCounter.count} rows affected`,
time: new Date(),
severity: 'info',
});
rowCounter.count = 0;
rowCounter.date = null;
}
}
}
async function waitForDrain(stream) {
return new Promise((resolve) => {
stream.once('drain', () => {
// console.log('CONTINUE DRAIN');
resolve();
});
});
}
module.exports = {
runStreamItem,
waitForDrain,
};
@@ -1,9 +1,9 @@
const driver = require('./driver');
const drivers = require('./drivers');
module.exports = {
packageName: 'dbgate-plugin-sqlite',
drivers: [driver],
drivers: drivers,
initialize(dbgateEnv) {
driver.initialize(dbgateEnv);
drivers.initialize(dbgateEnv);
},
};
@@ -1,3 +1,5 @@
// @ts-check
const { driverBase } = global.DBGATE_PACKAGES['dbgate-tools'];
const Dumper = require('./Dumper');
const { sqliteSplitterOptions, noSplitSplitterOptions } = require('dbgate-query-splitter/lib/options');
@@ -69,4 +71,44 @@ const driver = {
predefinedDataTypes: ['integer', 'real', 'text', 'blob'],
};
module.exports = driver;
/** @type {import('dbgate-types').EngineDriver} */
const libsqlDriver = {
...driverBase,
dumperClass: Dumper,
dialect,
engine: 'libsql@dbgate-plugin-sqlite',
title: 'LibSQL',
readOnlySessions: true,
supportsTransactions: true,
showConnectionField: (field, values) => {
if ((values?.authType ?? 'url') === 'url') {
return ['databaseUrl', 'authToken', 'isReadOnly', 'authType'].includes(field);
}
return ['databaseFile', 'isReadOnly', 'authType'].includes(field);
},
showConnectionTab: (field) => false,
defaultAuthTypeName: 'url',
authTypeFirst: true,
beforeConnectionSave: (connection) => ({
...connection,
singleDatabase: true,
defaultDatabase: getDatabaseFileLabel(connection.databaseFile || connection.databaseUrl),
}),
getQuerySplitterOptions: (usage) =>
usage == 'editor'
? { ...sqliteSplitterOptions, ignoreComments: true, preventSingleLineSplit: true }
: usage == 'stream'
? noSplitSplitterOptions
: sqliteSplitterOptions,
// isFileDatabase: true,
// isElectronOnly: true,
predefinedDataTypes: ['integer', 'real', 'text', 'blob'],
};
module.exports = [driver, libsqlDriver];
@@ -1,6 +1,6 @@
import driver from './driver';
import drivers from './drivers';
export default {
packageName: 'dbgate-plugin-sqlite',
drivers: [driver],
drivers: drivers,
};
+1
View File
@@ -0,0 +1 @@
{}
+71
View File
@@ -0,0 +1,71 @@
{
"app.preparingPlugins": "Preparing plugins ...",
"app.starting": "Starting DbGate",
"authToken": "Auth token",
"command.datagrid.addNewColumn": "Add new column",
"command.datagrid.addNewColumn.toolbar": "New column",
"command.datagrid.cloneRows": "Clone rows",
"command.datagrid.cloneRows.toolbar": "Clone row(s)",
"command.datagrid.copyToClipboard": "Copy to clipboard",
"command.datagrid.deleteSelectedRows": "Delete selected rows",
"command.datagrid.deleteSelectedRows.toolbar": "Delete row(s)",
"command.datagrid.editJsonDocument": "Edit row as JSON document",
"command.datagrid.insertNewRow": "Insert new row",
"command.datagrid.insertNewRow.toolbar": "New row",
"command.datagrid.loadCellFromFile": "Load cell from file",
"command.datagrid.openJsonArrayInSheet": "Open array as table",
"command.datagrid.openSelectionInMap": "Open selection in map",
"command.datagrid.reconnect": "Reconnect",
"command.datagrid.redo": "Redo",
"command.datagrid.removeField": "Remove field",
"command.datagrid.revertAllChanges": "Revert all changes",
"command.datagrid.revertAllChanges.toolbar": "Revert all",
"command.datagrid.revertRowChanges": "Revert row changes",
"command.datagrid.saveCellToFile": "Save cell to file",
"command.datagrid.setNull": "Set NULL",
"command.datagrid.undo": "Undo",
"command.datagrid.viewJsonDocument": "View row as JSON document",
"command.datagrid.viewJsonValue": "View cell as JSON document",
"command.tabs.addToFavorites": "Add current tab to favorites",
"command.tabs.closeAll": "Close all tabs",
"command.tabs.closeTab": "Close tab",
"command.tabs.closeTabsButCurrentDb": "Close tabs but current DB",
"command.tabs.closeTabsWithCurrentDb": "Close tabs with current DB",
"command.tabs.nextTab": "Next tab",
"command.tabs.previousTab": "Previous tab",
"command.tabs.reopenClosedTab": "Reopen closed tab",
"common.archive": "Archive",
"common.connections": "Connections",
"common.database": "Database",
"common.query": "Query",
"common.refresh": "Refresh",
"common.save": "Save",
"common.schema": "Schema",
"connection.connect": "Connect",
"connection.createDatabase": "Create database",
"connection.databaseName": "Database name",
"connection.delete": "Delete",
"connection.disconnect": "Disconnect",
"connection.duplicate": "Duplicate",
"connection.edit": "Edit",
"connection.newQuery": "New Query (server)",
"connection.refresh": "Refresh",
"connection.serverSummary": "Server summary",
"connection.sqlRestore": "Restore/import SQL dump",
"connection.viewDetails": "View details",
"error.driverNotFound": "Invalid database connection, driver not found",
"importExport.sourceFiles": "Source files",
"importExport.tablesViewsCollections": "Tables / views / collections",
"schema.add": "Add new schema",
"schema.createSchema": "Create schema",
"schema.delete": "Delete schema",
"schema.resetToDefault": "Reset to default",
"schema.schemaName": "Schema name",
"settings.localization": "Localization",
"tab.administration": "Administration",
"widget.databaseContent": "Database content",
"widget.databases": "Databases",
"widget.keys": "Keys",
"widget.pinned": "Pinned",
"widget.tablesViewsFunctions": "Tables, views, functions"
}
+1 -1
View File
@@ -7,7 +7,7 @@ checkout-and-merge-pro:
repository: dbgate/dbgate-pro
token: ${{ secrets.GH_TOKEN }}
path: dbgate-pro
ref: 6705e28d1de47cc8a11b3271cefaad714aa313d9
ref: 21048330597124a88fa1b8447e0bc18666eb69c5
- name: Merge dbgate/dbgate-pro
run: |
mkdir ../dbgate-pro
+8
View File
@@ -111,3 +111,11 @@ jobs:
image: cassandra:5.0.2
ports:
- 15942:9042
libsql:
image: ghcr.io/tursodatabase/libsql-server:latest
platform: linux/amd64
ports:
- '8080:8080'
volumes:
- ./data/libsql:/var/lib/sqld
+139 -1
View File
@@ -1703,11 +1703,82 @@
resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f"
integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==
"@libsql/darwin-arm64@0.5.0-pre.6":
version "0.5.0-pre.6"
resolved "https://registry.yarnpkg.com/@libsql/darwin-arm64/-/darwin-arm64-0.5.0-pre.6.tgz#1afb2e55aa25bd5fed6b37cbc2cbe9cfcca2e61e"
integrity sha512-T99Ap/ui7xqFe9ZjWUWRbSCqh9Bo/uZ/wOFtVi9U/2YlBdG4Vv2A7Uz1USYnivJm0nvyYjcy2N9enaRl2cyKkQ==
"@libsql/darwin-x64@0.5.0-pre.6":
version "0.5.0-pre.6"
resolved "https://registry.yarnpkg.com/@libsql/darwin-x64/-/darwin-x64-0.5.0-pre.6.tgz#0c410db574310ab32d52ee73ce218c7049d818ea"
integrity sha512-09fTHmTrxltuQ4oyM7RCz4qRF1oiZS9uf0IIIOI7do6dQu8830a7rrqhpg33LKBs9eBfKWuzpC6n8SuuatSFOw==
"@libsql/linux-arm64-gnu@0.5.0-pre.6":
version "0.5.0-pre.6"
resolved "https://registry.yarnpkg.com/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.5.0-pre.6.tgz#e220cc86924c8ef6b197033e0c161ef0fe327949"
integrity sha512-HDQH42ZxzhPMcFdcARV2I7oH7LK/jk2eqhODtIbnVn0kiklHY98F4wk1rbqFIzJljMuzq9HSycJtntUyubpnWg==
"@libsql/linux-arm64-musl@0.5.0-pre.6":
version "0.5.0-pre.6"
resolved "https://registry.yarnpkg.com/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.5.0-pre.6.tgz#41ba6db61e1ff64f29602c520d8e61903d2c143a"
integrity sha512-v6NmFwkQutzud5ZWbo0BWhfIe4OyfQ1qXq/uihpcLOjPUFyWl5vHelOQn1hflJeQ2PcaYxFQ6XPQimSs1HsqMw==
"@libsql/linux-x64-gnu@0.5.0-pre.6":
version "0.5.0-pre.6"
resolved "https://registry.yarnpkg.com/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.5.0-pre.6.tgz#2ce956c960eb54fa5a10d89856cf3d36bb9772b7"
integrity sha512-IOSlRJWNUoEdtL9Y2RrGJyNR4X9t2aWTcbVkYMRtKfDIqylYO8UXDtMLFdLLnjR4p5yZKnO9OqOkbMko8DKdOw==
"@libsql/linux-x64-musl@0.5.0-pre.6":
version "0.5.0-pre.6"
resolved "https://registry.yarnpkg.com/@libsql/linux-x64-musl/-/linux-x64-musl-0.5.0-pre.6.tgz#c5e58c067a52effd7ea119c58ca189596b28ae6e"
integrity sha512-BGICFHvEKIZtrD4UYjIg7SfGmak6zGRUAp/MVS+40FMe4eh7d6uvmQFs6JAaHErazLEKHLKbIKIYAXe1srHKVQ==
"@libsql/win32-x64-msvc@0.5.0-pre.6":
version "0.5.0-pre.6"
resolved "https://registry.yarnpkg.com/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.5.0-pre.6.tgz#0fe64844aaef3c5edb1c8e59f949c17ce6cad2ce"
integrity sha512-1RUqZ9wURWlHOXvafbnhRe2HGh+B7yfqvUQV3RWzS9c7oh1rJbeO7p0XDFFWyRjN8yYFbjlZDmRUcYr1Lo83qQ==
"@mdi/font@^7.1.96":
version "7.4.47"
resolved "https://registry.yarnpkg.com/@mdi/font/-/font-7.4.47.tgz#2ae522867da3a5c88b738d54b403eb91471903af"
integrity sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw==
"@messageformat/core@^3.4.0":
version "3.4.0"
resolved "https://registry.yarnpkg.com/@messageformat/core/-/core-3.4.0.tgz#2814c23383dec7bddf535d54f2a03e410165ca9f"
integrity sha512-NgCFubFFIdMWJGN5WuQhHCNmzk7QgiVfrViFxcS99j7F5dDS5EP6raR54I+2ydhe4+5/XTn/YIEppFaqqVWHsw==
dependencies:
"@messageformat/date-skeleton" "^1.0.0"
"@messageformat/number-skeleton" "^1.0.0"
"@messageformat/parser" "^5.1.0"
"@messageformat/runtime" "^3.0.1"
make-plural "^7.0.0"
safe-identifier "^0.4.1"
"@messageformat/date-skeleton@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@messageformat/date-skeleton/-/date-skeleton-1.1.0.tgz#3bad068cbf5873d14592cfc7a73dd4d8615e2739"
integrity sha512-rmGAfB1tIPER+gh3p/RgA+PVeRE/gxuQ2w4snFWPF5xtb5mbWR7Cbw7wCOftcUypbD6HVoxrVdyyghPm3WzP5A==
"@messageformat/number-skeleton@^1.0.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@messageformat/number-skeleton/-/number-skeleton-1.2.0.tgz#e7c245c41a1b2722bc59dad68f4d454f761bc9b4"
integrity sha512-xsgwcL7J7WhlHJ3RNbaVgssaIwcEyFkBqxHdcdaiJzwTZAWEOD8BuUFxnxV9k5S0qHN3v/KzUpq0IUpjH1seRg==
"@messageformat/parser@^5.1.0":
version "5.1.1"
resolved "https://registry.yarnpkg.com/@messageformat/parser/-/parser-5.1.1.tgz#ca7d6c18e9f3f6b6bc984a465dac16da00106055"
integrity sha512-3p0YRGCcTUCYvBKLIxtDDyrJ0YijGIwrTRu1DT8gIviIDZru8H23+FkY6MJBzM1n9n20CiM4VeDYuBsrrwnLjg==
dependencies:
moo "^0.5.1"
"@messageformat/runtime@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@messageformat/runtime/-/runtime-3.0.1.tgz#94d1f6c43265c28ef7aed98ecfcc0968c6c849ac"
integrity sha512-6RU5ol2lDtO8bD9Yxe6CZkl0DArdv0qkuoZC+ZwowU+cdRlVE1157wjCmlA5Rsf1Xc/brACnsZa5PZpEDfTFFg==
dependencies:
make-plural "^7.0.0"
"@mongodb-js/saslprep@^1.1.5":
version "1.1.6"
resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.1.6.tgz#54da3f794c71a17445740fe2b74882e0c76a3058"
@@ -1723,6 +1794,11 @@
call-me-maybe "^1.0.1"
glob-to-regexp "^0.3.0"
"@neon-rs/load@^0.0.4":
version "0.0.4"
resolved "https://registry.yarnpkg.com/@neon-rs/load/-/load-0.0.4.tgz#2a2a3292c6f1fef043f49886712d3c96a547532e"
integrity sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -1827,6 +1903,13 @@
magic-string "^0.25.7"
resolve "^1.17.0"
"@rollup/plugin-json@^6.1.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-6.1.0.tgz#fbe784e29682e9bb6dee28ea75a1a83702e7b805"
integrity sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==
dependencies:
"@rollup/pluginutils" "^5.1.0"
"@rollup/plugin-node-resolve@^13.0.5":
version "13.3.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz#da1c5c5ce8316cef96a2f823d111c1e4e498801c"
@@ -1872,6 +1955,15 @@
estree-walker "^1.0.1"
picomatch "^2.2.2"
"@rollup/pluginutils@^5.1.0":
version "5.1.4"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.4.tgz#bb94f1f9eaaac944da237767cdfee6c5b2262d4a"
integrity sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==
dependencies:
"@types/estree" "^1.0.0"
estree-walker "^2.0.2"
picomatch "^4.0.2"
"@sinclair/typebox@^0.24.1":
version "0.24.51"
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f"
@@ -2365,6 +2457,11 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
"@types/estree@^1.0.0":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
"@types/fs-extra@^8.0.1":
version "8.1.5"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.5.tgz#33aae2962d3b3ec9219b5aca2555ee00274f5927"
@@ -4499,6 +4596,11 @@ detect-indent@^6.0.0:
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6"
integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==
detect-libc@2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d"
integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==
detect-libc@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700"
@@ -4979,7 +5081,7 @@ estree-walker@^1.0.1:
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
estree-walker@^2.0.1:
estree-walker@^2.0.1, estree-walker@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
@@ -8219,6 +8321,22 @@ levn@^0.3.0, levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
libsql@0.5.0-pre.6:
version "0.5.0-pre.6"
resolved "https://registry.yarnpkg.com/libsql/-/libsql-0.5.0-pre.6.tgz#23e2b89977738baad10900104d3551d33fccc5b0"
integrity sha512-TvugJnL32QiZCvpu6Eh/uh2RjpzsxprqVd2hWygHFUK5abcotaRWYrHmuygXFe43QmsJrmYjN0DAKLqoiy8d2w==
dependencies:
"@neon-rs/load" "^0.0.4"
detect-libc "2.0.2"
optionalDependencies:
"@libsql/darwin-arm64" "0.5.0-pre.6"
"@libsql/darwin-x64" "0.5.0-pre.6"
"@libsql/linux-arm64-gnu" "0.5.0-pre.6"
"@libsql/linux-arm64-musl" "0.5.0-pre.6"
"@libsql/linux-x64-gnu" "0.5.0-pre.6"
"@libsql/linux-x64-musl" "0.5.0-pre.6"
"@libsql/win32-x64-msvc" "0.5.0-pre.6"
lie@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
@@ -8469,6 +8587,11 @@ make-fetch-happen@^9.0.1:
socks-proxy-agent "^6.0.0"
ssri "^8.0.0"
make-plural@^7.0.0:
version "7.4.0"
resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-7.4.0.tgz#fa6990dd550dea4de6b20163f74e5ed83d8a8d6d"
integrity sha512-4/gC9KVNTV6pvYg2gFeQYTW3mWaoJt7WZE5vrp1KnQDgW92JtYZnzmZT81oj/dUTqAIu0ufI2x3dkgu3bB1tYg==
makeerror@1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a"
@@ -8798,6 +8921,11 @@ mongodb@^6.3.0:
bson "^6.7.0"
mongodb-connection-string-url "^3.0.0"
moo@^0.5.1:
version "0.5.2"
resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.2.tgz#f9fe82473bc7c184b0d32e2215d3f6e67278733c"
integrity sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==
mri@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
@@ -9602,6 +9730,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatc
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
picomatch@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab"
integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
pify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
@@ -10442,6 +10575,11 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, s
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-identifier@^0.4.1:
version "0.4.2"
resolved "https://registry.yarnpkg.com/safe-identifier/-/safe-identifier-0.4.2.tgz#cf6bfca31c2897c588092d1750d30ef501d59fcb"
integrity sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==
safe-regex-test@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377"