mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
Compare commits
21 Commits
v0.7.25-be
...
v0.7.27
Author | SHA1 | Date | |
---|---|---|---|
f99a3cc054 | |||
59f7d3c670 | |||
8f2daa0f1c | |||
141d5bb69c | |||
171b6f924a | |||
f7419d8e9c | |||
5b1cd70e25 | |||
3e223b475e | |||
4a38656b7e | |||
25b7ae57c6 | |||
4b5718e9b7 | |||
780d83deaa | |||
e952f9f5f8 | |||
49f1a8ef2e | |||
121aa21a6d | |||
cb25963a67 | |||
3a47607a5f | |||
7494ff6fcf | |||
0ec9d3cfc1 | |||
|
4f615b26cf | ||
|
86a1e05197 |
@@ -284,6 +284,15 @@
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "mangas",
|
||||
"name": "Filipe Azevedo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1640325?v=4",
|
||||
"profile": "https://fazevedo.dev",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
8
.github/workflows/build-beta.yml
vendored
8
.github/workflows/build-beta.yml
vendored
@@ -19,17 +19,19 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: beta
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm i
|
||||
run: |
|
||||
npm i
|
||||
npm install "dmg-license" --save-optional
|
||||
|
||||
- name: "Build"
|
||||
run: npm run build
|
||||
|
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -25,17 +25,19 @@ jobs:
|
||||
exit 0
|
||||
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: master
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm i
|
||||
run: |
|
||||
npm i
|
||||
npm install "dmg-license" --save-optional
|
||||
|
||||
- name: "Build"
|
||||
run: npm run build
|
||||
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
|
@@ -8,12 +8,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: master
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
run: npm run build -- --arm64 --linux deb AppImage
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-build
|
||||
retention-days: 3
|
||||
|
6
.github/workflows/create-artifact-linux.yml
vendored
6
.github/workflows/create-artifact-linux.yml
vendored
@@ -8,10 +8,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
run: npm run build
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-build
|
||||
retention-days: 3
|
||||
|
12
.github/workflows/create-artifact-macos.yml
vendored
12
.github/workflows/create-artifact-macos.yml
vendored
@@ -8,20 +8,21 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: npm install & build
|
||||
run: |
|
||||
npm install
|
||||
npm install "dmg-license" --save-optional
|
||||
npm run build
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-build
|
||||
retention-days: 3
|
||||
@@ -38,17 +39,18 @@ jobs:
|
||||
ref: beta
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: npm install & build
|
||||
run: |
|
||||
npm install
|
||||
npm install "dmg-license" --save-optional
|
||||
npm run build
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-build-beta
|
||||
retention-days: 3
|
||||
|
@@ -11,7 +11,7 @@ jobs:
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.8'
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install pipx
|
||||
uses: CfirTsabari/actions-pipx@v1
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
# - name: Delete old package-lock.json
|
||||
# run: rm package-lock.json
|
||||
|
4
.github/workflows/test-builds.yml
vendored
4
.github/workflows/test-builds.yml
vendored
@@ -17,12 +17,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: develop
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
|
4
.github/workflows/test-e2e-win.yml
vendored
4
.github/workflows/test-e2e-win.yml
vendored
@@ -16,10 +16,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
|
40
CHANGELOG.md
40
CHANGELOG.md
@@ -2,6 +2,46 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [0.7.27](https://github.com/antares-sql/antares/compare/v0.7.26...v0.7.27) (2024-07-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* issue with new console and languages different than english, fixes [#837](https://github.com/antares-sql/antares/issues/837) ([59f7d3c](https://github.com/antares-sql/antares/commit/59f7d3c67083ac7e32bd29c9b7e6e044f2060c2f))
|
||||
|
||||
### [0.7.26](https://github.com/antares-sql/antares/compare/v0.7.26-beta.1...v0.7.26) (2024-07-15)
|
||||
|
||||
### [0.7.26-beta.1](https://github.com/antares-sql/antares/compare/v0.7.26-beta.0...v0.7.26-beta.1) (2024-07-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* custom SVG icons for connections, closes [#663](https://github.com/antares-sql/antares/issues/663) ([171b6f9](https://github.com/antares-sql/antares/commit/171b6f924acc7d7696f4f850a704af0baf616b87))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* table name in column list on export as SQL features, fixes [#822](https://github.com/antares-sql/antares/issues/822) ([f7419d8](https://github.com/antares-sql/antares/commit/f7419d8e9c4fe8ea80dbf9b2612ff44a66f50365))
|
||||
|
||||
### [0.7.26-beta.0](https://github.com/antares-sql/antares/compare/v0.7.25...v0.7.26-beta.0) (2024-07-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* in-app debug console, closes [#824](https://github.com/antares-sql/antares/issues/824) ([3e223b4](https://github.com/antares-sql/antares/commit/3e223b475ea57b24a6782feeabecad9c5596e271))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **PostgreSQL:** issue with ssl enabled and connection strings, fixes [#786](https://github.com/antares-sql/antares/issues/786) ([4a38656](https://github.com/antares-sql/antares/commit/4a38656b7e1094d3a9df28ce263c272f2014adb7))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **UI:** improved all connections modal, closes [#761](https://github.com/antares-sql/antares/issues/761) ([25b7ae5](https://github.com/antares-sql/antares/commit/25b7ae57c6a4be9e825dddc7a52a49b67e03771b))
|
||||
|
||||
### [0.7.25](https://github.com/antares-sql/antares/compare/v0.7.25-beta.2...v0.7.25) (2024-06-19)
|
||||
|
||||
### [0.7.25-beta.2](https://github.com/antares-sql/antares/compare/v0.7.25-beta.1...v0.7.25-beta.2) (2024-06-16)
|
||||
|
||||
|
||||
|
@@ -151,6 +151,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bagusindrayana"><img src="https://avatars.githubusercontent.com/u/36830534?v=4?s=100" width="100px;" alt="Bagus Indrayana"/><br /><sub><b>Bagus Indrayana</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=bagusindrayana" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/penguinlab"><img src="https://avatars.githubusercontent.com/u/10959317?v=4?s=100" width="100px;" alt="Naoki Ishikawa"/><br /><sub><b>Naoki Ishikawa</b></sub></a><br /><a href="#translation-penguinlab" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://fazevedo.dev"><img src="https://avatars.githubusercontent.com/u/1640325?v=4?s=100" width="100px;" alt="Filipe Azevedo"/><br /><sub><b>Filipe Azevedo</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=mangas" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "antares",
|
||||
"version": "0.7.25-beta.2",
|
||||
"version": "0.7.27",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "antares",
|
||||
"version": "0.7.25-beta.2",
|
||||
"version": "0.7.27",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "antares",
|
||||
"productName": "Antares",
|
||||
"version": "0.7.25-beta.2",
|
||||
"version": "0.7.27",
|
||||
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/antares-sql/antares.git",
|
||||
|
@@ -163,7 +163,7 @@ export const jsonToSqlInsert = (args: {
|
||||
const sqlInsertAfter = options && options.sqlInsertAfter ? options.sqlInsertAfter : 1;
|
||||
const sqlInsertDivider = options && options.sqlInsertDivider ? options.sqlInsertDivider : 'rows';
|
||||
const { elementsWrapper: ew } = customizations[client];
|
||||
const fieldNames = Object.keys(json[0]).map(key => `${ew}${key}${ew}`);
|
||||
const fieldNames = Object.keys(json[0]).map(key => `${ew}${key.split('.').pop()}${ew}`);
|
||||
let insertStmt = `INSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES `;
|
||||
let insertsString = '';
|
||||
let queryLength = 0;
|
||||
|
@@ -87,10 +87,10 @@ export default () => {
|
||||
shortCutRegister.unregister();
|
||||
});
|
||||
|
||||
ipcMain.handle('read-file', (event, filePath) => {
|
||||
ipcMain.handle('read-file', (event, { filePath, encoding }) => {
|
||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const content = fs.readFileSync(filePath, encoding);
|
||||
return content;
|
||||
}
|
||||
catch (error) {
|
||||
|
@@ -10,9 +10,7 @@
|
||||
:key="connection.uid"
|
||||
:connection="connection"
|
||||
/>
|
||||
<div class="connection-panel-wrapper p-relative">
|
||||
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
|
||||
</div>
|
||||
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
|
||||
</div>
|
||||
<TheFooter />
|
||||
<TheNotificationsBoard />
|
||||
@@ -48,6 +46,8 @@ import { useSchemaExportStore } from '@/stores/schemaExport';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
|
||||
import { useConsoleStore } from './stores/console';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const TheTitleBar = defineAsyncComponent(() => import(/* webpackChunkName: "TheTitleBar" */'@/components/TheTitleBar.vue'));
|
||||
@@ -80,6 +80,8 @@ const schemaExportStore = useSchemaExportStore();
|
||||
const { hideExportModal } = schemaExportStore;
|
||||
const { isExportModal: isExportSchemaModal } = storeToRefs(schemaExportStore);
|
||||
|
||||
const consoleStore = useConsoleStore();
|
||||
|
||||
const isAllConnectionsModal: Ref<boolean> = ref(false);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@@ -152,6 +154,60 @@ onMounted(() => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Console messages
|
||||
const oldLog = console.log;
|
||||
const oldWarn = console.warn;
|
||||
const oldInfo = console.info;
|
||||
const oldError = console.error;
|
||||
|
||||
console.log = function (...args) {
|
||||
consoleStore.putLog('debug', {
|
||||
level: 'log',
|
||||
process: 'renderer',
|
||||
message: args.join(' '),
|
||||
date: new Date()
|
||||
});
|
||||
oldLog.apply(this, args);
|
||||
};
|
||||
|
||||
console.info = function (...args) {
|
||||
consoleStore.putLog('debug', {
|
||||
level: 'info',
|
||||
process: 'renderer',
|
||||
message: args.join(' '),
|
||||
date: new Date()
|
||||
});
|
||||
oldInfo.apply(this, args);
|
||||
};
|
||||
|
||||
console.warn = function (...args) {
|
||||
consoleStore.putLog('debug', {
|
||||
level: 'warn',
|
||||
process: 'renderer',
|
||||
message: args.join(' '),
|
||||
date: new Date()
|
||||
});
|
||||
oldWarn.apply(this, args);
|
||||
};
|
||||
|
||||
console.error = function (...args) {
|
||||
consoleStore.putLog('debug', {
|
||||
level: 'error',
|
||||
process: 'renderer',
|
||||
message: args.join(' '),
|
||||
date: new Date()
|
||||
});
|
||||
oldError.apply(this, args);
|
||||
};
|
||||
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
console.error(event.reason);
|
||||
});
|
||||
|
||||
window.addEventListener('error', (event) => {
|
||||
console.error(event.error, '| File name:', event.filename.split('/').pop().split('?')[0]);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@@ -1,11 +1,19 @@
|
||||
<template>
|
||||
<SvgIcon
|
||||
v-if="type === 'mdi'"
|
||||
:type="type"
|
||||
:path="iconPath"
|
||||
:size="size"
|
||||
:rotate="rotate"
|
||||
:class="iconFlip"
|
||||
/>
|
||||
<svg
|
||||
v-else
|
||||
:width="size"
|
||||
:height="size"
|
||||
:viewBox="`0 0 ${size} ${size}`"
|
||||
v-html="iconPath"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -13,6 +21,10 @@ import SvgIcon from '@jamescoyle/vue-icon';
|
||||
import * as Icons from '@mdi/js';
|
||||
import { computed, PropType } from 'vue';
|
||||
|
||||
import { useConnectionsStore } from '@/stores/connections';
|
||||
|
||||
const { getIconByUid } = useConnectionsStore();
|
||||
|
||||
const props = defineProps({
|
||||
iconName: {
|
||||
type: String,
|
||||
@@ -23,7 +35,7 @@ const props = defineProps({
|
||||
default: 48
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
type: String as PropType<'mdi' | 'custom'>,
|
||||
default: () => 'mdi'
|
||||
},
|
||||
flip: {
|
||||
@@ -37,7 +49,18 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const iconPath = computed(() => {
|
||||
return (Icons as {[k:string]: string})[props.iconName];
|
||||
if (props.type === 'mdi')
|
||||
return (Icons as {[k:string]: string})[props.iconName];
|
||||
else if (props.type === 'custom') {
|
||||
const base64 = getIconByUid(props.iconName)?.base64;
|
||||
const svgString = Buffer
|
||||
.from(base64, 'base64')
|
||||
.toString('utf-8')
|
||||
.replaceAll(/width="[^"]*"|height="[^"]*"/g, '');
|
||||
|
||||
return svgString;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const iconFlip = computed(() => {
|
||||
|
@@ -365,7 +365,11 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
const handleWheelEvent = (e) => {
|
||||
if (!e.target.className.includes('select__')) deactivate();
|
||||
try {
|
||||
if (!e.target.className.includes('select__')) deactivate();
|
||||
}
|
||||
catch (_) {
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
285
src/renderer/components/DebugConsole.vue
Normal file
285
src/renderer/components/DebugConsole.vue
Normal file
@@ -0,0 +1,285 @@
|
||||
<template>
|
||||
<div
|
||||
ref="wrapper"
|
||||
class="console-wrapper"
|
||||
@mouseenter="isHover = true"
|
||||
@mouseleave="isHover = false"
|
||||
>
|
||||
<div ref="resizer" class="console-resizer" />
|
||||
<div
|
||||
id="console"
|
||||
ref="queryConsole"
|
||||
class="console column col-12"
|
||||
:style="{height: localHeight ? localHeight+'px' : ''}"
|
||||
>
|
||||
<div class="console-header">
|
||||
<ul class="tab tab-block">
|
||||
<li class="tab-item" :class="{'active': selectedTab === 'query'}">
|
||||
<a class="tab-link" @click="selectedTab = 'query'">{{ t('application.executedQueries') }}</a>
|
||||
</li>
|
||||
<li class="tab-item" :class="{'active': selectedTab === 'debug'}">
|
||||
<a class="tab-link" @click="selectedTab = 'debug'">{{ t('application.debugConsole') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button class="btn btn-clear mr-1" @click="resizeConsole(0)" />
|
||||
</div>
|
||||
<div
|
||||
v-show="selectedTab === 'query'"
|
||||
ref="queryConsoleBody"
|
||||
class="console-body"
|
||||
>
|
||||
<div
|
||||
v-for="(wLog, i) in workspaceQueryLogs"
|
||||
:key="i"
|
||||
class="console-log"
|
||||
tabindex="0"
|
||||
@contextmenu.prevent="contextMenu($event, wLog)"
|
||||
>
|
||||
<span class="console-log-datetime">{{ moment(wLog.date).format('HH:mm:ss') }}</span>: <code class="console-log-sql" v-html="highlight(wLog.sql, {html: true})" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-show="selectedTab === 'debug'"
|
||||
ref="logConsoleBody"
|
||||
class="console-body"
|
||||
>
|
||||
<div
|
||||
v-for="(log, i) in debugLogs"
|
||||
:key="i"
|
||||
class="console-log"
|
||||
tabindex="0"
|
||||
@contextmenu.prevent="contextMenu($event, log)"
|
||||
>
|
||||
<span class="console-log-datetime">{{ moment(log.date).format('HH:mm:ss') }}</span> <small>[{{ log.process.substring(0, 1).toUpperCase() }}]</small>: <span class="console-log-message" :class="`console-log-level-${log.level}`">{{ log.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BaseContextMenu
|
||||
v-if="isContext"
|
||||
:context-event="contextEvent"
|
||||
@close-context="isContext = false"
|
||||
>
|
||||
<div class="context-element" @click="copyLog">
|
||||
<span class="d-flex">
|
||||
<BaseIcon
|
||||
class="text-light mt-1 mr-1"
|
||||
icon-name="mdiContentCopy"
|
||||
:size="18"
|
||||
/> {{ t('general.copy') }}</span>
|
||||
</div>
|
||||
</BaseContextMenu>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as moment from 'moment';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { highlight } from 'sql-highlight';
|
||||
import { computed, nextTick, onMounted, Ref, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||
import BaseIcon from '@/components/BaseIcon.vue';
|
||||
import { copyText } from '@/libs/copyText';
|
||||
import { useConsoleStore } from '@/stores/console';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const consoleStore = useConsoleStore();
|
||||
|
||||
const { resizeConsole, getLogsByWorkspace } = consoleStore;
|
||||
const {
|
||||
isConsoleOpen,
|
||||
consoleHeight,
|
||||
selectedTab,
|
||||
debugLogs
|
||||
} = storeToRefs(consoleStore);
|
||||
|
||||
const props = defineProps({
|
||||
uid: {
|
||||
type: String,
|
||||
default: null,
|
||||
required: false
|
||||
}
|
||||
});
|
||||
|
||||
const wrapper: Ref<HTMLInputElement> = ref(null);
|
||||
const queryConsole: Ref<HTMLInputElement> = ref(null);
|
||||
const queryConsoleBody: Ref<HTMLInputElement> = ref(null);
|
||||
const logConsoleBody: Ref<HTMLInputElement> = ref(null);
|
||||
const resizer: Ref<HTMLInputElement> = ref(null);
|
||||
const localHeight = ref(consoleHeight.value);
|
||||
const isHover = ref(false);
|
||||
const isContext = ref(false);
|
||||
const contextContent: Ref<string> = ref(null);
|
||||
const contextEvent: Ref<MouseEvent> = ref(null);
|
||||
|
||||
const resize = (e: MouseEvent) => {
|
||||
const el = queryConsole.value;
|
||||
let elementHeight = el.getBoundingClientRect().bottom - e.pageY;
|
||||
if (elementHeight > 400) elementHeight = 400;
|
||||
localHeight.value = elementHeight;
|
||||
};
|
||||
|
||||
const workspaceQueryLogs = computed(() => {
|
||||
return getLogsByWorkspace(props.uid);
|
||||
});
|
||||
|
||||
const stopResize = () => {
|
||||
if (localHeight.value < 0) localHeight.value = 0;
|
||||
resizeConsole(localHeight.value);
|
||||
window.removeEventListener('mousemove', resize);
|
||||
window.removeEventListener('mouseup', stopResize);
|
||||
};
|
||||
|
||||
const contextMenu = (event: MouseEvent, wLog: {date: Date; sql?: string; message?: string}) => {
|
||||
contextEvent.value = event;
|
||||
contextContent.value = wLog.sql || wLog.message;
|
||||
isContext.value = true;
|
||||
};
|
||||
|
||||
const copyLog = () => {
|
||||
copyText(contextContent.value);
|
||||
isContext.value = false;
|
||||
};
|
||||
|
||||
watch(workspaceQueryLogs, async () => {
|
||||
if (!isHover.value) {
|
||||
await nextTick();
|
||||
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => debugLogs.value.length, async () => {
|
||||
if (!isHover.value) {
|
||||
await nextTick();
|
||||
logConsoleBody.value.scrollTop = logConsoleBody.value.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
watch(isConsoleOpen, async () => {
|
||||
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
|
||||
logConsoleBody.value.scrollTop = logConsoleBody.value.scrollHeight;
|
||||
});
|
||||
|
||||
watch(selectedTab, async () => {
|
||||
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
|
||||
logConsoleBody.value.scrollTop = logConsoleBody.value.scrollHeight;
|
||||
});
|
||||
|
||||
watch(consoleHeight, async (val) => {
|
||||
await nextTick();
|
||||
localHeight.value = val;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
|
||||
logConsoleBody.value.scrollTop = logConsoleBody.value.scrollHeight;
|
||||
|
||||
resizer.value.addEventListener('mousedown', (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
window.addEventListener('mousemove', resize);
|
||||
window.addEventListener('mouseup', stopResize);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.console-wrapper {
|
||||
width: -webkit-fill-available;
|
||||
z-index: 9;
|
||||
margin-top: auto;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
||||
.console-resizer {
|
||||
height: 4px;
|
||||
top: -1px;
|
||||
width: 100%;
|
||||
cursor: ns-resize;
|
||||
position: absolute;
|
||||
z-index: 99;
|
||||
transition: background 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: var(--primary-color-dark);
|
||||
}
|
||||
}
|
||||
|
||||
.console {
|
||||
padding: 0;
|
||||
padding-bottom: $footer-height;
|
||||
|
||||
.console-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 4px;
|
||||
|
||||
.tab-block {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tab-block,
|
||||
.tab-item {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.tab-link {
|
||||
padding: 0.2rem 0.6rem;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.console-body {
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 100%;
|
||||
padding: 0 6px 3px;
|
||||
|
||||
.console-log {
|
||||
padding: 1px 3px;
|
||||
margin: 1px 0;
|
||||
border-radius: $border-radius;
|
||||
user-select: text;
|
||||
|
||||
&-datetime {
|
||||
opacity: .6;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
&-sql {
|
||||
font-size: 95%;
|
||||
opacity: 0.8;
|
||||
font-weight: 700;
|
||||
|
||||
&:hover {
|
||||
user-select: text;
|
||||
}
|
||||
}
|
||||
|
||||
&-message {
|
||||
font-size: 95%;
|
||||
}
|
||||
|
||||
&-level {
|
||||
// &-log,
|
||||
// &-info {}
|
||||
&-warn {
|
||||
color: orange;
|
||||
}
|
||||
&-error {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
small {
|
||||
opacity: .6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -57,8 +57,22 @@
|
||||
>
|
||||
<div class="panel">
|
||||
<div class="panel-header p-2 text-center p-relative">
|
||||
<figure class="avatar avatar-lg pt-1 mb-1">
|
||||
<i class="settingbar-element-icon dbi" :class="[`dbi-${connection.client}`]" />
|
||||
<figure class="avatar avatar-lg pt-1 mb-1 bg-dark">
|
||||
<div
|
||||
v-if="connection.icon"
|
||||
class="settingbar-connection-icon"
|
||||
>
|
||||
<BaseIcon
|
||||
:icon-name="camelize(connection.icon)"
|
||||
:type="connection.hasCustomIcon ? 'custom' : 'mdi'"
|
||||
:size="42"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="settingbar-element-icon dbi ml-1"
|
||||
:class="[`dbi-${connection.client}`]"
|
||||
/>
|
||||
</figure>
|
||||
<div class="panel-title h6 text-ellipsis">
|
||||
{{ getConnectionName(connection.uid) }}
|
||||
@@ -136,7 +150,19 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer text-center py-0">
|
||||
<div v-if="connection.ssl" class="chip bg-success mt-2">
|
||||
<div
|
||||
v-if="connection.folderName"
|
||||
class="chip mt-2 bg-dark"
|
||||
>
|
||||
<BaseIcon
|
||||
icon-name="mdiFolder"
|
||||
class="mr-1"
|
||||
:style="[connection.color ? `color: ${connection.color};`: '']"
|
||||
:size="18"
|
||||
/>
|
||||
{{ connection.folderName }}
|
||||
</div>
|
||||
<div v-if="connection.ssl" class="chip bg-dark mt-2">
|
||||
<BaseIcon
|
||||
icon-name="mdiShieldKey"
|
||||
class="mr-1"
|
||||
@@ -144,7 +170,7 @@
|
||||
/>
|
||||
SSL
|
||||
</div>
|
||||
<div v-if="connection.ssh" class="chip bg-success mt-2">
|
||||
<div v-if="connection.ssh" class="chip bg-dark mt-2">
|
||||
<BaseIcon
|
||||
icon-name="mdiConsoleNetwork"
|
||||
class="mr-1"
|
||||
@@ -152,7 +178,7 @@
|
||||
/>
|
||||
SSH
|
||||
</div>
|
||||
<div v-if="connection.readonly" class="chip bg-success mt-2">
|
||||
<div v-if="connection.readonly" class="chip bg-dark mt-2">
|
||||
<BaseIcon
|
||||
icon-name="mdiLock"
|
||||
class="mr-1"
|
||||
@@ -209,6 +235,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||
import BaseIcon from '@/components/BaseIcon.vue';
|
||||
import { useFocusTrap } from '@/composables/useFocusTrap';
|
||||
import { camelize } from '@/libs/camelize';
|
||||
import { useConnectionsStore } from '@/stores/connections';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
|
||||
@@ -218,7 +245,9 @@ const connectionsStore = useConnectionsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const { connections,
|
||||
lastConnections
|
||||
connectionsOrder,
|
||||
lastConnections,
|
||||
getFolders: folders
|
||||
} = storeToRefs(connectionsStore);
|
||||
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
|
||||
|
||||
@@ -236,7 +265,8 @@ const clients = new Map([
|
||||
['mysql', 'MySQL'],
|
||||
['maria', 'MariaDB'],
|
||||
['pg', 'PostgreSQL'],
|
||||
['sqlite', 'SQLite']
|
||||
['sqlite', 'SQLite'],
|
||||
['firebird', 'Firebird SQL']
|
||||
]);
|
||||
|
||||
const searchTerm = ref('');
|
||||
@@ -244,12 +274,20 @@ const isConfirmModal = ref(false);
|
||||
const connectionHover: Ref<string> = ref(null);
|
||||
const selectedConnection: Ref<ConnectionParams> = ref(null);
|
||||
|
||||
const sortedConnections = computed(() => {
|
||||
const remappedConnections = computed(() => {
|
||||
return connections.value
|
||||
.map(c => {
|
||||
const connTime = lastConnections.value.find((lc) => lc.uid === c.uid)?.time || 0;
|
||||
const connIcon = connectionsOrder.value.find((co) => co.uid === c.uid).icon;
|
||||
const connHasCustomIcon = connectionsOrder.value.find((co) => co.uid === c.uid).hasCustomIcon;
|
||||
const folder = folders.value.find(f => f.connections.includes(c.uid));
|
||||
|
||||
return {
|
||||
...c,
|
||||
icon: connIcon,
|
||||
color: folder?.color,
|
||||
folderName: folder?.name,
|
||||
hasCustomIcon: connHasCustomIcon,
|
||||
time: connTime
|
||||
};
|
||||
})
|
||||
@@ -261,7 +299,7 @@ const sortedConnections = computed(() => {
|
||||
});
|
||||
|
||||
const filteredConnections = computed(() => {
|
||||
return sortedConnections.value.filter(connection => {
|
||||
return remappedConnections.value.filter(connection => {
|
||||
return connection.name?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
|
||||
connection.host?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
|
||||
connection.database?.toLocaleLowerCase().includes(searchTerm.value.toLocaleLowerCase()) ||
|
||||
|
@@ -49,18 +49,46 @@
|
||||
class="icon-box"
|
||||
:title="icon.name"
|
||||
:class="[{'selected': localConnection.icon === icon.code}]"
|
||||
@click="localConnection.icon = icon.code"
|
||||
@click="setIcon(icon.code)"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="icon-box"
|
||||
:title="icon.name"
|
||||
:class="[`dbi dbi-${connection.client}`, {'selected': localConnection.icon === icon.code}]"
|
||||
@click="localConnection.icon = icon.code"
|
||||
:class="[`dbi dbi-${connection.client}`, {'selected': localConnection.icon === null}]"
|
||||
@click="setIcon(null)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-3">
|
||||
<label class="form-label">{{ t('application.customIcon') }}</label>
|
||||
</div>
|
||||
<div class="col-9 icons-wrapper">
|
||||
<div
|
||||
v-for="icon in customIcons"
|
||||
:key="icon.uid"
|
||||
>
|
||||
<BaseIcon
|
||||
v-if="icon.uid"
|
||||
:icon-name="icon.uid"
|
||||
type="custom"
|
||||
:size="36"
|
||||
class="icon-box"
|
||||
:class="[{'selected': localConnection.icon === icon.uid}]"
|
||||
@click="setIcon(icon.uid, 'custom')"
|
||||
@contextmenu.prevent="contextMenu($event, icon.uid)"
|
||||
/>
|
||||
</div>
|
||||
<BaseIcon
|
||||
:icon-name="'mdiPlus'"
|
||||
:size="36"
|
||||
class="icon-box"
|
||||
@click="openFile"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,20 +102,45 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BaseContextMenu
|
||||
v-if="isContext"
|
||||
:context-event="contextEvent"
|
||||
@close-context="isContext = false"
|
||||
>
|
||||
<div class="context-element" @click="removeIconHandler">
|
||||
<span class="d-flex">
|
||||
<BaseIcon
|
||||
class="text-light mt-1 mr-1"
|
||||
icon-name="mdiDelete"
|
||||
:size="18"
|
||||
/> {{ t('general.delete') }}</span>
|
||||
</div>
|
||||
</BaseContextMenu>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { onBeforeUnmount, PropType, Ref, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||
import BaseIcon from '@/components/BaseIcon.vue';
|
||||
import { useFocusTrap } from '@/composables/useFocusTrap';
|
||||
import Application from '@/ipc-api/Application';
|
||||
import { camelize } from '@/libs/camelize';
|
||||
import { unproxify } from '@/libs/unproxify';
|
||||
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
|
||||
|
||||
const connectionsStore = useConnectionsStore();
|
||||
|
||||
const { addIcon, removeIcon, updateConnectionOrder, getConnectionName } = connectionsStore;
|
||||
const { customIcons } = storeToRefs(connectionsStore);
|
||||
|
||||
const isContext = ref(false);
|
||||
const contextContent: Ref<string> = ref(null);
|
||||
const contextEvent: Ref<MouseEvent> = ref(null);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
@@ -99,8 +152,6 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
const { updateConnectionOrder, getConnectionName } = connectionsStore;
|
||||
|
||||
const icons = [
|
||||
{ name: 'default', code: null },
|
||||
|
||||
@@ -160,14 +211,33 @@ const editFolderAppearance = () => {
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const camelize = (text: string) => {
|
||||
const textArr = text.split('-');
|
||||
for (let i = 0; i < textArr.length; i++) {
|
||||
if (i === 0) continue;
|
||||
textArr[i] = textArr[i].charAt(0).toUpperCase() + textArr[i].slice(1);
|
||||
}
|
||||
const setIcon = (code: string, type?: 'mdi' | 'custom') => {
|
||||
localConnection.value.icon = code;
|
||||
localConnection.value.hasCustomIcon = type === 'custom';
|
||||
};
|
||||
|
||||
return textArr.join('');
|
||||
const removeIconHandler = () => {
|
||||
if (localConnection.value.icon === contextContent.value) {
|
||||
setIcon(null);
|
||||
updateConnectionOrder(localConnection.value);
|
||||
}
|
||||
removeIcon(contextContent.value);
|
||||
isContext.value = false;
|
||||
};
|
||||
|
||||
const openFile = async () => {
|
||||
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: '"SVG"', extensions: ['svg'] }] });
|
||||
if (result && !result.canceled) {
|
||||
const file = result.filePaths[0];
|
||||
const content = await Application.readFile({ filePath: file, encoding: 'base64url' });
|
||||
addIcon(content);
|
||||
}
|
||||
};
|
||||
|
||||
const contextMenu = (event: MouseEvent, iconUid: string) => {
|
||||
contextEvent.value = event;
|
||||
contextContent.value = iconUid;
|
||||
isContext.value = true;
|
||||
};
|
||||
|
||||
const closeModal = () => emit('close');
|
||||
|
@@ -282,7 +282,7 @@
|
||||
import { ClientCode, SchemaInfos } from 'common/interfaces/antares';
|
||||
import { Customizations } from 'common/interfaces/customizations';
|
||||
import { ExportOptions, ExportState } from 'common/interfaces/exporter';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { ipcRenderer, IpcRendererEvent } from 'electron';
|
||||
import * as moment from 'moment';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
|
||||
@@ -293,6 +293,7 @@ import BaseSelect from '@/components/BaseSelect.vue';
|
||||
import { useFocusTrap } from '@/composables/useFocusTrap';
|
||||
import Application from '@/ipc-api/Application';
|
||||
import Schema from '@/ipc-api/Schema';
|
||||
import { useConsoleStore } from '@/stores/console';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useSchemaExportStore } from '@/stores/schemaExport';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
@@ -384,16 +385,28 @@ const startExport = async () => {
|
||||
else {
|
||||
progressStatus.value = response;
|
||||
addNotification({ status: 'error', message: response });
|
||||
useConsoleStore().putLog('debug', {
|
||||
level: 'error',
|
||||
process: 'worker',
|
||||
message: response,
|
||||
date: new Date()
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
useConsoleStore().putLog('debug', {
|
||||
level: 'error',
|
||||
process: 'worker',
|
||||
message: err.stack,
|
||||
date: new Date()
|
||||
});
|
||||
}
|
||||
|
||||
isExporting.value = false;
|
||||
};
|
||||
|
||||
const updateProgress = (event: Event, state: ExportState) => {
|
||||
const updateProgress = (event: IpcRendererEvent, state: ExportState) => {
|
||||
progressPercentage.value = Number((state.currentItemIndex / state.totalItems * 100).toFixed(1));
|
||||
switch (state.op) {
|
||||
case 'PROCESSING':
|
||||
|
@@ -55,7 +55,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ImportState } from 'common/interfaces/importer';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { ipcRenderer, IpcRendererEvent } from 'electron';
|
||||
import * as moment from 'moment';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, onBeforeUnmount, Ref, ref } from 'vue';
|
||||
@@ -63,6 +63,7 @@ import { useI18n } from 'vue-i18n';
|
||||
|
||||
import BaseIcon from '@/components/BaseIcon.vue';
|
||||
import Schema from '@/ipc-api/Schema';
|
||||
import { useConsoleStore } from '@/stores/console';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
|
||||
@@ -118,23 +119,35 @@ const startImport = async (file: string) => {
|
||||
else {
|
||||
progressStatus.value = response;
|
||||
addNotification({ status: 'error', message: response });
|
||||
useConsoleStore().putLog('debug', {
|
||||
level: 'error',
|
||||
process: 'worker',
|
||||
message: response,
|
||||
date: new Date()
|
||||
});
|
||||
}
|
||||
refreshSchema({ uid, schema: props.selectedSchema });
|
||||
completed.value = true;
|
||||
}
|
||||
catch (err) {
|
||||
addNotification({ status: 'error', message: err.stack });
|
||||
useConsoleStore().putLog('debug', {
|
||||
level: 'error',
|
||||
process: 'worker',
|
||||
message: err.stack,
|
||||
date: new Date()
|
||||
});
|
||||
}
|
||||
|
||||
isImporting.value = false;
|
||||
};
|
||||
|
||||
const updateProgress = (event: Event, state: ImportState) => {
|
||||
const updateProgress = (event: IpcRendererEvent, state: ImportState) => {
|
||||
progressPercentage.value = parseFloat(Number(state.percentage).toFixed(1));
|
||||
queryCount.value = Number(state.queryCount);
|
||||
};
|
||||
|
||||
const handleQueryError = (event: Event, err: { time: string; message: string }) => {
|
||||
const handleQueryError = (event: IpcRendererEvent, err: { time: string; message: string }) => {
|
||||
queryErrors.value.push(err);
|
||||
};
|
||||
|
||||
|
@@ -169,7 +169,7 @@ const emit = defineEmits(['close']);
|
||||
const { trapRef } = useFocusTrap();
|
||||
|
||||
const { getConnectionName } = useConnectionsStore();
|
||||
const { connectionsOrder, connections } = storeToRefs(useConnectionsStore());
|
||||
const { connectionsOrder, connections, customIcons } = storeToRefs(useConnectionsStore());
|
||||
const localConnections = unproxify<ConnectionParams[]>(connections.value);
|
||||
const localConnectionsOrder = unproxify<SidebarElement[]>(connectionsOrder.value);
|
||||
|
||||
@@ -246,7 +246,8 @@ const exportData = () => {
|
||||
|
||||
const exportObj = encrypt(JSON.stringify({
|
||||
connections: filteredConnections,
|
||||
connectionsOrder: filteredOrders
|
||||
connectionsOrder: filteredOrders,
|
||||
customIcons
|
||||
}), options.value.passkey);
|
||||
|
||||
// console.log(exportObj, JSON.parse(decrypt(exportObj, options.value.passkey)));
|
||||
|
@@ -103,7 +103,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import BaseIcon from '@/components/BaseIcon.vue';
|
||||
import BaseUploadInput from '@/components/BaseUploadInput.vue';
|
||||
import { unproxify } from '@/libs/unproxify';
|
||||
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
|
||||
import { CustomIcon, SidebarElement, useConnectionsStore } from '@/stores/connections';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -156,6 +156,7 @@ const importData = () => {
|
||||
const importObj: {
|
||||
connections: ConnectionParams[];
|
||||
connectionsOrder: SidebarElement[];
|
||||
customIcons: CustomIcon[];
|
||||
} = JSON.parse(decrypt(hash, options.value.passkey));
|
||||
|
||||
if (options.value.ignoreDuplicates) {
|
||||
|
@@ -341,7 +341,7 @@ onMounted(() => {
|
||||
lastTableFields.value = res.response.map((field: { name: string }) => field.name);
|
||||
editor.value.completers = [tableFieldsCompleter.value];
|
||||
editor.value.execCommand('startAutocomplete');
|
||||
}).catch(console.log);
|
||||
}).catch(console.error);
|
||||
}
|
||||
else
|
||||
editor.value.completers = customCompleter.value;
|
||||
|
@@ -56,6 +56,7 @@
|
||||
>
|
||||
<BaseIcon
|
||||
:icon-name="camelize(element.icon)"
|
||||
:type="element.hasCustomIcon ? 'custom' : 'mdi'"
|
||||
:size="36"
|
||||
/>
|
||||
</div>
|
||||
@@ -93,6 +94,7 @@ import * as Draggable from 'vuedraggable';
|
||||
|
||||
import BaseIcon from '@/components/BaseIcon.vue';
|
||||
import SettingBarConnectionsFolder from '@/components/SettingBarConnectionsFolder.vue';
|
||||
import { camelize } from '@/libs/camelize';
|
||||
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
|
||||
@@ -165,16 +167,6 @@ const getStatusBadge = (uid: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const camelize = (text: string) => {
|
||||
const textArr = text.split('-');
|
||||
for (let i = 0; i < textArr.length; i++) {
|
||||
if (i === 0) continue;
|
||||
textArr[i] = textArr[i].charAt(0).toUpperCase() + textArr[i].slice(1);
|
||||
}
|
||||
|
||||
return textArr.join('');
|
||||
};
|
||||
|
||||
watch(() => dummyNested.value.length, () => {
|
||||
dummyNested.value = [];
|
||||
});
|
||||
|
@@ -70,6 +70,7 @@
|
||||
>
|
||||
<BaseIcon
|
||||
:icon-name="camelize(getConnectionOrderByUid(element).icon)"
|
||||
:type="getConnectionOrderByUid(element).hasCustomIcon ? 'custom' : 'mdi'"
|
||||
:size="36"
|
||||
/>
|
||||
</div>
|
||||
|
@@ -43,11 +43,7 @@
|
||||
|
||||
<div class="footer-right-elements">
|
||||
<ul class="footer-elements">
|
||||
<li
|
||||
v-if="workspace?.connectionStatus === 'connected'"
|
||||
class="footer-element footer-link"
|
||||
@click="toggleConsole()"
|
||||
>
|
||||
<li class="footer-element footer-link" @click="toggleConsole()">
|
||||
<BaseIcon
|
||||
icon-name="mdiConsoleLine"
|
||||
class="mr-1"
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,440 +1,446 @@
|
||||
<template>
|
||||
<div class="connection-panel">
|
||||
<div class="panel">
|
||||
<div class="panel-nav">
|
||||
<ul class="tab tab-block">
|
||||
<li
|
||||
class="tab-item c-hand"
|
||||
:class="{'active': selectedTab === 'general'}"
|
||||
@click="selectTab('general')"
|
||||
>
|
||||
<a class="tab-link">{{ t('application.general') }}</a>
|
||||
</li>
|
||||
<li
|
||||
v-if="clientCustomizations.sslConnection"
|
||||
class="tab-item c-hand"
|
||||
:class="{'active': selectedTab === 'ssl'}"
|
||||
@click="selectTab('ssl')"
|
||||
>
|
||||
<a class="tab-link">{{ t('connection.ssl') }}</a>
|
||||
</li>
|
||||
<li
|
||||
v-if="clientCustomizations.sshConnection"
|
||||
class="tab-item c-hand"
|
||||
:class="{'active': selectedTab === 'ssh'}"
|
||||
@click="selectTab('ssh')"
|
||||
>
|
||||
<a class="tab-link">{{ t('connection.sshTunnel') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="selectedTab === 'general'" class="panel-body py-0">
|
||||
<div>
|
||||
<form class="form-horizontal">
|
||||
<fieldset class="m-0" :disabled="isBusy">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.connectionName') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="connection.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.client') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<BaseSelect
|
||||
v-model="connection.client"
|
||||
:options="clients"
|
||||
option-track-by="slug"
|
||||
option-label="name"
|
||||
class="form-select"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="connection.client === 'pg'" class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.connectionString') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
ref="pgString"
|
||||
v-model="connection.pgConnString"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.hostName') }}/IP</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.host"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('database.database') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="connection.databasePath"
|
||||
:message="t('general.browse')"
|
||||
@clear="pathClear('databasePath')"
|
||||
@change="pathSelection($event, 'databasePath')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.port') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.port"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="clientCustomizations.database" class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('database.database') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.database"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:placeholder="clientCustomizations.defaultDatabase"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.user') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.user"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:disabled="connection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.password') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.password"
|
||||
class="form-input"
|
||||
type="password"
|
||||
:disabled="connection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="clientCustomizations.connectionSchema" class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('database.schema') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.schema"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:placeholder="t('general.all')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="clientCustomizations.readOnlyMode" class="form-group columns mb-0">
|
||||
<div class="column col-5 col-sm-12" />
|
||||
<div class="column col-7 col-sm-12">
|
||||
<label class="form-checkbox form-inline my-0">
|
||||
<input v-model="connection.readonly" type="checkbox"><i class="form-icon" /> {{ t('connection.readOnlyMode') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns mb-0">
|
||||
<div class="column col-5 col-sm-12" />
|
||||
<div class="column col-7 col-sm-12">
|
||||
<label class="form-checkbox form-inline my-0">
|
||||
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ t('connection.askCredentials') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="clientCustomizations.singleConnectionMode" class="form-group columns mb-0">
|
||||
<div class="column col-5 col-sm-12" />
|
||||
<div class="column col-7 col-sm-12">
|
||||
<label class="form-checkbox form-inline my-0">
|
||||
<input v-model="connection.singleConnectionMode" type="checkbox"><i class="form-icon" /> {{ t('connection.singleConnection') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
<div class="connection-panel-wrapper p-relative">
|
||||
<div class="connection-panel">
|
||||
<div class="panel">
|
||||
<div class="panel-nav">
|
||||
<ul class="tab tab-block">
|
||||
<li
|
||||
class="tab-item c-hand"
|
||||
:class="{'active': selectedTab === 'general'}"
|
||||
@click="selectTab('general')"
|
||||
>
|
||||
<a class="tab-link">{{ t('application.general') }}</a>
|
||||
</li>
|
||||
<li
|
||||
v-if="clientCustomizations.sslConnection"
|
||||
class="tab-item c-hand"
|
||||
:class="{'active': selectedTab === 'ssl'}"
|
||||
@click="selectTab('ssl')"
|
||||
>
|
||||
<a class="tab-link">{{ t('connection.ssl') }}</a>
|
||||
</li>
|
||||
<li
|
||||
v-if="clientCustomizations.sshConnection"
|
||||
class="tab-item c-hand"
|
||||
:class="{'active': selectedTab === 'ssh'}"
|
||||
@click="selectTab('ssh')"
|
||||
>
|
||||
<a class="tab-link">{{ t('connection.sshTunnel') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedTab === 'ssl'" class="panel-body py-0">
|
||||
<div>
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">
|
||||
{{ t('connection.enableSsl') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<label class="form-switch d-inline-block" @click.prevent="toggleSsl">
|
||||
<input type="checkbox" :checked="connection.ssl">
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset class="m-0" :disabled="isBusy || !connection.ssl">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.privateKey') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="connection.key"
|
||||
:message="t('general.browse')"
|
||||
@clear="pathClear('key')"
|
||||
@change="pathSelection($event, 'key')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.certificate') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="connection.cert"
|
||||
:message="t('general.browse')"
|
||||
@clear="pathClear('cert')"
|
||||
@change="pathSelection($event, 'cert')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.caCertificate') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="connection.ca"
|
||||
:message="t('general.browse')"
|
||||
@clear="pathClear('ca')"
|
||||
@change="pathSelection($event, 'ca')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.ciphers') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="connection.ciphers"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12" />
|
||||
<div class="column col-7 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="connection.untrustedConnection" type="checkbox"><i class="form-icon" /> {{ t('connection.untrustedConnection') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedTab === 'ssh'" class="panel-body py-0">
|
||||
<div>
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">
|
||||
{{ t('connection.enableSsh') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<label class="form-switch d-inline-block" @click.prevent="toggleSsh">
|
||||
<input type="checkbox" :checked="connection.ssh">
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset class="m-0" :disabled="isBusy || !connection.ssh">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.hostName') }}/IP</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.sshHost"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.user') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.sshUser"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.password') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.sshPass"
|
||||
class="form-input"
|
||||
type="password"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.port') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.sshPort"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.privateKey') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="connection.sshKey"
|
||||
:message="t('general.browse')"
|
||||
@clear="pathClear('sshKey')"
|
||||
@change="pathSelection($event, 'sshKey')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.passphrase') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.sshPassphrase"
|
||||
class="form-input"
|
||||
type="password"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.keepAliveInterval') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<div class="input-group">
|
||||
<div v-if="selectedTab === 'general'" class="panel-body py-0">
|
||||
<div>
|
||||
<form class="form-horizontal">
|
||||
<fieldset class="m-0" :disabled="isBusy">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.connectionName') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.sshKeepAliveInterval"
|
||||
ref="firstInput"
|
||||
v-model="connection.name"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.client') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<BaseSelect
|
||||
v-model="connection.client"
|
||||
:options="clients"
|
||||
option-track-by="slug"
|
||||
option-label="name"
|
||||
class="form-select"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="connection.client === 'pg'" class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.connectionString') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
ref="pgString"
|
||||
v-model="connection.pgConnString"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.hostName') }}/IP</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.host"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('database.database') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="connection.databasePath"
|
||||
:message="t('general.browse')"
|
||||
@clear="pathClear('databasePath')"
|
||||
@change="pathSelection($event, 'databasePath')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.port') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.port"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
>
|
||||
<span class="input-group-addon">{{ t('general.seconds') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="clientCustomizations.database" class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('database.database') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.database"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:placeholder="clientCustomizations.defaultDatabase"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.user') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.user"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:disabled="connection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.password') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.password"
|
||||
class="form-input"
|
||||
type="password"
|
||||
:disabled="connection.ask"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="clientCustomizations.connectionSchema" class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('database.schema') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.schema"
|
||||
class="form-input"
|
||||
type="text"
|
||||
:placeholder="t('general.all')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="clientCustomizations.readOnlyMode" class="form-group columns mb-0">
|
||||
<div class="column col-5 col-sm-12" />
|
||||
<div class="column col-7 col-sm-12">
|
||||
<label class="form-checkbox form-inline my-0">
|
||||
<input v-model="connection.readonly" type="checkbox"><i class="form-icon" /> {{ t('connection.readOnlyMode') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!clientCustomizations.fileConnection" class="form-group columns mb-0">
|
||||
<div class="column col-5 col-sm-12" />
|
||||
<div class="column col-7 col-sm-12">
|
||||
<label class="form-checkbox form-inline my-0">
|
||||
<input v-model="connection.ask" type="checkbox"><i class="form-icon" /> {{ t('connection.askCredentials') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="clientCustomizations.singleConnectionMode" class="form-group columns mb-0">
|
||||
<div class="column col-5 col-sm-12" />
|
||||
<div class="column col-7 col-sm-12">
|
||||
<label class="form-checkbox form-inline my-0">
|
||||
<input v-model="connection.singleConnectionMode" type="checkbox"><i class="form-icon" /> {{ t('connection.singleConnection') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedTab === 'ssl'" class="panel-body py-0">
|
||||
<div>
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">
|
||||
{{ t('connection.enableSsl') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<label class="form-switch d-inline-block" @click.prevent="toggleSsl">
|
||||
<input type="checkbox" :checked="connection.ssl">
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
<fieldset class="m-0" :disabled="isBusy || !connection.ssl">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.privateKey') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="connection.key"
|
||||
:message="t('general.browse')"
|
||||
@clear="pathClear('key')"
|
||||
@change="pathSelection($event, 'key')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.certificate') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="connection.cert"
|
||||
:message="t('general.browse')"
|
||||
@clear="pathClear('cert')"
|
||||
@change="pathSelection($event, 'cert')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.caCertificate') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="connection.ca"
|
||||
:message="t('general.browse')"
|
||||
@clear="pathClear('ca')"
|
||||
@change="pathSelection($event, 'ca')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.ciphers') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
ref="firstInput"
|
||||
v-model="connection.ciphers"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12" />
|
||||
<div class="column col-7 col-sm-12">
|
||||
<label class="form-checkbox form-inline">
|
||||
<input v-model="connection.untrustedConnection" type="checkbox"><i class="form-icon" /> {{ t('connection.untrustedConnection') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedTab === 'ssh'" class="panel-body py-0">
|
||||
<div>
|
||||
<form class="form-horizontal">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">
|
||||
{{ t('connection.enableSsh') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<label class="form-switch d-inline-block" @click.prevent="toggleSsh">
|
||||
<input type="checkbox" :checked="connection.ssh">
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset class="m-0" :disabled="isBusy || !connection.ssh">
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.hostName') }}/IP</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.sshHost"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.user') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.sshUser"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.password') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.sshPass"
|
||||
class="form-input"
|
||||
type="password"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.port') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.sshPort"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.privateKey') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<BaseUploadInput
|
||||
:model-value="connection.sshKey"
|
||||
:message="t('general.browse')"
|
||||
@clear="pathClear('sshKey')"
|
||||
@change="pathSelection($event, 'sshKey')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.passphrase') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<input
|
||||
v-model="connection.sshPassphrase"
|
||||
class="form-input"
|
||||
type="password"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group columns">
|
||||
<div class="column col-5 col-sm-12">
|
||||
<label class="form-label cut-text">{{ t('connection.keepAliveInterval') }}</label>
|
||||
</div>
|
||||
<div class="column col-7 col-sm-12">
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="connection.sshKeepAliveInterval"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="1"
|
||||
>
|
||||
<span class="input-group-addon">{{ t('general.seconds') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<button
|
||||
id="connection-test"
|
||||
class="btn btn-gray mr-2 d-flex"
|
||||
:class="{'loading': isTesting}"
|
||||
:disabled="isBusy"
|
||||
@click="startTest"
|
||||
>
|
||||
<BaseIcon
|
||||
icon-name="mdiLightningBolt"
|
||||
:size="24"
|
||||
class="mr-1"
|
||||
/>
|
||||
{{ t('connection.testConnection') }}
|
||||
</button>
|
||||
<button
|
||||
id="connection-save"
|
||||
class="btn btn-primary mr-2 d-flex"
|
||||
:disabled="isBusy"
|
||||
@click="saveConnection"
|
||||
>
|
||||
<BaseIcon
|
||||
icon-name="mdiContentSave"
|
||||
:size="24"
|
||||
class="mr-1"
|
||||
/>
|
||||
{{ t('general.save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<button
|
||||
id="connection-test"
|
||||
class="btn btn-gray mr-2 d-flex"
|
||||
:class="{'loading': isTesting}"
|
||||
:disabled="isBusy"
|
||||
@click="startTest"
|
||||
>
|
||||
<BaseIcon
|
||||
icon-name="mdiLightningBolt"
|
||||
:size="24"
|
||||
class="mr-1"
|
||||
/>
|
||||
{{ t('connection.testConnection') }}
|
||||
</button>
|
||||
<button
|
||||
id="connection-save"
|
||||
class="btn btn-primary mr-2 d-flex"
|
||||
:disabled="isBusy"
|
||||
@click="saveConnection"
|
||||
>
|
||||
<BaseIcon
|
||||
icon-name="mdiContentSave"
|
||||
:size="24"
|
||||
class="mr-1"
|
||||
/>
|
||||
{{ t('general.save') }}
|
||||
</button>
|
||||
</div>
|
||||
<ModalAskCredentials
|
||||
v-if="isAsking"
|
||||
@close-asking="closeAsking"
|
||||
@credentials="continueTest"
|
||||
/>
|
||||
</div>
|
||||
<ModalAskCredentials
|
||||
v-if="isAsking"
|
||||
@close-asking="closeAsking"
|
||||
@credentials="continueTest"
|
||||
/>
|
||||
</div>
|
||||
<DebugConsole v-if="isConsoleOpen" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import customizations from 'common/customizations';
|
||||
import { ConnectionParams } from 'common/interfaces/antares';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, Ref, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import BaseIcon from '@/components/BaseIcon.vue';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
import BaseUploadInput from '@/components/BaseUploadInput.vue';
|
||||
import DebugConsole from '@/components/DebugConsole.vue';
|
||||
import ModalAskCredentials from '@/components/ModalAskCredentials.vue';
|
||||
import Connection from '@/ipc-api/Connection';
|
||||
import { useConnectionsStore } from '@/stores/connections';
|
||||
import { useConsoleStore } from '@/stores/console';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
|
||||
@@ -443,6 +449,7 @@ const { t } = useI18n();
|
||||
const { addConnection } = useConnectionsStore();
|
||||
const { addNotification } = useNotificationsStore();
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const { isConsoleOpen } = storeToRefs(useConsoleStore());
|
||||
|
||||
const { connectWorkspace, selectWorkspace } = workspacesStore;
|
||||
|
||||
|
@@ -598,6 +598,22 @@ localConnection.value = JSON.parse(JSON.stringify(props.connection));
|
||||
min-width: 450px;
|
||||
border-radius: $border-radius;
|
||||
|
||||
.panel-nav {
|
||||
.tab-block {
|
||||
background: transparent;
|
||||
margin: 0.2rem 0 0.15rem 0;
|
||||
|
||||
.tab-item {
|
||||
background: transparent;
|
||||
flex: 1 0 0;
|
||||
|
||||
> a {
|
||||
padding: 8px 4px 6px 4px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
flex: initial;
|
||||
}
|
||||
|
@@ -1,189 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
ref="wrapper"
|
||||
class="query-console-wrapper"
|
||||
@mouseenter="isHover = true"
|
||||
@mouseleave="isHover = false"
|
||||
>
|
||||
<div ref="resizer" class="query-console-resizer" />
|
||||
<div
|
||||
id="query-console"
|
||||
ref="queryConsole"
|
||||
class="query-console column col-12"
|
||||
:style="{height: localHeight ? localHeight+'px' : ''}"
|
||||
>
|
||||
<div class="query-console-header">
|
||||
<div>{{ t('application.console') }}</div>
|
||||
<button class="btn btn-clear mr-1" @click="resizeConsole(0)" />
|
||||
</div>
|
||||
<div ref="queryConsoleBody" class="query-console-body">
|
||||
<div
|
||||
v-for="(wLog, i) in workspaceLogs"
|
||||
:key="i"
|
||||
class="query-console-log"
|
||||
tabindex="0"
|
||||
@contextmenu.prevent="contextMenu($event, wLog)"
|
||||
>
|
||||
<span class="type-datetime">{{ moment(wLog.date).format('HH:mm:ss') }}</span>: <code class="query-console-log-sql" v-html="highlight(wLog.sql, {html: true})" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BaseContextMenu
|
||||
v-if="isContext"
|
||||
:context-event="contextEvent"
|
||||
@close-context="isContext = false"
|
||||
>
|
||||
<div class="context-element" @click="copyQuery">
|
||||
<span class="d-flex">
|
||||
<BaseIcon
|
||||
class="text-light mt-1 mr-1"
|
||||
icon-name="mdiContentCopy"
|
||||
:size="18"
|
||||
/> {{ t('general.copy') }}</span>
|
||||
</div>
|
||||
</BaseContextMenu>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as moment from 'moment';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { highlight } from 'sql-highlight';
|
||||
import { computed, nextTick, onMounted, Ref, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import BaseContextMenu from '@/components/BaseContextMenu.vue';
|
||||
import BaseIcon from '@/components/BaseIcon.vue';
|
||||
import { copyText } from '@/libs/copyText';
|
||||
import { useConsoleStore } from '@/stores/console';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const consoleStore = useConsoleStore();
|
||||
|
||||
const { resizeConsole, getLogsByWorkspace } = consoleStore;
|
||||
const { consoleHeight } = storeToRefs(consoleStore);
|
||||
|
||||
const props = defineProps({
|
||||
uid: String
|
||||
});
|
||||
|
||||
const wrapper: Ref<HTMLInputElement> = ref(null);
|
||||
const queryConsole: Ref<HTMLInputElement> = ref(null);
|
||||
const queryConsoleBody: Ref<HTMLInputElement> = ref(null);
|
||||
const resizer: Ref<HTMLInputElement> = ref(null);
|
||||
const localHeight = ref(250);
|
||||
const isHover = ref(false);
|
||||
const isContext = ref(false);
|
||||
const contextQuery: Ref<string> = ref(null);
|
||||
const contextEvent: Ref<MouseEvent> = ref(null);
|
||||
|
||||
const resize = (e: MouseEvent) => {
|
||||
const el = queryConsole.value;
|
||||
let consoleHeight = el.getBoundingClientRect().bottom - e.pageY;
|
||||
if (consoleHeight > 400) consoleHeight = 400;
|
||||
localHeight.value = consoleHeight;
|
||||
};
|
||||
|
||||
const workspaceLogs = computed(() => {
|
||||
return getLogsByWorkspace(props.uid);
|
||||
});
|
||||
|
||||
const stopResize = () => {
|
||||
if (localHeight.value < 0) localHeight.value = 0;
|
||||
resizeConsole(localHeight.value);
|
||||
window.removeEventListener('mousemove', resize);
|
||||
window.removeEventListener('mouseup', stopResize);
|
||||
};
|
||||
|
||||
const contextMenu = (event: MouseEvent, wLog: {date: Date; sql: string}) => {
|
||||
contextEvent.value = event;
|
||||
contextQuery.value = wLog.sql;
|
||||
isContext.value = true;
|
||||
};
|
||||
|
||||
const copyQuery = () => {
|
||||
copyText(contextQuery.value);
|
||||
isContext.value = false;
|
||||
};
|
||||
|
||||
watch(workspaceLogs, async () => {
|
||||
if (!isHover.value) {
|
||||
await nextTick();
|
||||
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
localHeight.value = consoleHeight.value;
|
||||
queryConsoleBody.value.scrollTop = queryConsoleBody.value.scrollHeight;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
resizer.value.addEventListener('mousedown', (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
window.addEventListener('mousemove', resize);
|
||||
window.addEventListener('mouseup', stopResize);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.query-console-wrapper {
|
||||
width: 100%;
|
||||
z-index: 9;
|
||||
margin-top: auto;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
||||
.query-console-resizer {
|
||||
height: 4px;
|
||||
top: -1px;
|
||||
width: 100%;
|
||||
cursor: ns-resize;
|
||||
position: absolute;
|
||||
z-index: 99;
|
||||
transition: background 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: var(--primary-color-dark);
|
||||
}
|
||||
}
|
||||
|
||||
.query-console {
|
||||
padding: 0;
|
||||
padding-bottom: $footer-height;
|
||||
|
||||
.query-console-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 4px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.query-console-body {
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 100%;
|
||||
padding: 0 6px 3px;
|
||||
|
||||
.query-console-log {
|
||||
padding: 1px 3px;
|
||||
margin: 1px 0;
|
||||
border-radius: $border-radius;
|
||||
|
||||
.query-console-log-sql {
|
||||
font-size: 95%;
|
||||
opacity: 0.8;
|
||||
font-weight: 700;
|
||||
|
||||
&:hover {
|
||||
user-select: text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -715,7 +715,7 @@ const openFile = async () => {
|
||||
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'SQL', extensions: ['sql', 'txt'] }] });
|
||||
if (result && !result.canceled) {
|
||||
const file = result.filePaths[0];
|
||||
const content = await Application.readFile(file);
|
||||
const content = await Application.readFile({ filePath: file, encoding: 'utf-8' });
|
||||
const fileName = file.split('/').pop().split('\\').pop();
|
||||
if (props.tab.filePath && props.tab.filePath !== file) {
|
||||
newTab({
|
||||
@@ -755,7 +755,7 @@ const saveFile = async () => {
|
||||
};
|
||||
|
||||
const loadFileContent = async (file: string) => {
|
||||
const content = await Application.readFile(file);
|
||||
const content = await Application.readFile({ filePath: file, encoding: 'utf-8' });
|
||||
query.value = content;
|
||||
lastSavedQuery.value = content;
|
||||
};
|
||||
|
@@ -312,6 +312,7 @@ export const enUS = {
|
||||
color: 'Color',
|
||||
label: 'Label',
|
||||
icon: 'Icon',
|
||||
customIcon: 'Custom icon',
|
||||
fileName: 'File name',
|
||||
choseFile: 'Choose file',
|
||||
data: 'Data',
|
||||
@@ -407,8 +408,10 @@ export const enUS = {
|
||||
saveFile: 'Save file',
|
||||
saveFileAs: 'Save file as',
|
||||
openFile: 'Open file',
|
||||
openNotes: 'Open notes'
|
||||
|
||||
openNotes: 'Open notes',
|
||||
debugConsole: 'Debug console', // <- console tab name
|
||||
executedQueries: 'Executed queries', // <- console tab name
|
||||
sizeLimitError: 'Maximum size of {size} exceeded'
|
||||
},
|
||||
faker: { // Faker.js methods, used in random generated content
|
||||
address: 'Address',
|
||||
|
@@ -46,6 +46,8 @@ export type AvailableLocale = keyof typeof messages
|
||||
|
||||
const i18n = createI18n<[NestedPartial<MessageSchema>], AvailableLocale>({
|
||||
fallbackLocale: 'en-US',
|
||||
silentTranslationWarn: true,
|
||||
silentFallbackWarn: true,
|
||||
allowComposition: true,
|
||||
messages
|
||||
});
|
||||
|
@@ -12,7 +12,7 @@ import { createApp } from 'vue';
|
||||
import App from '@/App.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import { useApplicationStore } from '@/stores/application';
|
||||
import { useConsoleStore } from '@/stores/console';
|
||||
import { QueryLog, useConsoleStore } from '@/stores/console';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
|
||||
@@ -37,11 +37,17 @@ i18n.global.locale = locale;
|
||||
// IPC exceptions
|
||||
ipcRenderer.on('unhandled-exception', (event, error) => {
|
||||
useNotificationsStore().addNotification({ status: 'error', message: error.message });
|
||||
useConsoleStore().putLog('debug', {
|
||||
level: 'error',
|
||||
process: 'main',
|
||||
message: error.message,
|
||||
date: new Date()
|
||||
});
|
||||
});
|
||||
|
||||
// IPC query logs
|
||||
ipcRenderer.on('query-log', (event, logRecord) => {
|
||||
useConsoleStore().putLog(logRecord);
|
||||
ipcRenderer.on('query-log', (event, logRecord: QueryLog) => {
|
||||
useConsoleStore().putLog('query', logRecord);
|
||||
});
|
||||
|
||||
ipcRenderer.on('toggle-console', () => {
|
||||
|
@@ -32,8 +32,8 @@ export default class {
|
||||
return ipcRenderer.invoke('unregister-shortcuts');
|
||||
}
|
||||
|
||||
static readFile (path: string): Promise<string> {
|
||||
return ipcRenderer.invoke('read-file', path);
|
||||
static readFile (params: {filePath: string; encoding: string}): Promise<string> {
|
||||
return ipcRenderer.invoke('read-file', params);
|
||||
}
|
||||
|
||||
static writeFile (path: string, content: unknown) {
|
||||
|
9
src/renderer/libs/camelize.ts
Normal file
9
src/renderer/libs/camelize.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export const camelize = (text: string) => {
|
||||
const textArr = text.split('-');
|
||||
for (let i = 0; i < textArr.length; i++) {
|
||||
if (i === 0) continue;
|
||||
textArr[i] = textArr[i].charAt(0).toUpperCase() + textArr[i].slice(1);
|
||||
}
|
||||
|
||||
return textArr.join('');
|
||||
};
|
@@ -38,7 +38,7 @@ const connStringConstruct = (args: ConnectionParams & { pgConnString?: string })
|
||||
args.sshPort = stringArgs.sshPort;
|
||||
|
||||
// ssl mode
|
||||
args.ssl = checkForSSl(args.pgConnString);
|
||||
args.ssl = checkForSSl(args.pgConnString) || args.ssl;
|
||||
args.cert = stringArgs.sslcert;
|
||||
args.key = stringArgs.sslkey;
|
||||
args.ca = stringArgs.sslrootcert;
|
||||
|
@@ -349,11 +349,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.query-console {
|
||||
.console {
|
||||
border-top: 1px solid #444;
|
||||
background-color: $bg-color-dark;
|
||||
|
||||
.query-console-log {
|
||||
.console-log {
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: $bg-color-gray;
|
||||
|
@@ -173,11 +173,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.query-console {
|
||||
.console {
|
||||
border-top: 1px solid darken($bg-color-light-gray, 15%);
|
||||
background-color: $bg-color-light;
|
||||
|
||||
.query-console-log {
|
||||
.console-log {
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: $bg-color-light-gray;
|
||||
|
@@ -5,7 +5,11 @@ import { ipcRenderer } from 'electron';
|
||||
import * as Store from 'electron-store';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import { i18n } from '@/i18n';
|
||||
import { useWorkspacesStore } from '@/stores/workspaces';
|
||||
|
||||
import { useNotificationsStore } from './notifications';
|
||||
|
||||
let key = localStorage.getItem('key');
|
||||
|
||||
export interface SidebarElement {
|
||||
@@ -16,8 +20,11 @@ export interface SidebarElement {
|
||||
color?: string;
|
||||
name?: string;
|
||||
icon?: null | string;
|
||||
hasCustomIcon?: boolean;
|
||||
}
|
||||
|
||||
export interface CustomIcon {base64: string; uid: string}
|
||||
|
||||
if (!key) { // If no key in local storace
|
||||
const storedKey = ipcRenderer.sendSync('get-key');// Ask for key stored on disk
|
||||
|
||||
@@ -44,7 +51,8 @@ export const useConnectionsStore = defineStore('connections', {
|
||||
state: () => ({
|
||||
connections: persistentStore.get('connections', []) as ConnectionParams[],
|
||||
lastConnections: persistentStore.get('lastConnections', []) as {uid: string; time: number}[],
|
||||
connectionsOrder: persistentStore.get('connectionsOrder', []) as SidebarElement[]
|
||||
connectionsOrder: persistentStore.get('connectionsOrder', []) as SidebarElement[],
|
||||
customIcons: persistentStore.get('custom_icons', []) as CustomIcon[]
|
||||
}),
|
||||
getters: {
|
||||
getConnectionByUid: state => (uid:string) => state.connections.find(connection => connection.uid === uid),
|
||||
@@ -74,7 +82,8 @@ export const useConnectionsStore = defineStore('connections', {
|
||||
.find(connection => connection.uid === uid),
|
||||
getFolders: state => state.connectionsOrder.filter(conn => conn.isFolder),
|
||||
getConnectionFolder: state => (uid:string) => state.connectionsOrder
|
||||
.find(folder => folder.isFolder && folder.connections.includes(uid))
|
||||
.find(folder => folder.isFolder && folder.connections.includes(uid)),
|
||||
getIconByUid: state => (uid:string) => state.customIcons.find(i => i.uid === uid)
|
||||
},
|
||||
actions: {
|
||||
addConnection (connection: ConnectionParams) {
|
||||
@@ -198,7 +207,8 @@ export const useConnectionsStore = defineStore('connections', {
|
||||
client: conn.client,
|
||||
uid: conn.uid,
|
||||
icon: conn.icon,
|
||||
name: conn.name
|
||||
name: conn.name,
|
||||
hasCustomIcon: conn.hasCustomIcon
|
||||
});
|
||||
|
||||
connIndex = connections.findIndex((conn, i) => conn.uid === el.uid && i !== el.index);
|
||||
@@ -247,15 +257,41 @@ export const useConnectionsStore = defineStore('connections', {
|
||||
this.connectionsOrder = (this.connectionsOrder as SidebarElement[]).filter(el => !emptyFolders.includes(el.uid));
|
||||
persistentStore.set('connectionsOrder', this.connectionsOrder);
|
||||
},
|
||||
// Custom Icons
|
||||
addIcon (svg: string) {
|
||||
if (svg.length > 16384) {
|
||||
const { t } = i18n.global;
|
||||
useNotificationsStore().addNotification({
|
||||
status: 'error',
|
||||
message: t('application.sizeLimitError', { size: '16KB' })
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const icon: CustomIcon = {
|
||||
uid: uidGen('I'),
|
||||
base64: svg
|
||||
};
|
||||
|
||||
this.customIcons.push(icon);
|
||||
persistentStore.set('custom_icons', this.customIcons);
|
||||
},
|
||||
removeIcon (uid: string) {
|
||||
this.customIcons = this.customIcons.filter((i: CustomIcon) => i.uid !== uid);
|
||||
persistentStore.set('custom_icons', this.customIcons);
|
||||
},
|
||||
importConnections (importObj: {
|
||||
connections: ConnectionParams[];
|
||||
connectionsOrder: SidebarElement[];
|
||||
customIcons: CustomIcon[];
|
||||
}) {
|
||||
this.connections = [...this.connections, ...importObj.connections];
|
||||
this.connectionsOrder = [...this.connectionsOrder, ...importObj.connectionsOrder];
|
||||
this.customIcons = [...this.customIcons, ...importObj.customIcons];
|
||||
|
||||
persistentStore.set('connections', this.connections);
|
||||
persistentStore.set('connectionsOrder', this.connectionsOrder);
|
||||
persistentStore.set('customIcons', this.customIcons);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -1,56 +1,63 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import { useWorkspacesStore } from './workspaces';
|
||||
const logsSize = 1000;
|
||||
|
||||
export interface ConsoleRecord {
|
||||
export type LogType = 'query' | 'debug'
|
||||
export interface QueryLog {
|
||||
cUid: string;
|
||||
sql: string;
|
||||
date: Date;
|
||||
}
|
||||
|
||||
export interface DebugLog {
|
||||
level: 'log' | 'info' | 'warn' | 'error';
|
||||
process: 'renderer' | 'main' | 'worker';
|
||||
message: string;
|
||||
date: Date;
|
||||
}
|
||||
|
||||
export const useConsoleStore = defineStore('console', {
|
||||
state: () => ({
|
||||
records: [] as ConsoleRecord[],
|
||||
consolesHeight: new Map<string, number>(),
|
||||
consolesOpened: new Set([])
|
||||
isConsoleOpen: false,
|
||||
queryLogs: [] as QueryLog[],
|
||||
debugLogs: [] as DebugLog[],
|
||||
selectedTab: 'query' as LogType,
|
||||
consoleHeight: 0
|
||||
}),
|
||||
getters: {
|
||||
getLogsByWorkspace: state => (uid: string) => state.records.filter(r => r.cUid === uid),
|
||||
isConsoleOpen: state => (uid: string) => state.consolesOpened.has(uid),
|
||||
consoleHeight: state => {
|
||||
const uid = useWorkspacesStore().getSelected;
|
||||
return state.consolesHeight.get(uid) || 0;
|
||||
}
|
||||
getLogsByWorkspace: state => (uid: string) => state.queryLogs.filter(r => r.cUid === uid)
|
||||
},
|
||||
actions: {
|
||||
putLog (record: ConsoleRecord) {
|
||||
this.records.push(record);
|
||||
putLog (type: LogType, record: QueryLog | DebugLog) {
|
||||
if (type === 'query') {
|
||||
this.queryLogs.push(record);
|
||||
|
||||
if (this.records.length > logsSize)
|
||||
this.records = this.records.slice(0, logsSize);
|
||||
if (this.queryLogs.length > logsSize)
|
||||
this.queryLogs = this.queryLogs.slice(0, logsSize);
|
||||
}
|
||||
else if (type === 'debug') {
|
||||
this.debugLogs.push(record);
|
||||
|
||||
if (this.debugLogs.length > logsSize)
|
||||
this.debugLogs = this.debugLogs.slice(0, logsSize);
|
||||
}
|
||||
},
|
||||
openConsole () {
|
||||
const uid = useWorkspacesStore().getSelected;
|
||||
this.consolesOpened.add(uid);
|
||||
this.consolesHeight.set(uid, 250);
|
||||
this.isConsoleOpen = true;
|
||||
this.consoleHeight = 250;
|
||||
},
|
||||
closeConsole () {
|
||||
const uid = useWorkspacesStore().getSelected;
|
||||
this.consolesOpened.delete(uid);
|
||||
this.consolesHeight.set(uid, 0);
|
||||
this.isConsoleOpen = false;
|
||||
this.consoleHeight = 0;
|
||||
},
|
||||
resizeConsole (height: number) {
|
||||
const uid = useWorkspacesStore().getSelected;
|
||||
if (height < 30)
|
||||
this.closeConsole();
|
||||
else
|
||||
this.consolesHeight.set(uid, height);
|
||||
this.consoleHeight = height;
|
||||
},
|
||||
toggleConsole () {
|
||||
const uid = useWorkspacesStore().getSelected;
|
||||
|
||||
if (this.consolesOpened.has(uid))
|
||||
if (this.isConsoleOpen)
|
||||
this.closeConsole();
|
||||
else
|
||||
this.openConsole();
|
||||
|
Reference in New Issue
Block a user