initial commit

This commit is contained in:
Fabio Di Stasio 2023-03-19 18:40:03 +01:00
commit 9f945c4b8f
90 changed files with 34458 additions and 0 deletions

27
.all-contributorsrc Normal file
View File

@ -0,0 +1,27 @@
{
"projectName": "mizar",
"projectOwner": "Fabio286",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
"README.md"
],
"imageSize": 100,
"commit": false,
"commitConvention": "angular",
"contributors": [
{
"login": "Fabio286",
"name": "Fabio Di Stasio",
"avatar_url": "https://avatars.githubusercontent.com/u/31471771?v=4",
"profile": "https://fabiodistasio.it/",
"contributions": [
"code",
"translation",
"doc"
]
}
],
"contributorsPerLine": 7,
"skipCi": true
}

5
.eslintignore Normal file
View File

@ -0,0 +1,5 @@
node_modules
assets
out
dist
build

96
.eslintrc Normal file
View File

@ -0,0 +1,96 @@
{
"env": {
"browser": true,
"es6": true,
"node": true
},
"extends": [
"standard",
"plugin:@typescript-eslint/recommended",
"plugin:vue/vue3-recommended"
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser",
"ecmaVersion": 9,
"sourceType": "module",
"requireConfigFile": false
},
"plugins": [
"vue",
"@typescript-eslint"
],
"rules": {
"space-infix-ops": "off",
"object-curly-newline": "off",
"indent": [
"error",
3,
{
"SwitchCase": 1
}
],
"linebreak-style": [
"error",
"unix"
],
"brace-style": [
"error",
"stroustrup"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
],
"curly": [
"error",
"multi-or-nest"
],
"no-console": "off",
"no-undef": "off",
"vue/no-side-effects-in-computed-properties": "off",
"vue/multi-word-component-names": "off",
"vue/require-default-prop": "off",
"vue/comment-directive": "off",
"vue/no-v-html": "off",
"vue/html-indent": [
"error",
3,
{
"attribute": 1,
"baseIndent": 1,
"closeBracket": 0,
"ignores": []
}
],
"vue/max-attributes-per-line": [
"error",
{
"singleline": {
"max": 2
},
"multiline": {
"max": 1
}
}
],
"@typescript-eslint/member-delimiter-style": [
"warn",
{
"multiline": {
"delimiter": "semi",
"requireLast": true
},
"singleline": {
"delimiter": "semi",
"requireLast": false
}
}
],
"@typescript-eslint/no-var-requires": "off"
}
}

6
.gitattributes vendored Normal file
View File

@ -0,0 +1,6 @@
* text eol=lf
*.jpg binary
*.png binary
*.gif binary
*.ico binary
*.icns binary

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
.DS_Store
dist
build
misc/*
!misc/.gitkeep
node_modules
thumbs.db
NOTES.md
*.txt
*.heapsnapshot

18
.stylelintrc Normal file
View File

@ -0,0 +1,18 @@
{
"extends": [
"stylelint-config-standard",
"stylelint-config-recommended-vue"
],
"fix": true,
"formatter": "verbose",
"plugins": [
"stylelint-scss"
],
"rules": {
"at-rule-no-unknown": null,
"no-descending-specificity": null,
"font-family-no-missing-generic-family-keyword": null,
"declaration-colon-newline-after": "always-multi-line"
},
"syntax": "scss"
}

7
.versionrc.json Normal file
View File

@ -0,0 +1,7 @@
{
"types": [
{"type":"feat","section":"Features"},
{"type":"perf","section":"Improvements"},
{"type":"fix","section":"Bug Fixes"}
]
}

133
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,133 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
fabio286@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

100
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,100 @@
# Contributors Guide
Antares SQL is an application based on [Electron.js](https://www.electronjs.org/) that uses [Vue.js](https://vuejs.org/) and [Spectre.css](https://picturepan2.github.io/spectre/) as frontend frameworks.
For the build process it takes advantage of [electron-builder](https://www.electron.build/).
This application uses [Pinia🍍](https://pinia.vuejs.org/) as application state manager and [electron-store](https://github.com/sindresorhus/electron-store) to save the various settings on disc.
This guide aims to provide useful information and guidelines to everyone wants to contribute with this open-source project.
For every other question related to this project please [contact me](https://github.com/Fabio286).
## Project Structure
The main files of the application are located inside `src` folder and are groupped in three subfolders.
### `common`
This folder contains small libraries, classes and objects. The purpose of `common` folder is to group together utilities used by **renderer** and **main** processes.
Noteworthy is the `customizations` folder that contains clients related customizations. Those settings are merged with `default.js` that lists every option.
Client related customizations are stored on Pinia and can be accessed by `customizations` property of current workspace object, or importing `common/customizations`.
An use case of customizations object can be the following:
```js
computed: {
defaultEngine () {
if (this.workspace.customizations.engines)
return this.workspace.engines.find(engine => engine.isDefault).name;
return '';
}
}
```
In this case the computed property `defaultEngine` returns the default engine for MySQL client, or an empty string with PostgreSQL that doesn't have engines.
Customization properties are also useful **if some features are ready for one client but not others**.
### `main`
Inside this folder are located all files required by main process.
`ipc-handlers` subfolder includes all IPC handlers for events sent from renderer process.
`libs` subfolder includes classes related to clients and **query and connection logics**.
**Everything above client's class level should be "client agnostic"** with a neutral and uniformed api interface
### `renderer`
In this folder is located the structure of Vue frontend application.
## Build
The command to build Antares SQL locally is `npm run build`.
## Conventions
### Electron
- **kebab-case** for IPC event names.
### Vue
- **PascalCase** for file names (with .vue extension) and including components inside others (`<MyComponent/>`).
- "**Base**" prefix for [base component names](https://vuejs.org/v2/style-guide/#Base-component-names-strongly-recommended).
- "**The**" prefix for [single-instance component names](https://vuejs.org/v2/style-guide/#Single-instance-component-names-strongly-recommended).
- [Tightly coupled component names](https://vuejs.org/v2/style-guide/#Tightly-coupled-component-names-strongly-recommended).
- [Order of words in component names](https://vuejs.org/v2/style-guide/#Order-of-words-in-component-names-strongly-recommended).
- **kebab-case** in templates for property and event names.
### Code Style
The project includes [ESlint](https://eslint.org/) and [StyleLint](https://stylelint.io/) config files with style rules. I recommend to set the lint on-save option in your code editor.
Alternatively you can launch following commands to lint the project.
Check if all the style rules have been followed:
```console
npm run lint
```
Apply style rules globally if possible:
```console
npm run lint:fix
```
### Other recommendations
Please, use if possible **template literals** to compose strings and **avoid unnecessary dependencies**.
### Commits
The commit style adopted for this project is [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
Basicly it's important to have **single scoped commits with a prefix** that follows this style because Antares SQL uses [standard-version](https://github.com/conventional-changelog/standard-version) to generate new releases and [CHANGELOG.md](https://github.com/Fabio286/antares/blob/master/CHANGELOG.md) file to track all notable changes.
For Visual Studio Code users may be useful [Conventional Commits](https://marketplace.visualstudio.com/items?itemName=vivaxy.vscode-conventional-commits) extension.
## Debug
**Debug mode**:
```console
npm run debug
```
After running the debug mode Antares will listen on port 9222 (main process) for a debugger.
On **Visual Studio Code** just launch "*Electron: Main*" configurations after running Antares in debug mode.

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
The MIT License (MIT)
Copyright (c) 2020 Fabio Di Stasio
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

22390
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

178
package.json Normal file
View File

@ -0,0 +1,178 @@
{
"name": "mizar",
"productName": "Mizar TCP Debugger",
"version": "1.0.0",
"description": "A TCP client/server debug tool",
"license": "MIT",
"scripts": {
"debug": "npm run rebuild:electron && npm run debug-runner",
"debug-runner": "node scripts/devRunner.js --remote-debug",
"compile": "npm run compile:main && npm run compile:workers && npm run compile:renderer",
"compile:main": "webpack --mode=production --config webpack.main.config.js",
"compile:workers": "webpack --mode=production --config webpack.workers.config.js",
"compile:renderer": "webpack --mode=production --config webpack.renderer.config.js",
"build": "cross-env NODE_ENV=production npm run compile && electron-builder --publish never",
"build:appx": "npm run build -- --win appx",
"rebuild:electron": "rimraf ./dist && npm run postinstall && npm run devtools:install",
"release": "standard-version",
"release:pre": "npm run release -- --prerelease alpha",
"devtools:install": "node scripts/devtoolsInstaller",
"postinstall": "electron-builder install-app-deps",
"test:e2e": "npm run compile && npm run test:e2e-dry",
"test:e2e-dry": "xvfb-maybe -- playwright test",
"lint": "eslint . --ext .js,.ts,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
"lint:fix": "eslint . --ext .js,.ts,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
"contributors:add": "all-contributors add",
"contributors:generate": "all-contributors generate"
},
"author": "Fabio Di Stasio <fabio286@gmail.com>",
"main": "./dist/main.js",
"mizar": {
"devtoolsId": "nhdogjmejiglipccpnnnanhbledajbpd"
},
"build": {
"appId": "com.fabio286.mizar",
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
"asar": true,
"buildDependenciesFromSource": true,
"directories": {
"output": "build",
"buildResources": "assets"
},
"asarUnpack": "**\\*.{node,dll}",
"files": [
"dist/**/*",
"node_modules",
"package.json"
],
"win": {
"target": [
"nsis",
"portable"
]
},
"linux": {
"target": [
{
"target": "deb",
"arch": [
"x64",
"armv7l",
"arm64"
]
},
{
"target": "AppImage",
"arch": [
"x64",
"armv7l",
"arm64"
]
}
],
"icon": "assets/linux",
"category": "Development"
},
"appImage": {
"license": "./LICENSE",
"category": "Development"
},
"nsis": {
"license": "./LICENSE",
"installerIcon": "assets/icon.ico",
"uninstallerIcon": "assets/icon.ico",
"installerHeader": "assets/icon.ico"
},
"portable": {
"artifactName": "${productName}-${version}-portable.exe"
},
"appx": {
"displayName": "Antares SQL",
"backgroundColor": "transparent",
"showNameOnTiles": true,
"identityName": "62514FabioDiStasio.AntaresSQLClient",
"publisher": "CN=1A2729ED-865C-41D2-9038-39AE2A63AA52",
"applicationId": "FabioDiStasio.AntaresSQLClient"
},
"dmg": {
"contents": [
{
"x": 130,
"y": 220
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
]
}
},
"dependencies": {
"@electron/remote": "~2.0.1",
"@mdi/font": "~7.1.96",
"@vueuse/core": "~8.7.5",
"electron-log": "~4.4.1",
"electron-store": "~8.1.0",
"electron-updater": "~4.6.5",
"electron-window-state": "~5.0.3",
"encoding": "~0.1.13",
"floating-vue": "~2.0.0-beta.20",
"moment": "~2.29.4",
"pinia": "~2.0.28",
"source-map-support": "~0.5.20",
"vue": "~3.2.45",
"vue-i18n": "~9.2.2"
},
"devDependencies": {
"@babel/eslint-parser": "~7.15.7",
"@babel/preset-env": "~7.15.8",
"@babel/preset-typescript": "~7.16.7",
"@playwright/test": "~1.28.1",
"@types/node": "~17.0.23",
"@typescript-eslint/eslint-plugin": "~5.18.0",
"@typescript-eslint/parser": "~5.18.0",
"@vue/compiler-sfc": "~3.2.33",
"all-contributors-cli": "~6.20.0",
"babel-loader": "~8.2.3",
"chalk": "~4.1.2",
"cross-env": "~7.0.2",
"css-loader": "~6.5.0",
"electron": "~22.0.3",
"electron-builder": "~22.10.3",
"eslint": "~7.32.0",
"eslint-config-standard": "~16.0.3",
"eslint-plugin-import": "~2.24.2",
"eslint-plugin-node": "~11.1.0",
"eslint-plugin-promise": "~5.2.0",
"eslint-plugin-vue": "~8.0.3",
"file-loader": "~6.2.0",
"html-webpack-plugin": "~5.5.0",
"mini-css-extract-plugin": "~2.4.5",
"node-loader": "~2.0.0",
"playwright": "~1.28.1",
"playwright-core": "~1.28.1",
"postcss-html": "~1.5.0",
"progress-webpack-plugin": "~1.0.12",
"rimraf": "~3.0.2",
"sass": "~1.42.1",
"sass-loader": "~12.3.0",
"standard-version": "~9.3.1",
"style-loader": "~3.3.1",
"stylelint": "~14.9.1",
"stylelint-config-recommended-vue": "~1.4.0",
"stylelint-config-standard": "~26.0.0",
"stylelint-scss": "~4.3.0",
"tree-kill": "~1.2.2",
"ts-loader": "~9.2.8",
"typescript": "~4.6.3",
"unzip-crx-3": "~0.2.0",
"vue-eslint-parser": "~8.3.0",
"vue-loader": "~16.8.3",
"webpack": "~5.72.0",
"webpack-cli": "~4.9.1",
"webpack-dev-server": "~4.11.1",
"xvfb-maybe": "~0.2.1"
}
}

131
scripts/devRunner.js Normal file
View File

@ -0,0 +1,131 @@
process.env.NODE_ENV = 'development';
// process.env.ELECTRON_ENABLE_LOGGING = true
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = false;
const chalk = require('chalk');
const electron = require('electron');
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const kill = require('tree-kill');
const path = require('path');
const { spawn } = require('child_process');
const mainConfig = require('../webpack.main.config');
const rendererConfig = require('../webpack.renderer.config');
const workersConfig = require('../webpack.workers.config');
let electronProcess = null;
let manualRestart = null;
const remoteDebugging = process.argv.includes('--remote-debug');
if (remoteDebugging) {
// disable devtools open in electron
process.env.RENDERER_REMOTE_DEBUGGING = true;
}
async function killElectron (pid) {
return new Promise((resolve, reject) => {
if (pid) {
kill(pid, 'SIGKILL', err => {
if (err) reject(err);
resolve();
});
}
else
resolve();
});
}
async function restartElectron () {
console.log(chalk.gray('\nStarting electron...'));
const { pid } = electronProcess || {};
await killElectron(pid);
electronProcess = spawn(electron, [
path.join(__dirname, '../dist/main.js'),
// '--enable-logging', // Enable to show logs from all electron processes
remoteDebugging ? '--inspect=9222' : '',
remoteDebugging ? '--remote-debugging-port=9223' : ''
]);
electronProcess.stdout.on('data', data => {
console.log(chalk.white(data.toString()));
});
electronProcess.stderr.on('data', data => {
console.error(chalk.red(data.toString()));
});
electronProcess.on('exit', () => {
if (!manualRestart) process.exit(0);
});
}
function startMain () {
const webpackSetup = webpack([mainConfig, workersConfig]);
webpackSetup.compilers.forEach((compiler) => {
const { name } = compiler;
switch (name) {
case 'workers':
compiler.hooks.afterEmit.tap('afterEmit', async () => {
console.log(chalk.gray(`\nCompiled ${name} script!`));
console.log(
chalk.gray(`\nWatching file changes for ${name} script...`)
);
});
break;
case 'main':
default:
compiler.hooks.afterEmit.tap('afterEmit', async () => {
console.log(chalk.gray(`\nCompiled ${name} script!`));
manualRestart = true;
await restartElectron();
setTimeout(() => {
manualRestart = false;
}, 2500);
console.log(
chalk.gray(`\nWatching file changes for ${name} script...`)
);
});
break;
}
});
webpackSetup.watch({ aggregateTimeout: 500 }, err => {
if (err) console.error(chalk.red(err));
});
}
function startRenderer (callback) {
const compiler = webpack(rendererConfig);
const { name } = compiler;
compiler.hooks.afterEmit.tap('afterEmit', () => {
console.log(chalk.gray(`\nCompiled ${name} script!`));
console.log(chalk.gray(`\nWatching file changes for ${name} script...`));
});
const server = new WebpackDevServer(compiler, {
port: 9080,
client: {
overlay: true,
logging: 'warn'
}
});
server.startCallback(err => {
if (err) console.error(chalk.red(err));
callback();
});
}
startRenderer(startMain);

View File

@ -0,0 +1,49 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
const fs = require('fs');
const path = require('path');
const https = require('https');
const unzip = require('unzip-crx-3');
const { antares } = require('../package.json');
const extensionID = antares.devtoolsId;
const destFolder = path.resolve(__dirname, `../misc/${extensionID}`);
const filePath = path.resolve(__dirname, `${destFolder}${extensionID}.crx`);
const fileUrl = `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${extensionID}%26uc&prodversion=32`;
const fileStream = fs.createWriteStream(filePath);
const downloadFile = url => {
return new Promise((resolve, reject) => {
const request = https.get(url);
request.on('response', response => {
if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
return downloadFile(response.headers.location)
.then(resolve)
.catch(reject);
}
response.pipe(fileStream);
response.on('close', () => {
console.log('Devtools download completed!');
resolve();
});
response.on('error', reject);
});
request.on('error', reject);
request.end();
});
};
(async () => {
try {
await downloadFile(fileUrl);
await unzip(filePath, destFolder);
fs.unlinkSync(filePath);
fs.unlinkSync(`${destFolder}/package.json`);// <- Avoid to display annoyng npm script in vscode
}
catch (error) {
console.log(error);
}
})();

408
src/main/libs/Sender.js Normal file
View File

@ -0,0 +1,408 @@
'use strict';
const net = require('net');
const fs = require('fs');
class Sender {
/**
*Creates an instance of Sender.
* @param {*} process Processo dove inviare i log
* @memberof Sender
*/
constructor (process) {
this.process = process;
this.closeOnEcho = true;
this.persistentConnection = false;
this.nMsgs = 0;
this.tMin = 0;
this.tMax = 0;
this.nClients = 1;
this.trace = false;
this.alertReset = false;
this.hexMsg = false;
this.nConnected = 0;
this.nClosed = 0;
this.nTryConnect = 0;
this.nReceived = [];
this.nSent = 0;
this.timeStart = new Date();
this.hosts = [];
this.messages = [];
this.nHostClients = [];
this.nHostBytes = [];
this.nHostMsgs = [];
this.storagePath = '';
}
/**
* Setta gli hosts
*
* @param {*} hosts
* @memberof Sender
*/
setHosts (hosts) {
this.hosts = hosts;
}
/**
* Setta i parametri del messaggio
*
* @param {*} params
* @memberof Sender
*/
setParams (params) {
this.closeOnEcho = params.closeOnEcho;
this.persistentConnection = params.persistentConnection;
this.nMsgs = params.nMsgs;
this.tMin = params.tMin;
this.tMax = params.tMax;
this.nClients = params.nClients;
this.trace = params.trace;
this.alertReset = params.alertReset;
this.loop = params.loop;
}
/**
* Setta il percorso della cartella storage
*
* @param {string} storagePath
* @memberof Sender
*/
setStoragePath (storagePath) {
this.storagePath = storagePath;
}
/**
* Carica i messaggi in memoria
*
* @memberof Sender
*/
loadMessages () {
let self = this;
if (self.trace) this.sendLog('Lettura dei messaggi');
let messages = fs.readFileSync(`${self.storagePath}/storage/clientMessages.json`);
messages = JSON.parse(messages);
this.messages = messages.filter((message) => {
return message.enabled === true;
});
if (self.trace) this.sendLog(`Messaggi caricari: ${this.messages.length}`);
}
/**
* Invia i log al render process
*
* @param {string} message Messaggio del log
* @param {string} [color=''] Colore del log (green, yellow, red)
* @memberof Sender
*/
sendLog (message, color = '') {
let log = {
event: 'log',
content: { message, color }
};
this.process.send(log);
}
/** Restituisce un messaggio casuale */
randMsg () {
let self = this;
if (self.messages.length > 0) {
let index = Math.floor((Math.random() * (self.messages.length)));
let msg;
switch (self.messages[index].format) {
case 'ascii':
msg = Buffer.from(self.messages[index].message, 'ascii');
break;
case 'hex':
msg = Buffer.from(self.messages[index].message.replace(/\s|0x/g, ''), 'hex');
break;
case 'binary':
msg = Buffer.from(self.messages[index].message.replace(/\s/g, ''), 'binary');
break;
}
return msg;
}
else return 'Nessun messaggio specificato';
};
/**
* Istanzia i client e invia i messaggi
*
* @param {*} params Parametri dei client
* @memberof Sender
*/
startFullTest (callback) {
let self = this;
/** Carica in memoria i messaggi */
self.loadMessages();
/** Applica uno sleep */
function delay () {
let wait = Math.floor((Math.random() * self.tMax) + self.tMin);
return new Promise(resolve => setTimeout(resolve, wait));
}
for (let x = 0; x < self.hosts.length; x++) { // hosts for
let params = self.hosts[x];
self.hosts[x].clients = [];
self.nHostClients[x] = 0;
self.nHostMsgs[x] = self.nHostMsgs[x] === undefined ? 0 : self.nHostMsgs[x];
self.nHostBytes[x] = self.nHostBytes[x] === undefined ? 0 : self.nHostBytes[x];
self.nReceived[x] = self.nReceived[x] === undefined ? 0 : self.nReceived[x];
for (let i = 0; i < self.nClients; i++) { // clients for
self.hosts[x].clients[i] = new net.Socket();
let client = self.hosts[x].clients[i];
let clientId = i + 1;
try {
client.connect(params, () => {
if (self.trace) self.sendLog(`Socket #${clientId} su ${params.host}:${params.port} aperto`);
self.nHostClients[x]++;
(async () => {
for (let i = 0; i < self.nMsgs; i++) { // msg for
await delay();
let msg = self.randMsg();
client.write(msg, err => {
if (err)
self.sendLog(`Socket #${clientId} su ${params.host}:${params.port}:\nErrore messaggio: ${err}`, 'red');
else {
self.nSent++;
self.nHostMsgs[x]++;
self.nHostBytes[x] += msg.length;
}
});
if (self.trace) self.sendLog(`Socket #${clientId} su ${params.host}:${params.port} messaggio #${i + 1}`);
if (i + 1 === self.nMsgs && !self.closeOnEcho && !self.persistentConnection) client.end();
}// <- msg for
})();
});
}
catch (err) {
self.sendLog(`Socket #${clientId} su ${params.host}:${params.port}:\n${err}`, 'red');
}
client.on('connect', err => {
self.nTryConnect++;
if (err)
self.sendLog(`Errore connessione #${clientId} su ${params.host}:${params.port}:\n${err}`, 'red');
else
self.nConnected++;
// if (self.nConnected === (self.nClients * self.hosts.length)) self.getReport();
});
client.on('data', data => {
self.nReceived[x]++;
if (self.closeOnEcho)
client.end();
if (self.trace) self.sendLog(`Socket #${clientId} su ${params.host}:${params.port} risposta: ${data}`);
});
client.on('close', () => {
self.nClosed++;
if (self.trace) self.sendLog(`Socket #${clientId} su ${params.host}:${params.port} chiuso`);
// Misura tempo esecuzione
if (self.nClosed === self.nTryConnect) {
if (!self.loop) self.getConsoleReports();
callback();
}
});
client.on('error', err => {
switch (err.code) {
case 'ECONNRESET':
if (self.alertReset)
self.sendLog(`Socket #${clientId} su ${params.host}:${params.port}:\n${err}`, 'yellow');
break;
default:
self.sendLog(`Socket #${clientId} su ${params.host}:${params.port}:\n${err}`, 'red');
}
});
}// <- clients for
}// <- hosts for
}
/**
* Connette i client per il test a step
*
* @param {*} callback
* @memberof Sender
*/
connectClients (callback) {
let self = this;
for (let x = 0; x < self.hosts.length; x++) { // hosts for
let params = self.hosts[x];
self.hosts[x].clients = [];
self.nHostMsgs[x] = 0;
self.nHostBytes[x] = 0;
self.nHostClients[x] = 0;
self.nReceived[x] = 0;
for (let i = 0; i < self.nClients; i++) { // clients for
self.hosts[x].clients[i] = new net.Socket();
let client = self.hosts[x].clients[i];
let clientId = i + 1;
try {
client.connect(params, () => {
if (self.trace) self.sendLog(`Socket #${clientId} su ${params.host}:${params.port} aperto`);
self.nHostClients[x]++;
});
}
catch (err) {
self.sendLog(`Socket #${clientId} su ${params.host}:${params.port}:\n${err}`, 'red');
}
client.on('connect', err => {
self.nTryConnect++;
if (err)
self.sendLog(`Errore connessione #${clientId} su ${params.host}:${params.port}:\n${err}`, 'red');
else
self.nConnected++;
// if (self.nConnected === (self.nClients * self.hosts.length)) self.getReport();
if ((self.nClients * self.hosts.length) === self.nTryConnect) callback();
});
client.on('data', data => {
self.nReceived[x]++;
if (self.closeOnEcho)
client.end();
if (self.trace) self.sendLog(`Socket #${clientId} su ${params.host}:${params.port} risposta: ${data}`);
});
client.on('close', () => {
self.nClosed++;
if (self.trace) self.sendLog(`Socket #${clientId} su ${params.host}:${params.port} chiuso`);
});
client.on('error', err => {
switch (err.code) {
case 'ECONNRESET':
if (self.alertReset)
self.sendLog(`Socket #${clientId} su ${params.host}:${params.port}:\n${err}`, 'yellow');
break;
default:
self.sendLog(`Socket #${clientId} su ${params.host}:${params.port}:\n${err}`, 'red');
}
});
}// <- clients for
}// <- hosts for
}
/**
* Invia i messaggi nella modalità a step
*
* @param {*} callback
* @memberof Sender
*/
sendMessages (callback) {
let self = this;
/** Carica in memoria i messaggi */
self.loadMessages();
self.nSent = 0;
/** Applica uno sleep */
function delay () {
let wait = Math.floor((Math.random() * self.tMax) + self.tMin);
return new Promise(resolve => setTimeout(resolve, wait));
}
for (let x = 0; x < self.hosts.length; x++) { // hosts for
for (let i = 0; i < self.hosts[x].clients.length; i++) { // clients for
let client = self.hosts[x].clients[i];
let params = self.hosts[x];
let clientId = i + 1;
(async () => {
for (let i = 0; i < self.nMsgs; i++) { // msg for
await delay();
let msg = self.randMsg();
client.write(msg, err => {
if (err)
self.sendLog(`Socket #${clientId} su ${params.host}:${params.port}:\nErrore messaggio: ${err}`, 'red');
else {
self.nSent++;
self.nHostMsgs[x]++;
self.nHostBytes[x] += msg.length;
if ((self.nMsgs * self.hosts.length * self.nClients) === self.nSent) callback();
}
});
if (self.trace) self.sendLog(`Socket #${clientId} su ${params.host}:${params.port} messaggio #${i + 1}`);
}// <- msg for
})();
}// <- clients for
}// <- hosts for
}
/** Genera il report su console */
getConsoleReports () {
let self = this;
let end = new Date() - self.timeStart;
let report = `Durata del test: ${end}ms`;
self.sendLog(report, 'green');
}
stopClients (callback) {
let self = this;
for (let x = 0; x < self.hosts.length; x++) {
for (let i = 0; i < self.hosts[x].clients.length; i++)
self.hosts[x].clients[i].end();
}
self.getConsoleReports();
callback();
}
getReports () {
let self = this;
let reportList = [];
for (let i = 0; i < self.hosts.length; i++) {
let report = {
host: `${self.hosts[i].host}:${self.hosts[i].port}`,
sockets: self.nHostClients[i],
data: self.nHostBytes[i],
messages: self.nHostMsgs[i],
received: self.nReceived[i]
};
reportList.push(report);
if ((i + 1) === self.hosts.length) {
let rep = {
event: 'report',
content: reportList
};
self.process.send(rep);
}
}
}
resetReports () {
let self = this;
for (let i = 0; i < self.hosts.length; i++) {
self.nHostBytes[i] = 0;
self.nHostMsgs[i] = 0;
self.nHostClients[i] = 0;
self.nReceived[i] = 0;
}
}
}
module.exports = Sender;

143
src/main/libs/Server.js Normal file
View File

@ -0,0 +1,143 @@
'use strict';
const net = require('net');
class Server {
constructor (process) {
this.process = process;
this.trace = false;
this.echo = false;
this.alertReset = false;
this.ports = [];
this.server = [];
this.nBytes = [];
this.nMsgs = [];
}
/**
* Setta le porte
*
* @param {*} ports
* @memberof Server
*/
setPorts (ports) {
this.ports = ports;
}
/**
* Invia i log al render process
*
* @param {string} message Messaggio del log
* @param {string} [color=''] Colore del log (green, yellow, red)
* @memberof Server
*/
sendLog (message, color = '') {
let log = {
event: 'log',
content: { message, color }
};
this.process.send(log);
}
startServer (params) {
let self = this;
self.trace = params.trace;
self.echo = params.echo;
self.alertReset = params.alertReset;
for (let i = 0; i < self.ports.length; i++) {
let port = self.ports[i].port;
self.server[i] = net.createServer();
self.nBytes[i] = 0;
self.nMsgs[i] = 0;
self.server[i].on('connection', socket => {
if (self.trace) self.sendLog(`Client connesso su porta ${port}`);
socket.on('data', msg => {
let msgString = msg.toString();
if (self.echo) socket.write(msg);
self.nBytes[i] += msg.length;
self.nMsgs[i]++;
if (self.trace) self.sendLog(`Messaggio ricevuto su porta ${port}: ${msgString}`);
});// <- socket data
socket.on('end', () => {
if (self.trace) self.sendLog(`Client disconnesso su porta ${port}`);
});
socket.on('error', (err) => {
switch (err.code) {
case 'ECONNRESET':
if (self.alertReset)
self.sendLog(`Errore client su porta ${port}: \n${err}`, 'yellow');
else
if (self.trace) self.sendLog(`Client disconnesso su porta ${port}`);
break;
default:
self.sendLog(`Errore client su porta ${port}: \n${err}`, 'red');
}
});
});// <- server
self.server[i].on('error', err => {
self.sendLog(`Errore server su porta ${port}: \n${err}`, 'red');
});
self.server[i].listen(port, () => {
self.sendLog(`In ascolto sulla porta ${port}`);
});
}
}
stopServer (callback) {
let self = this;
(async () => {
for (let i = 0; i < self.server.length; i++) {
await self.server[i].close(function () {
self.server[i].unref();
});
}
callback();
})();
}
getReports () {
let self = this;
let reportList = [];
for (let i = 0; i < self.server.length; i++) {
let report = {
port: self.server[i].address().port,
sockets: null,
data: self.nBytes[i],
messages: self.nMsgs[i]
};
self.server[i].getConnections((err, nSockets) => {
if (err) self.sendLog(`Errore report: \n${err}`, 'red');
report.sockets = nSockets;
reportList.push(report);
if ((i + 1) === self.server.length) {
let rep = {
event: 'report',
content: reportList
};
self.process.send(rep);
}
});
}
}
resetReports () {
let self = this;
for (let i = 0; i < self.server.length; i++) {
self.nBytes[i] = 0;
self.nMsgs[i] = 0;
}
}
}
module.exports = Server;

230
src/main/main.ts Normal file
View File

@ -0,0 +1,230 @@
import { app, BrowserWindow, nativeImage, ipcMain } from 'electron';
import * as fs from 'fs';
import * as path from 'path';
import * as Store from 'electron-store';
import { ChildProcess, fork, Serializable } from 'child_process';
import * as windowStateKeeper from 'electron-window-state';
import * as remoteMain from '@electron/remote/main';
// import ipcHandlers from './ipc-handlers';
Store.initRenderer();
const settingsStore = new Store({ name: 'settings' });
const appTheme = settingsStore.get('application_theme');
const isDevelopment = process.env.NODE_ENV !== 'production';
const isMacOS = process.platform === 'darwin';
const isLinux = process.platform === 'linux';
const isWindows = process.platform === 'win32';
const gotTheLock = app.requestSingleInstanceLock();
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
// global reference to mainWindow (necessary to prevent window from being garbage collected)
let mainWindow: BrowserWindow;
let mainWindowState: windowStateKeeper.State;
async function createMainWindow () {
const icon = require('../renderer/assets/icons/icon.png');
const window = new BrowserWindow({
width: mainWindowState.width,
height: mainWindowState.height,
x: mainWindowState.x,
y: mainWindowState.y,
minWidth: 900,
minHeight: 550,
show: !isWindows,
title: 'Mizar',
icon: nativeImage.createFromDataURL(icon.default),
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
devTools: isDevelopment,
spellcheck: false
},
autoHideMenuBar: true,
titleBarStyle: isLinux ? 'default' :'hidden',
titleBarOverlay: isWindows
? {
color: appTheme === 'dark' ? '#3f3f3f' : '#fff',
symbolColor: appTheme === 'dark' ? '#fff' : '#000',
height: 30
}
: false,
trafficLightPosition: isMacOS ? { x: 10, y: 8 } : undefined,
backgroundColor: '#1d1d1d'
});
mainWindowState.manage(window);
window.on('moved', saveWindowState);
remoteMain.enable(window.webContents);
try {
if (isDevelopment)
await window.loadURL('http://localhost:9080');
else {
const indexPath = path.resolve(__dirname, 'index.html');
await window.loadFile(indexPath);
}
}
catch (err) {
console.log(err);
}
window.on('closed', () => {
window.removeListener('moved', saveWindowState);
mainWindow = null;
});
return window;
}
if (!gotTheLock) app.quit();
else {
require('@electron/remote/main').initialize();
// Initialize ipcHandlers
// ipcHandlers();
ipcMain.on('refresh-theme-settings', () => {
const appTheme = settingsStore.get('application_theme');
if (isWindows && mainWindow) {
mainWindow.setTitleBarOverlay({
color: appTheme === 'dark' ? '#3f3f3f' : '#fff',
symbolColor: appTheme === 'dark' ? '#fff' : '#000'
});
}
});
ipcMain.on('change-window-title', (_, title: string) => {
if (mainWindow) mainWindow.setTitle(title);
});
// quit application when all windows are closed
app.on('window-all-closed', () => {
// on macOS it is common for applications to stay open until the user explicitly quits
if (!isMacOS) app.quit();
});
app.on('activate', async () => {
// on macOS it is common to re-create a window even after all windows have been closed
if (mainWindow === null)
mainWindow = await createMainWindow();
});
// create main BrowserWindow when electron is ready
app.on('ready', async () => {
mainWindowState = windowStateKeeper({
defaultWidth: 1024,
defaultHeight: 800
});
mainWindow = await createMainWindow();
if (isWindows)
mainWindow.show();
// if (isDevelopment)
// mainWindow.webContents.openDevTools();
process.on('uncaughtException', error => {
mainWindow.webContents.send('unhandled-exception', error);
});
process.on('unhandledRejection', error => {
mainWindow.webContents.send('unhandled-exception', error);
});
});
app.on('browser-window-created', (event, window) => {
if (isDevelopment) {
const { mizar } = require('../../package.json');
const extensionPath = path.resolve(__dirname, `../../misc/${mizar.devtoolsId}`);
window.webContents.session.loadExtension(extensionPath, { allowFileAccess: true }).catch(console.error);
}
});
}
// Server
let serverProcess: ChildProcess;
ipcMain.on('startServer', (event, { params, ports }) => {
event.sender.send('serverLog', { message: 'Server avviato', color: '' });
serverProcess = fork(isDevelopment ? './dist/serverProcess.js' : path.resolve(__dirname, './serverProcess.js'), [], {
execArgv: isDevelopment ? ['--inspect=9224'] : undefined
});
const message = {
event: 'start',
params,
ports
};
serverProcess.send(message);
serverProcess.on('message', (message: Serializable & {event: string; content: string}) => {
if (!mainWindow) return;
switch (message.event) {
case 'log':
mainWindow.webContents.send('serverLog', message.content);
break;
case 'report':
mainWindow.webContents.send('reportServerList', message.content);
break;
}
});
});
ipcMain.on('stopServer', (event) => {
try {
serverProcess.send({ event: 'stop' });
event.sender.send('serverFinish', 'Server stoppato');
}
catch (error) {
serverProcess.kill();
}
});
ipcMain.on('resetReports', () => {
if (!mainWindow) return;
try {
serverProcess.send({ event: 'reset' });
}
catch (error) {
const data = {
message: error.stack,
color: 'red'
};
mainWindow.webContents.send('serverLog', data);
}
});
ipcMain.on('getPorts', (event) => {
try {
let ports = fs.readFileSync(`${storagePath}/storage/serverPorts.json`);
ports = JSON.parse(ports);
event.sender.send('portList', ports);
}
catch (error) {
const data = {
message: error.stack,
color: 'red'
};
event.sender.send('serverLog', data);
}
});
ipcMain.on('updatePorts', (event, messageList) => {
try {
fs.writeFileSync(`${storagePath}/storage/serverPorts.json`, JSON.stringify(messageList, null, ' '));
}
catch (error) {
const data = {
message: error.stack,
color: 'red'
};
event.sender.send('serverLog', data);
}
});
function saveWindowState () {
mainWindowState.saveState(mainWindow);
}

View File

@ -0,0 +1,68 @@
const Sender = require('../classes/Sender');
Sends = new Sender(process);
let interval = null;
process.on('message', message => {
switch (message.event) {
case 'start':
Sends.setHosts(message.hosts);
Sends.setParams(message.params);
Sends.setStoragePath(message.storagePath);
Sends.startFullTest(() => {
let response = {
event: 'finish',
content: 'Test concluso'
};
process.send(response);
if (interval !== null) clearInterval(interval);
Sends.getReports();
});
Sends.getReports();
if (interval === null) {
interval = setInterval(() => {
Sends.getReports();
}, 200);
}
break;
case 'startStep':
Sends.setHosts(message.hosts);
Sends.setParams(message.params);
Sends.setStoragePath(message.storagePath);
Sends.connectClients(() => {
let response = {
event: 'log',
content: { message: 'Client connessi', color: '' }
};
process.send(response);
});
Sends.getReports();
if (interval === null) {
interval = setInterval(() => {
Sends.getReports();
}, 200);
}
break;
case 'sendStep':
Sends.sendMessages(() => {
let response = {
event: 'log',
content: { message: 'Messaggi inviati', color: '' }
};
process.send(response);
});
break;
case 'stop':
Sends.stopClients(() => {
if (interval !== null) clearInterval(interval);
Sends.getReports();
process.exit();
});
break;
}
});

View File

@ -0,0 +1,69 @@
import * as antares from 'common/interfaces/antares';
import * as fs from 'fs';
import { MySQLClient } from '../libs/clients/MySQLClient';
import { PostgreSQLClient } from '../libs/clients/PostgreSQLClient';
import { ClientsFactory } from '../libs/ClientsFactory';
import MysqlExporter from '../libs/exporters/sql/MysqlExporter';
import PostgreSQLExporter from '../libs/exporters/sql/PostgreSQLExporter';
let exporter: antares.Exporter;
process.on('message', async ({ type, client, tables, options }) => {
if (type === 'init') {
const connection = await ClientsFactory.getClient({
client: client.name,
params: client.config,
poolSize: 5
}) as MySQLClient | PostgreSQLClient;
await connection.connect();
switch (client.name) {
case 'mysql':
case 'maria':
exporter = new MysqlExporter(connection as MySQLClient, tables, options);
break;
case 'pg':
exporter = new PostgreSQLExporter(connection as PostgreSQLClient, tables, options);
break;
default:
process.send({
type: 'error',
payload: `"${client.name}" exporter not aviable`
});
return;
}
exporter.once('error', err => {
console.error(err);
process.send({
type: 'error',
payload: err.toString()
});
});
exporter.once('end', () => {
process.send({
type: 'end',
payload: { cancelled: exporter.isCancelled }
});
connection.destroy();
});
exporter.once('cancel', () => {
fs.unlinkSync(exporter.outputFile);
process.send({ type: 'cancel' });
});
exporter.on('progress', state => {
process.send({
type: 'export-progress',
payload: state
});
});
exporter.run();
}
else if (type === 'cancel')
exporter.cancel();
});
process.on('beforeExit', console.log);

View File

@ -0,0 +1,111 @@
import * as antares from 'common/interfaces/antares';
import * as pg from 'pg';
import * as mysql from 'mysql2';
import { MySQLClient } from '../libs/clients/MySQLClient';
import { PostgreSQLClient } from '../libs/clients/PostgreSQLClient';
import { ClientsFactory } from '../libs/ClientsFactory';
import MySQLImporter from '../libs/importers/sql/MySQLlImporter';
import PostgreSQLImporter from '../libs/importers/sql/PostgreSQLImporter';
import SSHConfig from 'ssh2-promise/lib/sshConfig';
import { ImportOptions } from 'common/interfaces/importer';
let importer: antares.Importer;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
process.on('message', async ({ type, dbConfig, options }: {
type: string;
dbConfig: mysql.ConnectionOptions & { schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean }
| pg.ClientConfig & { schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean }
| { databasePath: string; readonly: boolean };
options: ImportOptions;
}) => {
if (type === 'init') {
try {
const connection = await ClientsFactory.getClient({
client: options.type,
params: {
...dbConfig,
schema: options.schema
},
poolSize: 1
}) as MySQLClient | PostgreSQLClient;
const pool = await connection.getConnectionPool();
switch (options.type) {
case 'mysql':
case 'maria':
importer = new MySQLImporter(pool as unknown as mysql.Pool, options);
break;
case 'pg':
importer = new PostgreSQLImporter(pool as unknown as pg.PoolClient, options);
break;
default:
process.send({
type: 'error',
payload: `"${options.type}" importer not aviable`
});
return;
}
importer.once('error', err => {
console.error(err);
process.send({
type: 'error',
payload: err.toString()
});
});
importer.once('end', () => {
process.send({
type: 'end',
payload: { cancelled: importer.isCancelled }
});
});
importer.once('cancel', () => {
process.send({ type: 'cancel' });
});
importer.on('progress', state => {
process.send({
type: 'import-progress',
payload: state
});
});
importer.on('query-error', state => {
process.send({
type: 'query-error',
payload: state
});
});
importer.run();
}
catch (err) {
console.error(err);
process.send({
type: 'error',
payload: err.toString()
});
}
}
else if (type === 'cancel')
importer.cancel();
});
process.on('uncaughtException', (err) => {
console.error(err);
process.send({
type: 'error',
payload: err.toString()
});
});
process.on('unhandledRejection', (err) => {
console.error(err);
process.send({
type: 'error',
payload: err.toString()
});
});

View File

@ -0,0 +1,27 @@
const Server = require('../classes/Server');
myServer = new Server(process);
let interval = null;
process.on('message', message => {
switch (message.event) {
case 'start':
myServer.setPorts(message.ports);
myServer.startServer(message.params);
if (interval === null) {
interval = setInterval(() => {
myServer.getReports();
}, 200);
}
break;
case 'stop':
myServer.stopServer(() => {
if (interval !== null) clearInterval(interval);
process.exit();
});
break;
case 'reset':
myServer.resetReports();
break;
}
});

179
src/renderer/App.vue Normal file
View File

@ -0,0 +1,179 @@
<template>
<div id="wrapper" :class="[`theme-${applicationTheme}`, !disableBlur || 'no-blur']">
<TheTitleBar />
<div id="window-content">
<TheSettingBar @show-connections-modal="isAllConnectionsModal = true" />
<div id="main-content" class="container">
<div class="columns col-gapless">
<Workspace
v-for="connection in connections"
:key="connection.uid"
:connection="connection"
/>
<div class="connection-panel-wrapper p-relative">
<WorkspaceAddConnectionPanel v-if="selectedWorkspace === 'NEW'" />
</div>
</div>
<TheFooter />
<TheNotificationsBoard />
<TheScratchpad v-if="isScratchpad" />
<ModalSettings v-if="isSettingModal" />
<BaseTextEditor class="d-none" value="" />
</div>
</div>
<ModalAllConnections v-if="isAllConnectionsModal" @close="isAllConnectionsModal = false" />
</div>
</template>
<script setup lang="ts">
import { defineAsyncComponent, onMounted, Ref, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { ipcRenderer } from 'electron';
import { useI18n } from 'vue-i18n';
import { Menu, getCurrentWindow } from '@electron/remote';
import { useApplicationStore } from '@/stores/application';
import { useConnectionsStore } from '@/stores/connections';
import { useSettingsStore } from '@/stores/settings';
import { useWorkspacesStore } from '@/stores/workspaces';
import TheSettingBar from '@/components/TheSettingBar.vue';
const { t } = useI18n();
const TheTitleBar = defineAsyncComponent(() => import(/* webpackChunkName: "TheTitleBar" */'@/components/TheTitleBar.vue'));
const TheFooter = defineAsyncComponent(() => import(/* webpackChunkName: "TheFooter" */'@/components/TheFooter.vue'));
const TheNotificationsBoard = defineAsyncComponent(() => import(/* webpackChunkName: "TheNotificationsBoard" */'@/components/TheNotificationsBoard.vue'));
const Workspace = defineAsyncComponent(() => import(/* webpackChunkName: "Workspace" */'@/components/Workspace.vue'));
const WorkspaceAddConnectionPanel = defineAsyncComponent(() => import(/* webpackChunkName: "WorkspaceAddConnectionPanel" */'@/components/WorkspaceAddConnectionPanel.vue'));
const ModalSettings = defineAsyncComponent(() => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings.vue'));
const ModalAllConnections = defineAsyncComponent(() => import(/* webpackChunkName: "ModalAllConnections" */'@/components/ModalAllConnections.vue'));
const TheScratchpad = defineAsyncComponent(() => import(/* webpackChunkName: "TheScratchpad" */'@/components/TheScratchpad.vue'));
const BaseTextEditor = defineAsyncComponent(() => import(/* webpackChunkName: "BaseTextEditor" */'@/components/BaseTextEditor.vue'));
const applicationStore = useApplicationStore();
const connectionsStore = useConnectionsStore();
const settingsStore = useSettingsStore();
const workspacesStore = useWorkspacesStore();
const {
isSettingModal,
isScratchpad
} = storeToRefs(applicationStore);
const { connections } = storeToRefs(connectionsStore);
const { applicationTheme, disableBlur } = storeToRefs(settingsStore);
const { getSelected: selectedWorkspace } = storeToRefs(workspacesStore);
const { checkVersionUpdate } = applicationStore;
const { changeApplicationTheme } = settingsStore;
const isAllConnectionsModal: Ref<boolean> = ref(false);
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
changeApplicationTheme(applicationTheme.value);// Forces persistentStore to save on file and mail process
}, 1000);
});
onMounted(() => {
ipcRenderer.on('open-all-connections', () => {
isAllConnectionsModal.value = true;
});
ipcRenderer.on('open-scratchpad', () => {
isScratchpad.value = true;
});
ipcRenderer.on('open-settings', () => {
isSettingModal.value = true;
});
ipcRenderer.on('create-connection', () => {
workspacesStore.selectWorkspace('NEW');
});
ipcRenderer.send('check-for-updates');
checkVersionUpdate();
const InputMenu = Menu.buildFromTemplate([
{
label: t('word.cut'),
role: 'cut'
},
{
label: t('word.copy'),
role: 'copy'
},
{
label: t('word.paste'),
role: 'paste'
},
{
type: 'separator'
},
{
label: t('message.selectAll'),
role: 'selectAll'
}
]);
document.body.addEventListener('contextmenu', (e) => {
e.preventDefault();
e.stopPropagation();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let node: any = e.target;
while (node) {
if (node.nodeName.match(/^(input|textarea)$/i) || node.isContentEditable) {
InputMenu.popup({ window: getCurrentWindow() });
break;
}
node = node.parentNode;
}
});
document.addEventListener('keydown', e => {
if (e.altKey && e.key === 'Alt') { // Prevent Alt key to trigger hidden shortcut menu
e.preventDefault();
}
});
});
</script>
<style lang="scss">
html,
body {
height: 100%;
}
#wrapper {
height: 100vh;
position: relative;
}
#window-content {
display: flex;
position: relative;
overflow: hidden;
}
#main-content {
padding: 0;
justify-content: flex-start;
height: calc(100vh - #{$excluding-size});
width: calc(100% - #{$settingbar-width});
> .columns {
height: calc(100vh - #{$footer-height});
}
.connection-panel-wrapper {
height: calc(100vh - #{$excluding-size});
width: 100%;
padding-top: 10vh;
display: flex;
justify-content: center;
align-items: flex-start;
overflow: auto;
}
}
</style>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View File

@ -0,0 +1,53 @@
<template>
<header id="header">
<!-- <div id="appTitle">
<h2>Mizar TCP Tester</h2>
</div> -->
<nav id="appTabs">
<div
class="navTab"
:class="{ selected : selTab === 0 }"
@click="selectTab(0)"
>
Client
<transition name="fade">
<i
v-if="clientStatus === 1"
class="material-icons running"
title="In esecuzione"
>play_arrow</i>
</transition>
</div>
<div
class="navTab"
:class="{ selected : selTab === 1 }"
@click="selectTab(1)"
>
Server
<transition name="fade">
<i
v-if="serverStatus === 1"
class="material-icons running"
title="In esecuzione"
>play_arrow</i>
</transition>
</div>
</nav>
</header>
</template>
<script>
export default {
name: 'AppHeader',
props: {
selTab: Number,
clientStatus: Number,
serverStatus: Number
},
methods: {
selectTab (value) {
this.$emit('selectTab', value);
}
}
};
</script>

View File

@ -0,0 +1,54 @@
<template>
<div id="serverReports" class="box-100">
<h3>Report del Test</h3>
<table>
<thead>
<tr>
<th>Host</th>
<th>Client</th>
<th>Messaggi</th>
<th>Dati</th>
</tr>
</thead>
<tbody>
<tr v-for="(report, index) in reports" :key="index">
<td>{{ report.host }}</td>
<td>{{ report.sockets }}</td>
<td><span title="Inviati">{{ report.messages.toLocaleString() }}</span> <i class="material-icons">import_export</i><span title="Ricevuti">{{ report.received }}</span></td>
<td><span title="Inviati">{{ report.data.toLocaleString() }} B</span></td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Totali</td>
<td>{{ totSockets }}</td>
<td><span title="Inviati">{{ totMessages.toLocaleString() }}</span> <i class="material-icons">import_export</i><span title="Ricevuti">{{ totReceived.toLocaleString() }}</span></td>
<td><span title="Inviati">{{ totData.toLocaleString() }} B</span></td>
</tr>
</tfoot>
</table>
</div>
</template>
<script>
export default {
name: 'ClientReports',
props: {
reports: Array
},
computed: {
totSockets () {
return this.$props.reports.reduce((prev, cur) => prev + cur.sockets, 0);
},
totMessages () {
return this.$props.reports.reduce((prev, cur) => prev + cur.messages, 0);
},
totReceived () {
return this.$props.reports.reduce((prev, cur) => prev + cur.received, 0);
},
totData () {
return this.$props.reports.reduce((prev, cur) => prev + cur.data, 0);
}
}
};
</script>

View File

@ -0,0 +1,460 @@
<template>
<div class="flex box-100">
<div id="client" class="box-50">
<transition name="fade">
<NewHost
v-if="popNewHost"
@hideAddHost="hideAddHost"
@createHost="createHost"
/>
</transition>
<transition name="fade">
<NewMessage
v-if="popNewMessage"
@hideAddMessage="hideAddMessage"
@createMessage="createMessage"
/>
</transition>
<transition name="fade">
<EditMessage
v-if="popEditMessage"
:message="messageList[idEditedMsg]"
:index="idEditedMsg"
@hideEditMessage="hideEditMessage"
@editMessage="editMessage"
/>
</transition>
<transition name="fade">
<SaveConfig
v-if="popSaveConfig"
:params="params"
@hideSaveConfig="hideSaveConfig"
@saveConfig="saveConfig"
/>
</transition>
<transition name="fade">
<LoadConfig
v-if="popLoadConfig"
@hideLoadConfig="hideLoadConfig"
@loadConfig="loadConfig"
/>
</transition>
<form autocomplete="off" @submit.prevent="startTest">
<fieldset :disabled="running !== 0">
<Hosts
ref="hosts"
:host-list="hostList"
@updateHosts="updateHosts"
@showAddHost="showAddHost"
@deleteHost="deleteHost"
@toggleHostCheck="toggleHostCheck"
/>
<Messages
ref="messages"
:message-list="messageList"
@updateMessages="updateMessages"
@showAddMessage="showAddMessage"
@showEditMessage="showEditMessage"
@deleteMessage="deleteMessage"
@toggleMessageCheck="toggleMessageCheck"
/>
<div class="flex box-100">
<div class="input-element">
<label>Numero di Messaggi</label>
<input
v-model.number="params.nMsgs"
min="1"
step="1"
type="number"
required
>
</div>
<div class="input-element">
<label>Numero di Client</label>
<input
v-model.number="params.nClients"
min="1"
step="1"
type="number"
required
>
</div>
</div>
<div class="flex box-100">
<div class="input-element">
<label>Intervallo Minimo (ms)</label>
<input
v-model.number="params.tMin"
min="0"
step="1"
type="number"
required
>
</div>
<div class="input-element">
<label>Intervallo Massimo (ms)</label>
<input
v-model.number="params.tMax"
min="0"
step="1"
type="number"
required
>
</div>
</div>
<div class="flex box-100">
<div class="box-50">
<label class="checkbox">
<input
v-model="params.closeOnEcho"
type="checkbox"
>
<div class="checkbox-block" />
<span>Chiudi alla Risposta</span>
</label>
<label class="checkbox" title="Connessione Persistente">
<input
v-model="params.persistentConnection"
type="checkbox"
>
<div class="checkbox-block" />
<span>Conn. Persistente</span>
</label>
<label class="checkbox">
<input
v-model="params.stepTest"
type="checkbox"
>
<div class="checkbox-block" />
<span>Test a Step</span>
</label>
</div>
<div class="box-50">
<label class="checkbox">
<input
v-model="params.trace"
type="checkbox"
>
<div class="checkbox-block" />
<span>Abilita Trace</span>
</label>
<label class="checkbox">
<input
v-model="params.alertReset"
type="checkbox"
>
<div class="checkbox-block" />
<span>Allerta ECONNRESET</span>
</label>
<label class="checkbox" title="Ripete il test dopo il suo termine">
<input
v-model="params.loop"
type="checkbox"
>
<div class="checkbox-block" />
<span>Ripetizione Automatica</span>
</label>
</div>
</div>
</fieldset>
<div class="buttons">
<div class="button-wrap">
<i class="material-icons">get_app</i>
<button
class="save"
title="Carica configurazione"
:disabled="running !== 0"
@click.prevent="showLoadConfig"
>
Carica
</button>
</div>
<div class="button-wrap">
<i class="material-icons">save</i>
<button
class="save"
title="Salva configurazione corrente"
@click.prevent="showSaveConfig"
>
Salva
</button>
</div>
<div v-if="running === 0" class="button-wrap">
<i class="material-icons white">play_arrow</i>
<button class="confirm" type="submit">
Start
</button>
</div>
<div v-if="running !== 0 && params.stepTest" class="button-wrap">
<i class="material-icons white">message</i>
<button
class="confirm"
title="Invia Messaggi"
@click.prevent="sendMessages"
>
Invia
</button>
</div>
<div v-if="running !== 0" class="button-wrap">
<i class="material-icons white">stop</i>
<button class="stop" @click.prevent="stopTest">
Stop
</button>
</div>
</div>
</form>
<transition name="fade">
<client-reports
v-if="reportList.length > 0"
ref="reports"
:reports="reportList"
/>
</transition>
</div><!-- /client -->
<Console
ref="console"
:logs="slicedLogs"
/>
</div>
</template>
<script>
import Console from './console.vue';
import Hosts from './hosts.vue';
import Messages from './messages.vue';
import NewHost from './new-host.vue';
import NewMessage from './new-message.vue';
import EditMessage from './edit-message.vue';
import SaveConfig from './save-config.vue';
import LoadConfig from './load-config.vue';
import ClientReports from './client-reports.vue';
import { ipcRenderer } from 'electron';
export default {
name: 'Client',
components: {
Console,
Hosts,
Messages,
NewHost,
NewMessage,
EditMessage,
SaveConfig,
LoadConfig,
ClientReports
},
data () {
return {
running: 0,
params: {
nMsgs: 1,
nClients: 1,
tMin: 0,
tMax: 0,
trace: false,
alertReset: false,
closeOnEcho: false,
persistentConnection: false,
stepTest: false,
loop: false
},
logs: [],
hostList: [],
messageList: [],
reportList: [],
popNewHost: false,
popNewMessage: false,
popEditMessage: false,
popSaveConfig: false,
popLoadConfig: false,
idEditedMsg: null
};
},
computed: {
slicedLogs () {
if (this.logs.length > 500)
this.logs = this.logs.slice(-500);
return this.logs;
}
},
created () {
ipcRenderer.on('clientLog', (event, data) => {
let time = new Date().toLocaleString();
let { message, color } = data;
let log = {
time: time,
message,
color
};
this.logs.push(log);
});
ipcRenderer.on('testFinish', (event, message) => {
this.running = 0;
this.$emit('clientStatus', this.running);
let time = new Date().toLocaleString();
let log = {
time: time,
message,
color: ''
};
this.logs.push(log);
});
ipcRenderer.send('getHosts');
ipcRenderer.on('hostList', (event, hosts) => {
this.hostList = hosts;
});
ipcRenderer.send('getMessages');
ipcRenderer.on('messageList', (event, messages) => {
this.messageList = messages;
});
ipcRenderer.on('reportClientList', (event, reports) => {
this.reportList = reports;
});
},
methods: {
startTest () {
if (this.params.tMin < 100 && this.params.trace === true) {
this.params.trace = false;
let time = new Date().toLocaleString();
let log = {
time: time,
message: 'Trace disabilitato: Intervalli troppo brevi',
color: 'yellow'
};
this.logs.push(log);
}
this.running = 1;
this.$emit('clientStatus', this.running);
if (this.params.stepTest) {
this.params.closeOnEcho = false;
this.params.persistentConnection = false;
this.params.loop = false;
}
let obj = {
params: this.params,
hosts: this.hostList.filter((host) => {
return host.enabled === true;
})
};
ipcRenderer.send('startTest', obj);
},
sendMessages () {
ipcRenderer.send('sendMessages');
},
stopTest () {
ipcRenderer.send('stopTest');
},
// Host
createHost (host) {
this.hostList.push(host);
this.popNewHost = false;
ipcRenderer.send('updateHosts', this.hostList);
},
showAddHost () {
this.popNewHost = true;
},
hideAddHost () {
this.popNewHost = false;
},
updateHosts () {
ipcRenderer.send('updateHosts', this.hostList);
},
deleteHost (hostId) {
this.hostList.splice(hostId, 1);
ipcRenderer.send('updateHosts', this.hostList);
},
toggleHostCheck (status) {
if (this.running !== 0) return;
let enable = status === 0;
this.hostList.forEach((host) => {
host.enabled = enable;
});
ipcRenderer.send('updateHosts', this.hostList);
},
// Messaggi
createMessage (message) {
this.messageList.push(message);
this.popNewMessage = false;
ipcRenderer.send('updateMessages', this.messageList);
},
editMessage (message, index) {
this.popEditMessage = false;
this.$set(this.messageList, index, message);
ipcRenderer.send('updateMessages', this.messageList);
},
updateMessages () {
ipcRenderer.send('updateMessages', this.messageList);
},
showAddMessage () {
this.popNewMessage = true;
},
hideAddMessage () {
this.popNewMessage = false;
},
showEditMessage (index) {
this.idEditedMsg = index;
this.popEditMessage = true;
},
hideEditMessage () {
this.popEditMessage = false;
},
deleteMessage (messageId) {
this.messageList.splice(messageId, 1);
ipcRenderer.send('updateMessages', this.messageList);
},
toggleMessageCheck (status) {
if (this.running !== 0) return;
let enable = status === 0;
this.messageList.forEach((message) => {
message.enabled = enable;
});
ipcRenderer.send('updateMessages', this.messageList);
},
// Convigurazioni
showSaveConfig () {
this.popSaveConfig = true;
},
hideSaveConfig () {
this.popSaveConfig = false;
},
saveConfig (config) {
this.popSaveConfig = false;
ipcRenderer.send('saveClientConfig', config);
},
showLoadConfig () {
this.popLoadConfig = true;
},
hideLoadConfig () {
this.popLoadConfig = false;
},
loadConfig (config) {
this.popLoadConfig = false;
this.params = config.params;
let time = new Date().toLocaleString();
let log = {
time: time,
message: `Configurazione "${config.name}" ripristinata`,
color: 'green'
};
this.logs.push(log);
}
}
};
</script>

View File

@ -0,0 +1,25 @@
<template>
<div class="console box-50">
<div
v-for="(log, index) in logs"
:key="index"
class="log"
:class="log.color"
>
{{ log.time }} - <span v-html="log.message" />
</div>
</div>
</template>
<script>
export default {
name: 'Console',
props: {
logs: Array
},
updated () {
var elem = this.$el;
elem.scrollTop = elem.scrollHeight;
}
};
</script>

View File

@ -0,0 +1,79 @@
<template>
<div id="popcontainer">
<div class="popup">
<div class="box-100">
<h4>Modifica Messaggio</h4>
<div class="input-element">
<label>Nome</label>
<input
v-model="staticMsg.name"
type="text"
required
autofocus
>
</div>
<div class="input-element">
<label>Messaggio</label>
<textarea
v-model="staticMsg.message"
required
>Corpo del messaggio</textarea>
</div>
</div>
<div class="input-element">
<label>Formato</label>
<select v-model="staticMsg.format" required>
<option value="" disabled>
Seleziona
</option>
<option value="ascii">
ASCII
</option>
<option value="hex">
HEX
</option>
</select>
</div>
<div class="buttons">
<button class="cancel" @click="close">
Annulla
</button>
<button
class="confirm"
:disabled="validation"
@click="confirm"
>
Modifica
</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'EditMessage',
props: {
message: Object,
index: Number
},
data () {
return {
staticMsg: Object.assign({}, this.$props.message)
};
},
computed: {
validation () {
return this.staticMsg.message === '' || this.staticMsg.name === '' || this.staticMsg.format === '';
}
},
methods: {
close () {
this.$emit('hideEditMessage');
},
confirm () {
this.$emit('editMessage', this.staticMsg, this.index);
}
}
};
</script>

View File

@ -0,0 +1,82 @@
<template>
<div id="hostBox" class="box-100">
<h3><span class="toggle-select"><i class="material-icons" @click="toggleCheck(checkStatus)">{{ checkIcon(checkStatus) }}</i></span><span>Hosts</span></h3>
<div class="tools-box">
<div class="round-button green-bg" @click="showAdd">
<span>Aggiungi Host</span>
<i class="material-icons">add</i>
</div>
</div>
<ul id="hostList">
<li v-for="(host, index) in sortedHosts" :key="index">
<label class="checkbox">
<input
v-model="host.enabled"
type="checkbox"
@change="updateHosts"
>
<div class="checkbox-block" />
<span>{{ host.host }}:{{ host.port }}</span>
</label>
<i
class="material-icons deleteHost"
:title="`Elimina host ${host.host}:${host.port}`"
@click="deleteHost(index)"
>clear</i>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'Hosts',
props: {
hostList: Array
},
computed: {
checkStatus () {
let checked = this.hostList.filter((host) => {
return host.enabled;
});
if (this.hostList.length === checked.length)
return 2;
else if (checked.length > 0)
return 1;
else
return 0;
},
sortedHosts () {
return this.$props.hostList.sort((a, b) => (a.host < b.host ? -1 : (a.host > b.host ? 1 : 0)) || a.port - b.port);
}
},
updated () {
var elem = this.$el;
elem.scrollTop = elem.scrollHeight;
},
methods: {
updateHosts () {
this.$emit('updateHosts');
},
showAdd () {
this.$emit('showAddHost');
},
deleteHost (value) {
this.$emit('deleteHost', value);
},
checkIcon (status) {
switch (status) {
case 0:
return 'check_box_outline_blank';
case 1:
return 'indeterminate_check_box';
case 2:
return 'check_box';
}
},
toggleCheck (status) {
this.$emit('toggleHostCheck', status);
}
}
};
</script>

View File

@ -0,0 +1,78 @@
<template>
<div id="popcontainer">
<div class="popup">
<div class="box-100">
<h4>Configurazioni Salvate</h4>
<ul id="configList">
<li
v-for="(config, index) in configList"
:key="index"
>
<span
@mouseover="selected = index"
@mouseleave="selected = null"
>
<i
v-if="selected !== index"
class="material-icons radio-btn"
>radio_button_unchecked</i>
<i
v-if="selected === index"
title="Seleziona"
class="material-icons radio-btn"
@click="loadConfig(index)"
>radio_button_checked</i>
</span>
<span>{{ config.name }} ({{ config.time }})</span>
<i
class="material-icons deleteConfig"
:title="`Elimina configurazione ${config.name}`"
@click="deleteConfig(index)"
>clear</i>
</li>
</ul>
</div>
<div class="buttons">
<button class="cancel" @click="close">
Chiudi
</button>
</div>
</div>
</div>
</template>
<script>
import { ipcRenderer } from 'electron';
export default {
name: 'LoadConfig',
data () {
return {
configList: [],
selected: null
};
},
created () {
ipcRenderer.send('getClientConfigs');
ipcRenderer.on('configList', (event, configs) => {
this.configList = configs;
});
},
methods: {
close () {
this.$emit('hideLoadConfig');
this.selected = null;
},
deleteConfig: function (index) {
this.configList.splice(index, 1);
ipcRenderer.send('updateClientConfig', this.configList);
},
loadConfig (index) {
this.selected = index;
this.$emit('loadConfig', this.configList[index]);
this.close();
}
}
};
</script>

View File

@ -0,0 +1,65 @@
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity .2s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>
<template>
<div id="wrapper">
<AppHeader
ref="header"
:sel-tab="selTab"
:client-status="clientStatus"
:server-status="serverStatus"
@selectTab="selectTab"
/>
<div id="main">
<Client
v-show="selTab === 0"
ref="client"
@clientStatus="clientUpdateStatus"
/>
<Server
v-show="selTab === 1"
ref="server"
@serverStatus="serverUpdateStatus"
/>
</div>
</div>
</template>
<script>
import AppHeader from './app-header.vue';
import Client from './client.vue';
import Server from './server.vue';
export default {
name: 'Main',
components: {
AppHeader,
Client,
Server
},
data () {
return {
selTab: 0,
clientStatus: 0,
serverStatus: 0
};
},
methods: {
selectTab (value) {
this.selTab = value;
},
clientUpdateStatus (value) {
this.clientStatus = value;
},
serverUpdateStatus (value) {
this.serverStatus = value;
}
}
};
</script>

View File

@ -0,0 +1,97 @@
<template>
<div id="messageBox" class="box-100">
<h3><span class="toggle-select"><i class="material-icons" @click="toggleCheck(checkStatus)">{{ checkIcon(checkStatus) }}</i></span><span>Messaggi</span></h3>
<div class="tools-box">
<div class="round-button green-bg" @click="showAdd">
<span>Aggiungi Messaggio</span>
<i class="material-icons">add</i>
</div>
</div>
<ul id="messageList">
<li v-for="(message, index) in sortedMessages" :key="index">
<label class="checkbox">
<input
v-model="message.enabled"
type="checkbox"
@change="updateMessages"
>
<div class="checkbox-block" />
<span class="format">({{ message.format }})</span>
<span>{{ message.name | truncate(25, '...') }}</span>
</label>
<i
class="material-icons editMessage"
:title="`Modifica messaggio ${message.name}`"
@click="showEdit(index)"
>edit</i>
<i
class="material-icons deleteMessage"
:title="`Elimina messaggio ${message.name}`"
@click="deleteMessage(index)"
>clear</i>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'Messages',
filters: {
truncate (text, length, suffix) {
if (text.length <= length) suffix = '';
return text.substring(0, length) + suffix;
}
},
props: {
messageList: Array
},
computed: {
checkStatus () {
let checked = this.messageList.filter((message) => {
return message.enabled;
});
if (this.messageList.length === checked.length)
return 2;
else if (checked.length > 0)
return 1;
else
return 0;
},
sortedMessages () {
return this.$props.messageList.sort((a, b) => (a.name < b.name ? -1 : (a.name > b.name ? 1 : 0)));
}
},
updated () {
var elem = this.$el;
elem.scrollTop = elem.scrollHeight;
},
methods: {
updateMessages () {
this.$emit('updateMessages');
},
showAdd () {
this.$emit('showAddMessage');
},
showEdit (message, index) {
this.$emit('showEditMessage', message, index);
},
deleteMessage (value) {
this.$emit('deleteMessage', value);
},
checkIcon (status) {
switch (status) {
case 0:
return 'check_box_outline_blank';
case 1:
return 'indeterminate_check_box';
case 2:
return 'check_box';
}
},
toggleCheck (status) {
this.$emit('toggleMessageCheck', status);
}
}
};
</script>

View File

@ -0,0 +1,73 @@
<template>
<div id="popcontainer">
<div class="popup">
<div class="box-100">
<h4>Nuovo Host</h4>
<div class="input-element">
<label>Indirizzo Host</label>
<input
v-model="host.host"
type="text"
required
autofocus
>
</div>
<div class="input-element">
<label>Porta</label>
<input
v-model.number="host.port"
min="1"
step="1"
type="number"
required
>
</div>
</div>
<div class="buttons">
<button class="cancel" @click="close">
Annulla
</button>
<button
class="confirm"
:disabled="validation"
@click="confirm"
>
Crea
</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'NewHost',
data () {
return {
host: {
host: '',
port: '',
enabled: true
}
};
},
computed: {
validation () {
return this.host.host === '' || this.host.port === '';
}
},
methods: {
close () {
this.$emit('hideAddHost');
},
confirm () {
this.$emit('createHost', this.host);
this.host = {
host: '',
port: '',
enabled: true
};
}
}
};
</script>

View File

@ -0,0 +1,86 @@
<template>
<div id="popcontainer">
<div class="popup">
<div class="box-100">
<h4>Nuovo Messaggio</h4>
<div class="input-element">
<label>Nome</label>
<input
v-model="message.name"
type="text"
required
autofocus
>
</div>
<div class="input-element">
<label>Messaggio</label>
<textarea
v-model="message.message"
required
>Corpo del messaggio</textarea>
</div>
</div>
<div class="input-element">
<label>Formato</label>
<select v-model="message.format" required>
<option value="" disabled>
Seleziona
</option>
<option value="ascii">
ASCII
</option>
<option value="hex">
HEX
</option>
</select>
</div>
<div class="buttons">
<button class="cancel" @click="close">
Annulla
</button>
<button
class="confirm"
:disabled="validation"
@click="confirm"
>
Crea
</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'NewMessage',
data () {
return {
message: {
message: '',
name: '',
enabled: true,
format: ''
}
};
},
computed: {
validation () {
return this.message.message === '' || this.message.name === '' || this.message.format === '';
}
},
methods: {
close () {
this.$emit('hideAddMessage');
},
confirm () {
this.$emit('createMessage', this.message);
this.message = {
message: '',
name: '',
format: '',
enabled: true
};
}
}
};
</script>

View File

@ -0,0 +1,76 @@
<template>
<div id="popcontainer">
<div class="popup">
<div class="box-100">
<h4>Nuova porta</h4>
<div class="input-element">
<label>Porta</label>
<input
v-model.number="port.port"
min="1"
max="65535"
step="1"
type="number"
required
autofocus
>
<span class="input-msg">{{ errMsg }}</span>
</div>
</div>
<div class="buttons">
<button class="cancel" @click="close">
Annulla
</button>
<button
class="confirm"
:disabled="validation"
@click="confirm"
>
Crea
</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'NewPort',
data () {
return {
port: {
port: '',
enabled: true
},
errMsg: ''
};
},
computed: {
validation () {
return this.port.port === '' || this.port.port > 65535 || this.port.port < 1;
}
},
methods: {
close () {
this.$emit('hideAddPort');
this.port = {
port: '',
enabled: true
};
this.errMsg = '';
},
confirm () {
let portList = this.$parent.portList;
if (portList.findIndex((port) => port.port === this.port.port) < 0) {
this.$emit('createPort', this.port);
this.port = {
port: '',
enabled: true
};
this.errMsg = '';
}
else this.errMsg = 'Porta già esistente!';
}
}
};
</script>

View File

@ -0,0 +1,82 @@
<template>
<div id="portBox" class="box-100">
<h3><span class="toggle-select"><i class="material-icons" @click="toggleCheck(checkStatus)">{{ checkIcon(checkStatus) }}</i></span><span>Porte</span></h3>
<div class="tools-box">
<div class="round-button green-bg" @click="showAdd">
<span>Aggiungi Porta</span>
<i class="material-icons">add</i>
</div>
</div>
<ul id="portList">
<li v-for="(port, index) in sortedPorts" :key="index">
<label class="checkbox">
<input
v-model="port.enabled"
type="checkbox"
@change="updatePorts"
>
<div class="checkbox-block" />
<span>{{ port.port }}</span>
</label>
<i
class="material-icons deletePort"
:title="`Elimina porta ${port.port}`"
@click="deletePort(index)"
>clear</i>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'Ports',
props: {
portList: Array
},
computed: {
checkStatus () {
let checked = this.portList.filter((port) => {
return port.enabled;
});
if (this.portList.length === checked.length)
return 2;
else if (checked.length > 0)
return 1;
else
return 0;
},
sortedPorts () {
return this.$props.portList.sort((a, b) => a.port - b.port);
}
},
updated () {
var elem = this.$el;
elem.scrollTop = elem.scrollHeight;
},
methods: {
updatePorts () {
this.$emit('updatePorts');
},
showAdd () {
this.$emit('showAddPort');
},
deletePort (value) {
this.$emit('deletePort', value);
},
checkIcon (status) {
switch (status) {
case 0:
return 'check_box_outline_blank';
case 1:
return 'indeterminate_check_box';
case 2:
return 'check_box';
}
},
toggleCheck (status) {
this.$emit('togglePortCheck', status);
}
}
};
</script>

View File

@ -0,0 +1,62 @@
<template>
<div id="popcontainer">
<div class="popup">
<div class="box-100">
<h4>Nuova Configurazione</h4>
<div class="input-element">
<label>Nome</label>
<input
v-model="name"
type="text"
placeholder="Nome configurazione"
required
>
</div>
</div>
<div class="buttons">
<button class="cancel" @click="close">
Annulla
</button>
<button
class="confirm"
:disabled="validation"
@click="confirm"
>
Salva
</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'SaveCongif',
props: {
params: Object
},
data () {
return {
name: ''
};
},
computed: {
validation () {
return this.name === '';
}
},
methods: {
close () {
this.$emit('hideSaveConfig');
},
confirm () {
let config = {
name: this.name,
time: new Date().toLocaleString(),
params: this.$props.params
};
this.$emit('saveConfig', config);
}
}
};
</script>

View File

@ -0,0 +1,68 @@
<template>
<div id="serverReports" class="box-100">
<h3>Stato del Server</h3>
<table>
<thead>
<tr>
<th>Porta</th>
<th>Socket</th>
<th>Messaggi</th>
<th>Dati</th>
</tr>
</thead>
<tbody>
<tr v-for="(report, index) in reports" :key="index">
<td>{{ report.port }}</td>
<td>{{ report.sockets }}</td>
<td>{{ report.messages.toLocaleString() }}</td>
<td>{{ report.data.toLocaleString() }} B</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Totali</td>
<td>{{ totSockets }}</td>
<td>{{ totMessages.toLocaleString() }}</td>
<td>{{ totData.toLocaleString() }} B</td>
</tr>
</tfoot>
</table>
<div class="buttons">
<div class="button-wrap">
<i class="material-icons">replay</i>
<button
class="save"
title="Azzera i dati ricevuti"
@click="reset"
>
Reset
</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ServerReports',
props: {
reports: Array
},
computed: {
totSockets () {
return this.$props.reports.reduce((prev, cur) => prev + cur.sockets, 0);
},
totMessages () {
return this.$props.reports.reduce((prev, cur) => prev + cur.messages, 0);
},
totData () {
return this.$props.reports.reduce((prev, cur) => prev + cur.data, 0);
}
},
methods: {
reset () {
this.$emit('resetReports');
}
}
};
</script>

View File

@ -0,0 +1,208 @@
<template>
<div class="flex box-100">
<div id="server" class="box-50">
<transition name="fade">
<NewPort
v-show="popNewPort"
@hideAddPort="hideAddPort"
@createPort="createPort"
/>
</transition>
<form autocomplete="off" @submit.prevent="startServer">
<fieldset :disabled="running !== 0">
<Ports
ref="ports"
:port-list="portList"
@updatePorts="updatePorts"
@showAddPort="showAddPort"
@deletePort="deletePort"
@togglePortCheck="togglePortCheck"
/>
<div class="flex box-100">
<div class="box-50">
<label class="checkbox">
<input
v-model="params.echo"
type="checkbox"
>
<div class="checkbox-block" />
<span>Echo Server</span>
</label>
<label class="checkbox">
<input
v-model="params.trace"
type="checkbox"
>
<div class="checkbox-block" />
<span>Abilita Trace</span>
</label>
</div>
<div class="box-50">
<label class="checkbox">
<input
v-model="params.alertReset"
type="checkbox"
>
<div class="checkbox-block" />
<span>Allerta ECONNRESET</span>
</label>
</div>
</div>
</fieldset>
<div class="buttons">
<div v-if="running === 0" class="button-wrap">
<i class="material-icons white">play_arrow</i>
<button class="confirm" type="submit">
Start
</button>
</div>
<div v-if="running === 1" class="button-wrap">
<i class="material-icons white">stop</i>
<button class="stop" @click="stopServer">
Stop
</button>
</div>
</div>
</form>
<transition name="fade">
<server-reports
v-if="reportList.length > 0"
ref="reports"
:reports="reportList"
@resetReports="resetReports"
/>
</transition>
</div><!-- /server -->
<Console
ref="console"
:logs="slicedLogs"
/>
</div>
</template>
<script>
import Console from './console.vue';
import Ports from './ports.vue';
import NewPort from './new-port.vue';
import ServerReports from './server-reports.vue';
import { ipcRenderer } from 'electron';
export default {
name: 'Server',
components: {
Console,
Ports,
NewPort,
ServerReports
},
data () {
return {
running: 0,
params: {
trace: false,
echo: true,
alertReset: false
},
logs: [],
portList: [],
reportList: [],
popNewPort: false
};
},
computed: {
slicedLogs () {
if (this.logs.length > 500)
this.logs = this.logs.slice(-500);
return this.logs;
}
},
created () {
ipcRenderer.on('serverLog', (event, data) => {
let time = new Date().toLocaleString();
let { message, color } = data;
let log = {
time: time,
message,
color
};
this.logs.push(log);
});
ipcRenderer.on('serverFinish', (event, message) => {
this.running = 0;
this.reportList = [];
this.$emit('serverStatus', this.running);
let time = new Date().toLocaleString();
let log = {
time: time,
message,
color: ''
};
this.logs.push(log);
});
ipcRenderer.send('getPorts');
ipcRenderer.on('portList', (event, ports) => {
this.portList = ports;
});
ipcRenderer.on('reportServerList', (event, reports) => {
this.reportList = reports;
});
},
methods: {
saveTest (e) {
e.preventDefault();
},
startServer (e) {
e.preventDefault();
this.running = 1;
this.$emit('serverStatus', this.running);
let obj = {
params: this.params,
ports: this.portList.filter((port) => {
return port.enabled === true;
})
};
ipcRenderer.send('startServer', obj);
},
stopServer (e) {
e.preventDefault();
ipcRenderer.send('stopServer');
},
updatePorts () {
ipcRenderer.send('updatePorts', this.portList);
},
showAddPort () {
this.popNewPort = true;
},
hideAddPort () {
this.popNewPort = false;
},
createPort (port) {
this.portList.push(port);
this.popNewPort = false;
ipcRenderer.send('updatePorts', this.portList);
},
deletePort (portId) {
this.portList.splice(portId, 1);
ipcRenderer.send('updatePorts', this.portList);
},
resetReports () {
ipcRenderer.send('resetReports');
},
togglePortCheck (status) {
if (this.running !== 0) return;
let enable = status === 0;
this.portList.forEach((host) => {
host.enabled = enable;
});
ipcRenderer.send('updatePorts', this.portList);
}
}
};
</script>

849
src/renderer/css/main.css Normal file
View File

@ -0,0 +1,849 @@
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(../assets/fonts/MaterialIcons-Regular.eot); /* For IE6-8 */
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url(../assets/fonts/MaterialIcons-Regular.woff2) format('woff2'),
url(../assets/fonts/MaterialIcons-Regular.woff) format('woff'),
url(../assets/fonts/MaterialIcons-Regular.ttf) format('truetype');
}
@font-face {
font-family: 'Roboto Regular';
font-style: normal;
font-weight: 400;
src: url(../assets/fonts/Roboto-Regular.ttf) format('truetype');
}
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: url(../assets/fonts/RobotoMono-Regular.ttf) format('truetype');
}
.material-icons {
font-family: 'Material Icons';
font-style: normal;
font-weight: normal;
font-size: 14px;
display: inline-flex;
vertical-align: middle;
text-decoration: inherit;
margin-right: 2px;
text-align: center;
line-height: 1;
text-transform: initial;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
}
/*Generale*/
*{
box-sizing:border-box;
}
html{
font-family: 'Roboto Regular', Helvetica, sans-serif;
font-size:100%;
color:#222;
height: 100%;
}
body{
height: 100%;
background: #EAEAEA;
color: #222;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none;
}
a, a:link, a:visited{
color:#006799;
transition:color 0.2s;
}
a:hover{
color:#000;
}
p{
font-size:1.0em;
margin-bottom: 1em;
}
li{
list-style:none;
}
img{
max-width:100%;
}
input,
textarea,
select{
font-size: 14px;
font-family: "Trebuchet MS", Helvetica, sans-serif;
border: 1px solid #B7B7B7;
border-radius: 5px;
box-sizing: border-box;
margin: 1px;
padding: 10px;
display: block;
background: transparent;
color: #777;
max-width: 100%;
width: 100%;
outline: none;
letter-spacing: 0px;
}
select{
padding: 6px 3px;
}
input[type="text"],
input[type="email"],
input[type="password"],
select{
width: 100%;
}
input[type="text"]:disabled,
input[type="number"]:disabled,
input[type="submit"]:disabled{
opacity: 0.5;
}
input:focus, select:focus, textarea:focus {
border-color: #0153B0;
border-width: 2px;
color: #777;
margin: 0;
}
label {
margin: 5px 0;
display: block;
font-size: 11px;
font-weight: 700;
letter-spacing: 1px;
color: #0153B0;
background: #eaeaea;
}
/*HEADER*/
/*MAIN*/
#wrapper{
height: 100%;
}
#header {
padding: 10px 15px 0;
justify-content: space-between;
align-items: center;
width: 100%;
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
background: #383e42;
color: #fcfcfc;
margin-bottom: 10px;
}
#header .material-icons{
font-size: 24px;
margin-top: -2px;
}
#appTitle{
font-size: 18px;
}
#appTabs{
display: flex;
/* margin-top: 15px; */
}
#appTabs .navTab{
padding: 10px 30px 15px;
cursor: pointer;
font-size: 14px;
opacity: 0.6;
border-bottom: 2px solid transparent;
text-transform: uppercase;
letter-spacing: 1px;
transition: all 0.2s;
position: relative;
}
#appTabs .navTab.selected{
border-bottom: 2px solid #fff;
opacity: 1;
}
#appTabs .navTab .running{
position: absolute;
right: 5px;
top: 7px;
color: #33ce33;
font-size: 22px;
}
#main{
padding: 5px 5px;
position: relative;
}
#client{
max-width: 400px;
min-width: 400px;
height: calc(100vh - 90px);
overflow-y: auto;
overflow-x: hidden;
padding-top: 10px;
}
#server{
max-width: 400px;
min-width: 400px;
height: calc(100vh - 90px);
overflow-y: auto;
overflow-x: hidden;
padding-top: 10px;
}
.input-element{
padding: 15px 10px;
position: relative;
}
.input-element label{
position: absolute;
top: 5px;
left: 20px;
padding: 0 5px;
z-index: 1;
}
.input-msg{
font-size: 12px;
color: #ff2f2f;
}
.box-50{
width: 50%;
margin: 5px;
}
.box-100{
width: 100%;
}
.console{
background: #2f323a;
border-radius: 5px;
color: #FCFCFC;
padding: 15px;
font-size: 12px;
line-height: 1.3;
font-family: monospace;
margin-right: 10px;
height: calc(100vh - 90px);
overflow: auto;
width: 100%;
user-select: text;
}
.console ::selection{
background-color: #33ce33;
color: #FCFCFC;
}
#hostBox,
#portBox,
#messageBox{
padding: 5px 5px 15px 10px;
position: relative;
}
#hostBox h3,
#portBox h3,
#messageBox h3{
margin: 5px 0;
display: flex;
font-size: 11px;
font-weight: 700;
letter-spacing: 1px;
color: #0153B0;
position: absolute;
align-items: center;
top: -9px;
left: 19px;
padding: 0 5px;
z-index: 1;
text-shadow: -1px 0 #eaeaea, 0 1px #eaeaea, 1px 0 #eaeaea, 0 -1px #eaeaea
}
#hostBox h3 span,
#portBox h3 span,
#messageBox h3 span{
z-index: 1;
}
#hostBox h3::after,
#portBox h3::after,
#messageBox h3::after{
content: '';
height: 1px;
width: 100%;
background: #eaeaea;
display: block;
position: absolute;
z-index: 0;
left: 0;
}
.tools-box{
position: absolute;
top: -8px;
right: 15px;
display: flex;
}
#hostBox .round-button,
#portBox .round-button,
#messageBox .round-button{
position: relative;
border-radius: 50px;
color: #fff;
padding: 5px;
height: 26px;
max-width: 26px;
overflow: hidden;
cursor: pointer;
align-items: center;
transition: max-width 0.3s;
z-index: 2;
margin-left: 10px;
}
#hostBox .round-button span,
#portBox .round-button span,
#messageBox .round-button span{
white-space: nowrap;
opacity: 0;
margin-right: 20px;
font-size: 14px;
padding: 1px 0 0 5px;
transition: opacity 0.2s;
}
#hostBox .round-button .material-icons,
#portBox .round-button .material-icons,
#messageBox .round-button .material-icons{
font-size: 16px;
height: 16px;
margin: 0;
position: absolute;
top: 5px;
right: 5px;
}
#hostBox .round-button:hover,
#portBox .round-button:hover,
#messageBox .round-button:hover{
max-width: 180px;
}
#hostBox .round-button:hover span,
#portBox .round-button:hover span,
#messageBox .round-button:hover span{
opacity: 1;
}
#messageBox .format{
font-family: "Roboto Mono";
font-size: 10px;
}
#hostList,
#portList,
#messageList{
max-height: 120px;
border: 1px solid #B7B7B7;
border-radius: 5px;
box-sizing: border-box;
padding: 8px 10px;
overflow: auto;
}
#portList{
max-height: 300px;
}
#configList{
padding: 0 5px 0;
max-height: 40vh;
overflow: auto;
margin-bottom: 20px;
}
#configList li{
display: flex;
align-items: center;
margin-bottom: 10px;
}
#configList li .radio-btn{
font-size: 24px;
cursor: pointer;
color: #1565C0;
}
#hostList li,
#portList li,
#configList li,
#messageList li{
position: relative;
padding-right: 25px;
}
#hostList li .deleteHost,
#portList li .deletePort,
#configList li .deleteConfig,
#messageList li .deleteMessage{
position: absolute;
top: -2px;
right: 0;
cursor: pointer;
font-size: 18px;
height: 18px;
width: 18px;
background: #e22424;
border-radius: 50px;
color: #EAEAEA;
opacity: 0;
transition: opacity 0.2s;
}
#hostList li .editHost,
#portList li .editPort,
#configList li .editConfig,
#messageList li .editMessage{
position: absolute;
top: -2px;
right: 24px;
cursor: pointer;
font-size: 16px;
height: 18px;
width: 18px;
background: #1565c0;
border-radius: 50px;
color: #EAEAEA;
opacity: 0;
transition: opacity 0.2s;
display: flex;
justify-content: center;
align-items: center;
}
#configList li .deleteConfig{
top: 3px;
margin: 0;
}
fieldset:not(:disabled) #hostList li:hover .deleteHost,
fieldset:not(:disabled) #portList li:hover .deletePort,
#configList li:hover .deleteConfig,
fieldset:not(:disabled) #messageList li:hover .deleteMessage,
fieldset:not(:disabled) #hostList li:hover .editHost,
fieldset:not(:disabled) #portList li:hover .editPort,
#configList li:hover .editConfig,
fieldset:not(:disabled) #messageList li:hover .editMessage{
opacity: 1;
}
#hostList li .checkbox,
#portList li .checkbox,
#messageList li .checkbox{
font-size: 13px;
color: #777;
margin-left: 5px;
width: fit-content;
}
#serverReports{
padding: 5px 5px 15px 10px;
}
#serverReports h3{
margin: 15px 0 20px;
}
#serverReports table{
width: 100%;
}
#serverReports th{
padding: 5px;
vertical-align: middle;
border-bottom: 2px solid #B7B7B7;
font-size: 14px;
font-weight: 700;
text-align: left;
}
#serverReports td{
border-bottom: 1px solid #B7B7B7;
padding: 5px 5px 4px;
font-size: 12px;
line-height: 1.2;
}
#serverReports tfoot{
background: #d0d0d0;
font-weight: 700;
}
.help{
cursor: help;
}
.bold{
font-weight: 700;
}
.green {
color: #33ce33;
}
.green-bg {
background: #2aa72a;
}
.blue-bg {
background: #0153B0;
}
.yellow {
color: yellow;
}
.red {
color: #ff2f2f;
}
#popcontainer{
position: fixed;
height: 100vh;
width: 100vw;
background: rgba(0,0,0,0.3);
z-index: 11;
display: flex;
justify-content: center;
align-items: center;
top: 0;
left: 0;
}
.popup{
padding: 15px;
border-radius: 3px;
background: #EAEAEA;
color: #000;
box-shadow: 0 0 10px -2px #000;
max-width: 70vw;
max-height: 90vh;
}
.popup h4 {
font-size: 18px;
margin-bottom: 15px;
}
.popup p{
line-height: 1.4;
}
.buttons {
display: flex;
justify-content: space-evenly;
}
.buttons button{
transition: filter 0.2s;
letter-spacing: 1px;
}
.buttons button:hover {
filter: brightness(110%);
}
.buttons button:active {
filter: brightness(85%);
}
.button-wrap{
position: relative;
margin: 20px 5px;
}
.button-wrap .material-icons{
position: absolute;
top: 11px;
left: 8px;
font-size: 16px;
z-index: 1;
pointer-events: none;
}
button.cancel {
background: transparent;
display: inline-block;
padding: 10px 14px 9px;
color: #0153B0 !important;
text-transform: uppercase;
border-radius: 5px;
border: none;
font-family: "Trebuchet MS", Helvetica, sans-serif;
cursor: pointer;
outline: none;
}
button.save {
display: inline-block;
padding: 10px 14px 9px;
text-transform: uppercase;
border-radius: 5px;
border: 1px solid #B7B7B7;
font-family: "Trebuchet MS", Helvetica, sans-serif;
cursor: pointer;
outline: none;
width: max-content;
}
button.confirm,
input[type="submit"]{
background: #0153B0;
display: inline-block;
padding: 10px 14px 9px;
color: #fff !important;
text-transform: uppercase;
border-radius: 5px;
border: none;
border-bottom: 1px solid #0153B0;
font-family: "Trebuchet MS", Helvetica, sans-serif;
cursor: pointer;
outline: none;
width: max-content;
}
button:disabled.confirm,
input[type="submit"]:disabled{
background: #777;
border-bottom: 1px solid #777;
cursor: not-allowed;
}
button.stop,
input[type="submit"]{
background: #e22424;
display: inline-block;
padding: 10px 14px 9px;
color: #fff !important;
text-transform: uppercase;
border-radius: 5px;
border: none;
border-bottom: 1px solid #e22424;
font-family: "Trebuchet MS", Helvetica, sans-serif;
cursor: pointer;
outline: none;
width: max-content;
}
.button-wrap .material-icons.white{
color: #fff;
}
.button-wrap button{
padding-left: 30px;
}
#error404{
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
text-align: center;
}
/*SIDEBAR*/
/*FOOTER*/
/*VARI*/
.txtright{
text-align: right;
}
.txtleft{
text-align: left;
}
.txtcenter{
text-align: center;
}
.alignright {
float: right;
margin: 0.5em;
margin-right:0;
}
.alignleft {
float: left;
margin: 0.5em;
margin-left:0;
}
.flex{
display: flex;
}
.checkbox{
display: flex;
align-items: center;
user-select: none;
cursor: pointer;
margin: 7px 10px;
transition: opacity 0.2s;
}
fieldset:disabled .checkbox{
opacity: 0.5;
cursor: default;
}
.toggle-select{
position: relative;
}
.toggle-select .material-icons{
font-size: 18px;
color: #1565C0;
z-index: 1;
position: relative;
cursor: pointer;
}
.toggle-select::after{
content: '';
height: 10px;
width: 10px;
background: #eaeaea;
display: block;
position: absolute;
top: 4px;
left: 4px;
border-radius: 1px;
z-index: 0;
}
.checkbox-block{
display: flex;
height: 14px;
width: 14px;
min-width: 14px;
background: #EBEBEB;
margin-right: 5px;
border: 2px solid #1565C0;
border-radius: 2px;
justify-content: center;
align-items: center;
transition: background 0.2s;
}
.checkbox input[type="checkbox"]{
display: none;
}
.checkbox input:checked + .checkbox-block{
background:#1565C0;
}
.checkbox input:checked + .checkbox-block::before{
content: 'done';
display: block;
font-family: 'Material Icons';
color: #fcfcfc;
font-size: 14px;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
}
/*TABLET PORTRAIT*/
@media only screen and (min-width: 768px) {
/*768px-HEADER*/
/*768px-MAIN*/
/*768px-FOOTER*/
}/**/
/*TABLET LANDSCAPE & NETBOOK*/
@media only screen and (min-width: 1024px){
/*1024px-HEADER*/
/*1024px-MAIN*/
/*SIDEBAR*/
/*1024px-FOOTER*/
}/**/
/*DESKTOP*/
@media only screen and (min-width: 1200px){
}/**/

View File

@ -0,0 +1,95 @@
html, body, div, span, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
abbr, address, cite, code,
del, dfn, em, img, ins, kbd, q, samp,
small, strong, sub, sup, var,
b, i,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, figcaption, figure,
footer, header, hgroup, menu, nav, section, summary,
time, mark, audio, video {
margin:0;
padding:0;
border:0;
outline:0;
font-size:100%;
vertical-align:baseline;
background:transparent;
}
body {
line-height:1;
}
article,aside,details,figcaption,figure,
footer,header,hgroup,menu,nav,section {
display:block;
}
nav ul {
list-style:none;
}
blockquote, q {
quotes:none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content:'';
content:none;
}
a {
margin:0;
padding:0;
font-size:100%;
vertical-align:baseline;
background:transparent;
text-decoration:none;
}
/* change colours to suit your needs */
ins {
background-color:#ff9;
color:#000;
text-decoration:none;
}
/* change colours to suit your needs */
mark {
background-color:#ff9;
color:#000;
font-style:italic;
font-weight:bold;
}
del {
text-decoration: line-through;
}
abbr[title], dfn[title] {
border-bottom:1px dotted;
cursor:help;
}
table {
border-collapse:collapse;
border-spacing:0;
}
/* change border colour to suit your needs */
hr {
display:block;
height:1px;
border:0;
border-top:1px solid #cccccc;
margin:1em 0;
padding:0;
}
input, select {
vertical-align:middle;
}

507
src/renderer/i18n/en-US.ts Normal file
View File

@ -0,0 +1,507 @@
export const enUS = {
word: {
edit: 'Edit',
save: 'Save',
close: 'Close',
delete: 'Delete',
confirm: 'Confirm',
cancel: 'Cancel',
send: 'Send',
connectionName: 'Connection name',
client: 'Client',
hostName: 'Host name',
port: 'Port',
user: 'User',
password: 'Password',
credentials: 'Credentials',
connect: 'Connect',
connected: 'Connected',
disconnect: 'Disconnect',
disconnected: 'Disconnected',
refresh: 'Refresh',
settings: 'Settings',
general: 'General',
themes: 'Themes',
update: 'Update',
about: 'About',
language: 'Language',
version: 'Version',
donate: 'Donate',
run: 'Run',
schema: 'Schema',
results: 'Results',
size: 'Size',
seconds: 'Seconds',
type: 'Type',
mimeType: 'Mime-Type',
download: 'Download',
add: 'Add',
data: 'Data',
properties: 'Properties',
insert: 'Insert',
connecting: 'Connecting',
name: 'Name',
collation: 'Collation',
clear: 'Clear',
options: 'Options',
autoRefresh: 'Auto-refresh',
indexes: 'Indexes',
foreignKeys: 'Foreign keys',
length: 'Length',
unsigned: 'Unsigned',
default: 'Default',
comment: 'Comment',
key: 'Key | Keys',
order: 'Order',
expression: 'Expression',
autoIncrement: 'Auto Increment',
engine: 'Engine',
field: 'Field | Fields',
approximately: 'Approximately',
total: 'Total',
table: 'Table',
discard: 'Discard',
stay: 'Stay',
author: 'Author',
light: 'Light',
dark: 'Dark',
autoCompletion: 'Auto Completion',
application: 'Application',
editor: 'Editor',
view: 'View',
definer: 'Definer',
algorithm: 'Algorithm',
trigger: 'Trigger | Triggers',
storedRoutine: 'Stored routine | Stored routines',
scheduler: 'Scheduler | Schedulers',
event: 'Event',
parameters: 'Parameters',
function: 'Function | Functions',
deterministic: 'Deterministic',
context: 'Context',
export: 'Export',
import: 'Import',
returns: 'Returns',
timing: 'Timing',
state: 'State',
execution: 'Execution',
starts: 'Starts',
ends: 'Ends',
ssl: 'SSL',
privateKey: 'Private key',
certificate: 'Certificate',
caCertificate: 'CA certificate',
ciphers: 'Ciphers',
upload: 'Upload',
browse: 'Browse',
faker: 'Faker',
content: 'Content',
cut: 'Cut',
copy: 'Copy',
paste: 'Paste',
tools: 'Tools',
variables: 'Variables',
processes: 'Processes',
database: 'Database',
scratchpad: 'Scratchpad',
array: 'Array',
changelog: 'Changelog',
format: 'Format',
sshTunnel: 'SSH tunnel',
structure: 'Structure',
small: 'Small',
medium: 'Medium',
large: 'Large',
row: 'Row | Rows',
cell: 'Cell | Cells',
triggerFunction: 'Trigger function | Trigger functions',
all: 'All',
duplicate: 'Duplicate',
routine: 'Routine',
new: 'New',
history: 'History',
select: 'Select',
passphrase: 'Passphrase',
filter: 'Filter',
change: 'Change',
views: 'Views',
triggers: 'Triggers',
routines: 'Routines',
functions: 'Functions',
schedulers: 'Schedulers',
includes: 'Includes',
drop: 'Drop',
completed: 'Completed',
aborted: 'Aborted',
disabled: 'Disabled',
enable: 'Enable',
disable: 'Disable',
commit: 'Commit',
rollback: 'Rollback',
connectionString: 'Connection string',
contributors: 'Contributors',
pin: 'Pin',
unpin: 'Unpin',
console: 'Console',
shortcuts: 'Shortcuts',
folder: 'Folder | Folders',
appearence: 'Appearence',
color: 'Color',
label: 'Label',
icon: 'Icon',
resultsTable: 'Results table'
},
message: {
appWelcome: 'Welcome to Antares SQL Client!',
appFirstStep: 'Your first step: create a new database connection.',
addConnection: 'Add connection',
createConnection: 'Create connection',
createNewConnection: 'Create new connection',
askCredentials: 'Ask for credentials',
testConnection: 'Test connection',
editConnection: 'Edit connection',
deleteConnection: 'Delete connection',
deleteCorfirm: 'Do you confirm the cancellation of',
connectionSuccessfullyMade: 'Connection successfully made!',
madeWithJS: 'Made with 💛 and JavaScript!',
checkForUpdates: 'Check for updates',
noUpdatesAvailable: 'No updates available',
checkingForUpdate: 'Checking for updates',
checkFailure: 'Check failed, please try later',
updateAvailable: 'Update available',
downloadingUpdate: 'Downloading update',
updateDownloaded: 'Update downloaded',
restartToInstall: 'Restart Antares to install',
unableEditFieldWithoutPrimary: 'Unable to edit a field without a primary key in resultset',
editCell: 'Edit cell',
deleteRows: 'Delete row | Delete {count} rows',
confirmToDeleteRows: 'Do you confirm to delete one row? | Do you confirm to delete {count} rows?',
notificationsTimeout: 'Notifications timeout',
uploadFile: 'Upload file',
addNewRow: 'Add new row',
numberOfInserts: 'Number of inserts',
openNewTab: 'Open a new tab',
affectedRows: 'Affected rows',
createNewDatabase: 'Create new Database',
databaseName: 'Database name',
serverDefault: 'Server default',
deleteDatabase: 'Delete database',
editDatabase: 'Edit database',
clearChanges: 'Clear changes',
addNewField: 'Add new field',
manageIndexes: 'Manage indexes',
manageForeignKeys: 'Manage foreign keys',
allowNull: 'Allow NULL',
zeroFill: 'Zero fill',
customValue: 'Custom value',
onUpdate: 'On update',
deleteField: 'Delete field',
createNewIndex: 'Create new index',
addToIndex: 'Add to index',
createNewTable: 'Create new table',
emptyTable: 'Empty table',
deleteTable: 'Delete table',
emptyCorfirm: 'Do you confirm to empty',
unsavedChanges: 'Unsaved changes',
discardUnsavedChanges: 'You have some unsaved changes. Closing this tab these changes will be discarded.',
thereAreNoIndexes: 'There are no indexes',
thereAreNoForeign: 'There are no foreign keys',
createNewForeign: 'Create new foreign key',
referenceTable: 'Ref. table',
referenceField: 'Ref. field',
foreignFields: 'Foreign fields',
invalidDefault: 'Invalid default',
onDelete: 'On delete',
applicationTheme: 'Application Theme',
editorTheme: 'Editor Theme',
wrapLongLines: 'Wrap long lines',
selectStatement: 'Select statement',
triggerStatement: 'Trigger statement',
sqlSecurity: 'SQL security',
updateOption: 'Update option',
deleteView: 'Delete view',
createNewView: 'Create new view',
deleteTrigger: 'Delete trigger',
createNewTrigger: 'Create new trigger',
currentUser: 'Current user',
routineBody: 'Routine body',
dataAccess: 'Data access',
thereAreNoParameters: 'There are no parameters',
createNewParameter: 'Create new parameter',
createNewRoutine: 'Create new stored routine',
deleteRoutine: 'Delete stored routine',
functionBody: 'Function body',
createNewFunction: 'Create new function',
deleteFunction: 'Delete function',
schedulerBody: 'Scheduler body',
createNewScheduler: 'Create new scheduler',
deleteScheduler: 'Delete scheduler',
preserveOnCompletion: 'Preserve on completion',
enableSsl: 'Enable SSL',
manualValue: 'Manual value',
tableFiller: 'Table Filler',
fakeDataLanguage: 'Fake data language',
searchForElements: 'Search for elements',
selectAll: 'Select all',
queryDuration: 'Query duration',
includeBetaUpdates: 'Include beta updates',
setNull: 'Set NULL',
processesList: 'Processes list',
processInfo: 'Process info',
manageUsers: 'Manage users',
createNewSchema: 'Create new schema',
schemaName: 'Schema name',
editSchema: 'Edit schema',
deleteSchema: 'Delete schema',
markdownSupported: 'Markdown supported',
plantATree: 'Plant a Tree',
dataTabPageSize: 'DATA tab page size',
enableSsh: 'Enable SSH',
pageNumber: 'Page number',
duplicateTable: 'Duplicate table',
noOpenTabs: 'There are no open tabs, navigate on the left bar or:',
noSchema: 'No schema',
restorePreviourSession: 'Restore previous session',
runQuery: 'Run query',
thereAreNoTableFields: 'There are no table fields',
newTable: 'New table',
newView: 'New view',
newTrigger: 'New trigger',
newRoutine: 'New routine',
newFunction: 'New function',
newScheduler: 'New scheduler',
newTriggerFunction: 'New trigger function',
thereIsNoQueriesYet: 'There is no queries yet',
searchForQueries: 'Search for queries',
killProcess: 'Kill process',
closeTab: 'Close tab',
exportSchema: 'Export schema',
importSchema: 'Import schema',
directoryPath: 'Directory path',
newInserStmtEvery: 'New INSERT statement every',
processingTableExport: 'Processing {table}',
fechingTableExport: 'Fetching {table} data',
writingTableExport: 'Writing {table} data',
checkAllTables: 'Check all tables',
uncheckAllTables: 'Uncheck all tables',
goToDownloadPage: 'Go to download page',
readOnlyMode: 'Read-only mode',
killQuery: 'Kill query',
insertRow: 'Insert row | Insert rows',
commitMode: 'Commit mode',
autoCommit: 'Auto commit',
manualCommit: 'Manual commit',
actionSuccessful: '{action} successful',
importQueryErrors: 'Warning: {n} error has occurrend | Warning: {n} errors occurred',
executedQueries: '{n} query executed | {n} queries executed',
ourputFormat: 'Output format',
singleFile: 'Single {ext} file',
zipCompressedFile: 'ZIP compressed {ext} file',
disableBlur: 'Disable blur',
untrustedConnection: 'Untrusted connection',
missingOrIncompleteTranslation: 'Missing or incomplete translation?',
findOutHowToContribute: 'Find out how to contribute',
disableFKChecks: 'Disable foreigh key checks',
allConnections: 'All connections',
searchForConnections: 'Search for connections',
disableScratchpad: 'Disable scratchpad',
reportABug: 'Report a bug',
nextTab: 'Next tab',
previousTab: 'Previous tab',
selectTabNumber: 'Select tab number {param}',
toggleConsole: 'Toggle console',
addShortcut: 'Add shortcut',
editShortcut: 'Edit shortcut',
deleteShortcut: 'Delete shortcut',
restoreDefaults: 'Restore defaults',
restoreDefaultsQuestion: 'Do you confirm to restore default values?',
registerAShortcut: 'Register a shortcut',
invalidShortcutMessage: 'Invalid combination, continue to type',
shortcutAlreadyExists: 'Shortcut already exists',
saveContent: 'Save content',
openAllConnections: 'Open all connections',
openSettings: 'Open settings',
openScratchpad: 'Open scratchpad',
runOrReload: 'Run or reload',
formatQuery: 'Format query',
queryHistory: 'Query history',
clearQuery: 'Clear query',
openFilter: 'Open filter',
nextResultsPage: 'Next results page',
previousResultsPage: 'Previous results page',
fillCell: 'Fill cell',
editFolder: 'Edit folder',
folderName: 'Folder name',
deleteFolder: 'Delete folder',
editConnectionAppearence: 'Edit connection appearence',
executeSelectedQuery: 'Execute selected query',
defaultCopyType: 'Default copy type',
showTableSize: 'Show table size in sidebar',
showTableSizeDescription: 'MySQL/MariaDB only. Enable this option may affects performance on schema with many tables.'
},
faker: {
address: 'Address',
commerce: 'Commerce',
company: 'Company',
database: 'Database',
date: 'Date',
finance: 'Finance',
git: 'Git',
hacker: 'Hacker',
internet: 'Internet',
lorem: 'Lorem',
name: 'Name',
music: 'Music',
phone: 'Phone',
random: 'Random',
system: 'System',
time: 'Time',
vehicle: 'Vehicle',
zipCode: 'Zip code',
zipCodeByState: 'Zip code by state',
city: 'City',
cityPrefix: 'City prefix',
citySuffix: 'City suffix',
streetName: 'Street name',
streetAddress: 'Street address',
streetSuffix: 'Street suffix',
streetPrefix: 'Street prefix',
secondaryAddress: 'Secondary address',
county: 'County',
country: 'Country',
countryCode: 'Country code',
state: 'State',
stateAbbr: 'State abbreviation',
latitude: 'Latitude',
longitude: 'Longitude',
direction: 'Direction',
cardinalDirection: 'Cardinal direction',
ordinalDirection: 'Ordinal direction',
nearbyGPSCoordinate: 'Nearby GPS coordinate',
timeZone: 'Time zone',
color: 'Color',
department: 'Department',
productName: 'Product name',
price: 'Price',
productAdjective: 'Product adjective',
productMaterial: 'Product material',
product: 'Product',
productDescription: 'Product description',
suffixes: 'Suffixes',
companyName: 'Company name',
companySuffix: 'Company suffix',
catchPhrase: 'Catch phrase',
bs: 'BS',
catchPhraseAdjective: 'Catch phrase adjective',
catchPhraseDescriptor: 'Catch phrase descriptor',
catchPhraseNoun: 'Catch phrase noun',
bsAdjective: 'BS adjective',
bsBuzz: 'BS buzz',
bsNoun: 'BS noun',
column: 'Column',
type: 'Type',
collation: 'Collation',
engine: 'Engine',
past: 'Past',
now: 'Now',
future: 'Future',
between: 'Between',
recent: 'Recent',
soon: 'Soon',
month: 'Month',
weekday: 'Weekday',
account: 'Account',
accountName: 'Account name',
routingNumber: 'Routing number',
mask: 'Mask',
amount: 'Amount',
transactionType: 'Transaction type',
currencyCode: 'Currency code',
currencyName: 'Currency name',
currencySymbol: 'Currency symbol',
bitcoinAddress: 'Bitcoin address',
litecoinAddress: 'Litecoin address',
creditCardNumber: 'Credit card number',
creditCardCVV: 'Credit card CVV',
ethereumAddress: 'Ethereum address',
iban: 'Iban',
bic: 'Bic',
transactionDescription: 'Transaction description',
branch: 'Branch',
commitEntry: 'Commit entry',
commitMessage: 'Commit message',
commitSha: 'Commit SHA',
shortSha: 'Short SHA',
abbreviation: 'Abbreviation',
adjective: 'Adjective',
noun: 'Noun',
verb: 'Verb',
ingverb: 'Ingverb',
phrase: 'Phrase',
avatar: 'Avatar',
email: 'Email',
exampleEmail: 'Example email',
userName: 'Username',
protocol: 'Protocol',
url: 'Url',
domainName: 'Domin name',
domainSuffix: 'Domain suffix',
domainWord: 'Domain word',
ip: 'Ip',
ipv6: 'Ipv6',
userAgent: 'User agent',
mac: 'Mac',
password: 'Password',
word: 'Word',
words: 'Words',
sentence: 'Sentence',
slug: 'Slug',
sentences: 'Sentences',
paragraph: 'Paragraph',
paragraphs: 'Paragraphs',
text: 'Text',
lines: 'Lines',
genre: 'Genre',
firstName: 'First name',
lastName: 'Last name',
middleName: 'Middle name',
findName: 'Full name',
jobTitle: 'Job title',
gender: 'Gender',
prefix: 'Prefix',
suffix: 'Suffix',
title: 'Title',
jobDescriptor: 'Job descriptor',
jobArea: 'Job area',
jobType: 'Job type',
phoneNumber: 'Phone number',
phoneNumberFormat: 'Phone number format',
phoneFormats: 'Phone formats',
number: 'Number',
float: 'Float',
arrayElement: 'Array element',
arrayElements: 'Array elements',
objectElement: 'Object element',
uuid: 'Uuid',
boolean: 'Boolean',
image: 'Image',
locale: 'Locale',
alpha: 'Alpha',
alphaNumeric: 'Alphanumeric',
hexaDecimal: 'Hexadecimal',
fileName: 'File name',
commonFileName: 'Common file name',
mimeType: 'Mime type',
commonFileType: 'Common file type',
commonFileExt: 'Common file extension',
fileType: 'File type',
fileExt: 'File extension',
directoryPath: 'Directory path',
filePath: 'File path',
semver: 'Semver',
manufacturer: 'Manufacturer',
model: 'Model',
fuel: 'Fuel',
vin: 'Vin'
}
};

View File

@ -0,0 +1,43 @@
import { createI18n } from 'vue-i18n';
import { enUS } from './en-US';
import { itIT } from './it-IT';
import { arSA } from './ar-SA';
import { esES } from './es-ES';
import { frFR } from './fr-FR';
import { ptBR } from './pt-BR';
import { deDE } from './de-DE';
import { viVN } from './vi-VN';
import { jaJP } from './ja-JP';
import { zhCN } from './zh-CN';
import { ruRU } from './ru-RU';
import { idID } from './id-ID';
const messages = {
'en-US': enUS,
'it-IT': itIT,
'ar-SA': arSA,
'es-ES': esES,
'fr-FR': frFR,
'pt-BR': ptBR,
'de-DE': deDE,
'vi-VN': viVN,
'ja-JP': jaJP,
'zh-CN': zhCN,
'ru-RU': ruRU,
'id-ID': idID
};
type NestedPartial<T> = {
[K in keyof T]?: T[K] extends Array<infer R> ? Array<NestedPartial<R>> : (T[K] extends unknown ? unknown : NestedPartial<T[K]>)
};
export type MessageSchema = typeof enUS
export type AvailableLocale = keyof typeof messages
const i18n = createI18n<[NestedPartial<MessageSchema>], AvailableLocale>({
fallbackLocale: 'en-US',
allowComposition: true,
messages
});
export { i18n };

491
src/renderer/i18n/it-IT.ts Normal file
View File

@ -0,0 +1,491 @@
export const itIT = {
word: {
edit: 'Modifica',
save: 'Salva',
close: 'Chiudi',
delete: 'Elimina',
confirm: 'Conferma',
cancel: 'Annulla',
send: 'Invia',
connectionName: 'Nome connessione',
client: 'Client',
hostName: 'Nome host',
port: 'Porta',
user: 'Utente',
password: 'Password',
credentials: 'Credenziali',
connect: 'Connetti',
connected: 'Connesso',
disconnect: 'Disconnetti',
disconnected: 'Disconnesso',
refresh: 'Aggiorna',
settings: 'Impostazioni',
general: 'Generale',
themes: 'Temi',
update: 'Aggiornamento',
about: 'Informazioni',
language: 'Lingua',
version: 'Versione',
donate: 'Dona',
run: 'Esegui',
schema: 'Schema',
results: 'Risultati',
size: 'Dimensioni',
seconds: 'Secondi',
type: 'Tipo',
mimeType: 'Mime-Type',
download: 'Scarica',
add: 'Aggiungi',
data: 'Dati',
properties: 'Proprietà',
insert: 'Inserisci',
connecting: 'Connessione in corso',
name: 'Nome',
collation: 'Confronto',
clear: 'Scarta',
options: 'Opzioni',
autoRefresh: 'Auto-aggiorna',
indexes: 'Indici',
foreignKeys: 'Chiavi esterne',
length: 'Lunghezza',
unsigned: 'Senza segno',
default: 'Default',
comment: 'Commento',
key: 'Chiave | Chiavi',
order: 'Ordine',
expression: 'Espressione',
autoIncrement: 'Auto Incremento',
engine: 'Motore',
field: 'Campo | Campi',
approximately: 'Approssimativamente',
total: 'Totali',
table: 'Tabella',
discard: 'Scarta',
stay: 'Resta',
author: 'Autore',
light: 'Chiaro',
dark: 'Scuro',
autoCompletion: 'Auto Completamento',
application: 'Applicazione',
editor: 'Editor',
view: 'Vista',
definer: 'Definer',
algorithm: 'Algoritmo',
trigger: 'Trigger | Triggers',
storedRoutine: 'Stored routine | Stored routines',
scheduler: 'Scheduler | Schedulers',
event: 'Evento',
parameters: 'Parametri',
function: 'Funzione | Funzioni',
deterministic: 'Deterministico',
context: 'Contesto',
export: 'Esporta',
import: 'Importa',
returns: 'Ritorna',
timing: 'Temporizzazione',
state: 'Stato',
execution: 'Esecuzione',
starts: 'Inizia',
ends: 'Finisce',
ssl: 'SSL',
privateKey: 'Chiave privata',
certificate: 'Certificato',
caCertificate: 'Certificato CA',
ciphers: 'Ciphers',
upload: 'Carica',
browse: 'Sfoglia',
faker: 'Faker',
content: 'Contenuto',
cut: 'Taglia',
copy: 'Copia',
paste: 'Incolla',
tools: 'Strumenti',
variables: 'Variabili',
processes: 'Processi',
database: 'Database',
scratchpad: 'Blocco appunti',
array: 'Array',
changelog: 'Changelog',
format: 'Formatta',
sshTunnel: 'SSH tunnel',
structure: 'Structure',
small: 'Piccolo',
medium: 'Medio',
large: 'Largo',
row: 'Riga | Righe',
cell: 'Cella | Celle',
triggerFunction: 'Funzione di trigger | Funzioni di trigger',
all: 'Tutto',
duplicate: 'Duplica',
routine: 'Routine',
new: 'Nuovo',
history: 'Cronologia',
select: 'Seleziona',
passphrase: 'Passphrase',
filter: 'Filtra',
change: 'Cambia',
views: 'Viste',
triggers: 'Trigger',
routines: 'Routine',
functions: 'Function',
schedulers: 'Scheduler',
includes: 'Includi',
drop: 'Drop',
completed: 'Completato',
aborted: 'Annullato',
disabled: 'Disabilitato',
enable: 'Abilita',
disable: 'Disabilita',
commit: 'Commit',
rollback: 'Rollback',
connectionString: 'Connection string',
contributors: 'Contributori',
pin: 'Fissa',
unpin: 'Sgancia',
console: 'Console',
shortcuts: 'Scorciatoie'
},
message: {
appWelcome: 'Benvenuto in Antares SQL Client!',
appFirstStep: 'Primo step: crea una nuova connessione.',
addConnection: 'Aggiungi connessione',
createConnection: 'Crea connessione',
createNewConnection: 'Crea nuova connessione',
askCredentials: 'Chiedi credenziali',
testConnection: 'Testa connessione',
editConnection: 'Modifica connessione',
deleteConnection: 'Elimina connessione',
deleteCorfirm: 'Confermi l\'eliminazione di',
connectionSuccessfullyMade: 'Connessione avvenuta con successo!',
madeWithJS: 'Fatto con 💛 e JavaScript!',
checkForUpdates: 'Cerca aggiornamenti',
noUpdatesAvailable: 'Nessun aggiornamento disponibile',
checkingForUpdate: 'Controllo aggiornamenti in corso',
checkFailure: 'Controllo fallito, riprova più tardi',
updateAvailable: 'Aggiornamento disponibile',
downloadingUpdate: 'Download dell\'aggiornamento',
updateDownloaded: 'Aggiornamento scaricato',
restartToInstall: 'Riavvia Antares per installare l\'aggiornamento',
unableEditFieldWithoutPrimary: 'Impossibile modificare il campo senza una primary key nel resultset',
editCell: 'Modifica cella',
deleteRows: 'Elimina riga | Elimina {count} righe',
confirmToDeleteRows: 'Confermi di voler cancellare una riga? | Confermi di voler cancellare {count} righe?',
notificationsTimeout: 'Timeout Notifiche',
uploadFile: 'Carica file',
addNewRow: 'Aggiungi nuova riga',
numberOfInserts: 'Numero di insert',
openNewTab: 'Apri nuova scheda',
affectedRows: 'Righe interessate',
createNewDatabase: 'Crea nuovo database',
databaseName: 'Nome database',
serverDefault: 'Default del server',
deleteDatabase: 'Cancella database',
editDatabase: 'Modifica database',
clearChanges: 'Scarta modifiche',
addNewField: 'Aggiungi nuovo campo',
manageIndexes: 'Gestisci indici',
manageForeignKeys: 'Gestisci chiavi esterne',
allowNull: 'Permetti NULL',
zeroFill: 'Riempimento con zero',
customValue: 'Varore personalizzato',
onUpdate: 'All\'aggiornamento',
deleteField: 'Cancella campo',
createNewIndex: 'Crea nuovo indice',
addToIndex: 'Aggiungi a indice',
createNewTable: 'Crea nuova tabella',
emptyTable: 'Svuota tabella',
deleteTable: 'Cancella tabella',
emptyCorfirm: 'Confermi di voler svuotare',
unsavedChanges: 'Modifiche non salvate',
discardUnsavedChanges: 'Hai modifiche non salvate. Lasciando questa scheda le modifiche saranno scartate.',
thereAreNoIndexes: 'Non ci sono indici',
thereAreNoForeign: 'Non ci sono chiavi esterne',
createNewForeign: 'Crea nuova chiave esterna',
referenceTable: 'Tabella di rif.',
referenceField: 'Campo di rif.',
foreignFields: 'Campi esterni',
invalidDefault: 'Default non valido',
onDelete: 'All\'eliminazione',
applicationTheme: 'Tema applicazione',
editorTheme: 'Tema editor',
wrapLongLines: 'A capo righe lunghe',
selectStatement: 'Dichiarazione select',
triggerStatement: 'Dichiarazione trigger',
sqlSecurity: 'Sicurezza SQL',
updateOption: 'Update option',
deleteView: 'Elimina vista',
createNewView: 'Crea nuova vista',
deleteTrigger: 'Elimina trigger',
createNewTrigger: 'Crea nuovo trigger',
currentUser: 'Utente attuale',
routineBody: 'Corpo della routine',
dataAccess: 'Accesso dati',
thereAreNoParameters: 'Non ci sono parametri',
createNewParameter: 'Crea nuovo parametro',
createNewRoutine: 'Crea nuova stored routine',
deleteRoutine: 'Elimina stored routine',
functionBody: 'Corpo della funzione',
createNewFunction: 'Crea nuova funzione',
deleteFunction: 'Elimina funzione',
schedulerBody: 'Corpo dello scheduler',
createNewScheduler: 'Crea nuovo scheduler',
deleteScheduler: 'Elimina scheduler',
preserveOnCompletion: 'Preserva al completamento',
enableSsl: 'Abilita SSL',
manualValue: 'Valore manuale',
tableFiller: 'Riempitore Tabella',
fakeDataLanguage: 'Lingua dati falsi',
searchForElements: 'Cerca elementi',
selectAll: 'Seleziona tutto',
queryDuration: 'Durata query',
includeBetaUpdates: 'Includi aggiornamenti beta',
setNull: 'Imposta NULL',
processesList: 'Lista processi',
processInfo: 'Info processo',
manageUsers: 'Gestisci utenti',
createNewSchema: 'Crea nuovo schema',
schemaName: 'Nome schema',
editSchema: 'Modifica schema',
deleteSchema: 'Elimina schema',
markdownSupported: 'Markdown supportato',
plantATree: 'Pianta un albero',
dataTabPageSize: 'Grandezza pagina tab DATI',
enableSsh: 'Abilita SSH',
pageNumber: 'Numero pagina',
duplicateTable: 'Duplica tabella',
noOpenTabs: 'Non ci sono tab aperte, naviga nella barra sinistra o:',
noSchema: 'Nessuno schema',
restorePreviourSession: 'Ripristina sessione precedente',
runQuery: 'Esegui query',
thereAreNoTableFields: 'Non ci sono campi della tabella',
newTable: 'Nuova tabella',
newView: 'Nuova vista',
newTrigger: 'Nuovo trigger',
newRoutine: 'Nuova routine',
newFunction: 'Nuova funzione',
newScheduler: 'Nuovo scheduler',
newTriggerFunction: 'Nuova funzione di trigger',
thereIsNoQueriesYet: 'Non ci sono ancora query',
searchForQueries: 'Cerca query',
killProcess: 'Uccidi processo',
closeTab: 'Chiudi tab',
exportSchema: 'Esporta schema',
importSchema: 'Importa schema',
directoryPath: 'Percorso directory',
newInserStmtEvery: 'Nuova istruzione INSERT ogni',
processingTableExport: 'Processo {table}',
fechingTableExport: 'Ricavo i dati {table}',
writingTableExport: 'Scrittura dati {table}',
checkAllTables: 'Seleziona tutte le tabelle',
uncheckAllTables: 'Deseleziona tutte le tabelle',
goToDownloadPage: 'Vai alla pagina di download',
readOnlyMode: 'Modalità sola lettura',
killQuery: 'Interrompi query',
insertRow: 'Inserisci riga | Inserisci righe',
commitMode: 'Modalità commit',
autoCommit: 'Auto commit',
manualCommit: 'Commit manuale',
actionSuccessful: '{action} riuscito',
importQueryErrors: 'Attenzione: si è verificato un errore | Attenzione si sono verificati {n} errori',
executedQueries: '{n} query eseguite | {n} query eseguite',
ourputFormat: 'Formato output',
singleFile: 'Singolo file {ext}',
zipCompressedFile: 'File {ext} zippato',
disableBlur: 'Disabilita sfocatura',
untrustedConnection: 'Connessione non affidabile',
missingOrIncompleteTranslation: 'Traduzione mancante o incompleta?',
findOutHowToContribute: 'Scopri come contribuire',
disableFKChecks: 'DIsabilita controllo foreigh key',
allConnections: 'Tutte le connessioni',
searchForConnections: 'Cerca una connessione',
disableScratchpad: 'Disabilita scratchpad',
reportABug: 'Segnala un bug',
nextTab: 'Prossima tab',
previousTab: 'Tab precedente',
selectTabNumber: 'Seleziona tab numero {param}',
toggleConsole: 'Attiva/disattiva console',
addShortcut: 'Aggiungi scorciatoia',
editShortcut: 'Modifica scorciatoia',
deleteShortcut: 'Cancella scorciatoia',
restoreDefaults: 'Ripristina predefiniti',
restoreDefaultsQuestion: 'Confermi di ripristinare i valori predefiniti?',
registerAShortcut: 'Registra una scorciatoia',
invalidShortcutMessage: 'Combinazione non valida, continua a digitare',
shortcutAlreadyExists: 'Scorciatoia esistente',
saveContent: 'Salva contenuto',
openAllConnections: 'Apri tutte le connessioni',
openSettings: 'Apri le impostazioni',
openScratchpad: 'Apri lo scratchpad',
runOrReload: 'Esegui o ricarica',
formatQuery: 'Formatta query',
queryHistory: 'Cronologia query',
clearQuery: 'Pulisci query',
openFilter: 'Apri il filtro',
nextResultsPage: 'Prossima pagina risultati',
previousResultsPage: 'Pagina risultati precedente'
},
faker: {
address: 'Indirizzo',
commerce: 'Commercio',
company: 'Compagnia',
database: 'Database',
date: 'Data',
finance: 'Finanza',
git: 'Git',
hacker: 'Hacker',
internet: 'Internet',
lorem: 'Lorem',
name: 'Nome',
music: 'Musica',
phone: 'Telefono',
random: 'Casuale',
system: 'Sistema',
time: 'Tempo',
vehicle: 'Veicolo',
zipCode: 'Codice zip',
zipCodeByState: 'Codice zip per stato',
city: 'Città',
cityPrefix: 'Prefisso città',
citySuffix: 'Suffisso città',
streetName: 'Nome strada',
streetAddress: 'Indirizzo strada',
streetSuffix: 'Suffisso strada',
streetPrefix: 'Prefisso strada',
secondaryAddress: 'Indirizzo secondario',
county: 'Contea',
country: 'Nazione',
countryCode: 'Codice nazione',
state: 'Stato',
stateAbbr: 'Abbreviazione stato',
latitude: 'Latitudine',
longitude: 'Longitudine',
direction: 'Direzione',
cardinalDirection: 'Direzione cardinale',
ordinalDirection: 'Direzione ordinale',
nearbyGPSCoordinate: 'Coordinate GPS vicine',
timeZone: 'Time zone',
color: 'Colore',
department: 'Dipartimento',
productName: 'Nome prodotto',
price: 'Prezzo',
productAdjective: 'Aggettivo prodotto',
productMaterial: 'Materiale prodotto',
product: 'Prodotto',
productDescription: 'Descrizione prodotto',
suffixes: 'Suffissi',
companyName: 'Nome compagnia',
companySuffix: 'Suffisso compagnia',
catchPhrase: 'Slogan',
bs: 'BS',
catchPhraseAdjective: 'Aggettivo slogan',
catchPhraseDescriptor: 'Descrittore slogan',
catchPhraseNoun: 'Sostantivo slogan',
bsAdjective: 'Aggettivo BS',
bsBuzz: 'Buzz BS',
bsNoun: 'Sostantivo BS',
column: 'Colonna',
type: 'Tipo',
collation: 'Confronto',
engine: 'Motore',
past: 'Passato',
future: 'Futuro',
between: 'Tra',
recent: 'Recente',
soon: 'Presto',
month: 'Mese',
weekday: 'Giorno della settimana',
account: 'Account',
accountName: 'Nome account',
routingNumber: 'Numero di instradamento',
mask: 'Maschera',
amount: 'Ammontare',
transactionType: 'Tipo transazione',
currencyCode: 'Codice valuta',
currencyName: 'Nome valuta',
currencySymbol: 'Simbolo valuta',
bitcoinAddress: 'Indirizzo Bitcoin',
litecoinAddress: 'Indirizzo Litecoin',
creditCardNumber: 'Numero carta di credito',
creditCardCVV: 'CVV carta di credito',
ethereumAddress: 'Indirizzo Ethereum',
iban: 'Iban',
bic: 'Bic',
transactionDescription: 'Descrizione transazione',
branch: 'Ramo',
commitEntry: 'Commit entry',
commitMessage: 'Messaggio di commit',
commitSha: 'SHA del commit',
shortSha: 'SHA breve',
abbreviation: 'Abbreviazione',
adjective: 'Aggettivo',
noun: 'Sostantivo',
verb: 'Verbo',
ingverb: 'Ingverb',
phrase: 'Frase',
avatar: 'Avatar',
email: 'Email',
exampleEmail: 'Email di esempio',
userName: 'Username',
protocol: 'Protocollo',
url: 'Url',
domainName: 'Nome dominio',
domainSuffix: 'Suffisso dominio',
domainWord: 'Parola dominio',
ip: 'Ip',
ipv6: 'Ipv6',
userAgent: 'User agent',
mac: 'Mac',
password: 'Password',
word: 'Parola',
words: 'Parole',
sentence: 'Sentenza',
slug: 'Slug',
sentences: 'Sentenze',
paragraph: 'Paragrafo',
paragraphs: 'Paragrafi',
text: 'Testo',
lines: 'Righe',
genre: 'Genere',
firstName: 'Nome',
lastName: 'Cognome',
middleName: 'Secondo nome',
findName: 'Nome completo',
jobTitle: 'Titolo di lavoro',
gender: 'Genere',
prefix: 'Prefisso',
suffix: 'Suffisso',
title: 'Titolo',
jobDescriptor: 'Descrittore del lavoro',
jobArea: 'Area di lavoro',
jobType: 'Tipo di lavoro',
phoneNumber: 'Numero di telefono',
phoneNumberFormat: 'Formato numeri di telefono',
phoneFormats: 'Formati di telefono',
number: 'Numero',
float: 'Float',
arrayElement: 'Elemento array',
arrayElements: 'Elementi array',
objectElement: 'Elemento object',
uuid: 'Uuid',
boolean: 'Booleano',
image: 'Immagine',
locale: 'Localizzazione',
alpha: 'Alfabetico',
alphaNumeric: 'Alfanumerico',
hexaDecimal: 'Esadecimale',
fileName: 'Nome file',
commonFileName: 'Nome file comune',
mimeType: 'Mime type',
commonFileType: 'Tipo file comune',
commonFileExt: 'Estensione file comune',
fileType: 'Tipo file',
fileExt: 'Estensione file',
directoryPath: 'Percorso directory',
filePath: 'Percorso file',
semver: 'Semver',
manufacturer: 'Produttore',
model: 'Modello',
fuel: 'Carburante',
vin: 'Vin'
}
};

View File

@ -0,0 +1,14 @@
export const localesNames: {[key: string]: string} = {
'en-US': 'English',
'it-IT': 'Italiano',
'ar-SA': 'العربية',
'es-ES': 'Español',
'fr-FR': 'Français',
'pt-BR': 'Português (Brasil)',
'de-DE': 'Deutsch (Deutschland)',
'vi-VN': 'Tiếng Việt',
'ja-JP': '日本語',
'zh-CN': '简体中文',
'ru-RU': 'Русский',
'id-ID': 'Bahasa Indonesia'
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 889 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -0,0 +1,301 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 1024 1024"
style="enable-background:new 0 0 1024 1024;"
xml:space="preserve"
sodipodi:docname="Antares-shape-2.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs80" /><sodipodi:namedview
id="namedview78"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.66015625"
inkscape:cx="210.55621"
inkscape:cy="511.2426"
inkscape:window-width="2560"
inkscape:window-height="1009"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="g75" />
<style
type="text/css"
id="style2">
.st0{fill:url(#SVGID_1_);}
.st1{fill:url(#XMLID_15_);}
.st2{opacity:0.5;fill:#FFBC00;}
.st3{fill:#FFBC00;}
.st4{fill:url(#XMLID_16_);}
.st5{fill:url(#XMLID_17_);}
.st6{fill:url(#XMLID_18_);}
.st7{fill:url(#XMLID_19_);}
.st8{fill:url(#XMLID_20_);}
.st9{fill:url(#XMLID_21_);}
.st10{opacity:0.46;}
.st11{fill:url(#XMLID_23_);}
.st12{fill:url(#XMLID_24_);}
.st13{fill:url(#XMLID_25_);}
.st14{fill:url(#XMLID_27_);}
.st15{fill:url(#XMLID_28_);}
.st16{fill:url(#XMLID_29_);}
.st17{fill:url(#XMLID_30_);}
.st18{opacity:0.75;}
.st19{opacity:0.44;clip-path:url(#XMLID_31_);}
.st20{fill:url(#XMLID_32_);}
.st21{fill:url(#XMLID_33_);}
.st22{fill:url(#XMLID_34_);}
.st23{fill:url(#XMLID_35_);}
.st24{fill:url(#XMLID_37_);}
.st25{fill:url(#XMLID_38_);}
.st26{opacity:0.43;fill:#FFBC00;}
.st27{opacity:0.58;fill:#FFBC00;}
.st28{fill:#FFBE06;}
.st29{fill:none;}
.st30{fill:url(#XMLID_42_);}
.st31{fill:url(#XMLID_43_);}
.st32{fill:url(#XMLID_44_);}
.st33{fill:url(#XMLID_45_);}
.st34{fill:url(#XMLID_46_);}
.st35{fill:url(#SVGID_4_);}
.st36{fill:url(#SVGID_5_);}
.st37{fill:url(#SVGID_6_);}
.st38{fill:url(#SVGID_7_);}
.st39{fill:url(#SVGID_8_);}
.st40{fill:url(#SVGID_11_);}
.st41{fill:url(#SVGID_12_);}
.st42{fill:url(#SVGID_13_);}
.st43{fill:url(#SVGID_14_);}
.st44{fill:#C68D00;}
.st45{fill:#CE000F;}
</style>
<g
id="g75">
<radialGradient
id="XMLID_15_"
cx="358.2692"
cy="227.2655"
r="830.0055"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
style="stop-color:#F6971E"
id="stop4" />
<stop
offset="0.6338"
style="stop-color:#F4592D"
id="stop6" />
<stop
offset="0.7025"
style="stop-color:#EF4F29"
id="stop8" />
<stop
offset="0.8178"
style="stop-color:#E1351D"
id="stop10" />
<stop
offset="0.9647"
style="stop-color:#CA0B0B"
id="stop12" />
<stop
offset="1"
style="stop-color:#C40006"
id="stop14" />
</radialGradient>
<path
id="XMLID_124_"
style="fill:#ffffff;fill-opacity:0.15000001"
class="st1"
d="M 510.30078 9.0996094 A 502.79999 502.79999 0 0 0 7.5 511.90039 A 502.79999 502.79999 0 0 0 510.30078 1014.6992 A 502.79999 502.79999 0 0 0 1013.0996 511.90039 A 502.79999 502.79999 0 0 0 510.30078 9.0996094 z M 326.17578 78.685547 C 349.74023 78.648438 372.16211 83.875 393.09961 95 C 420.19961 112.9 439.6 136.99922 455 172.69922 C 465.4 205.69922 469.30078 238.79961 465.80078 281.59961 C 457.70078 373.59961 411.09922 469.09961 341.19922 531.59961 C 312.29922 557.49961 284.4 573.59961 256.5 589.59961 L 256.69922 593.19922 C 279.39922 594.99922 301.39922 586.10078 326.19922 571.80078 C 415.89922 516.50078 483.49922 397.69922 504.69922 285.19922 C 495.49922 338.79922 478.19922 391.4 449.69922 445 C 423.79922 489.6 397.30039 527.1 359.40039 562 C 300.00039 612.9 238.89961 638.80078 183.09961 626.30078 C 122.39961 611.00078 90.399609 545.8 92.599609 461 C 92.399609 457.4 92.1 453.89961 89 455.59961 C 87.6 458.29961 84.700781 463.60078 83.300781 466.30078 C 69.000781 517.30078 77.1 562.79922 91 601.19922 C 116.5 666.39922 177.79922 688.69922 244.69922 676.19922 C 204.99922 685.99922 167.69922 683.3 134.19922 665.5 C 107.09922 647.6 84.599609 625.30039 70.599609 586.90039 C 55.899609 537.80039 50.700391 486.89922 70.400391 421.69922 C 95.300391 338.69922 139.50078 255.59922 207.30078 209.19922 C 227.30078 195.79922 245.99922 185.09961 267.69922 172.59961 C 270.79922 170.79961 270.60039 167.20039 268.90039 166.40039 C 159.40039 170.00039 46.500391 333.30039 20.900391 474.40039 C 36.400391 372.60039 87.500391 272.6 167.90039 198.5 C 213.50039 157.4 258.79922 136.89922 305.19922 130.69922 C 386.89922 122.69922 437.10078 194.1 434.80078 299.5 C 435.00078 303.1 436.70039 304.00039 438.40039 304.90039 C 446.50039 281.70039 451.39961 260.29922 451.59961 239.69922 C 451.50293 126.5894 381.21362 66.112098 291.87891 82.363281 C 288.79216 82.977349 285.71662 83.531343 282.59961 84.300781 C 285.71508 83.559319 288.80642 82.922208 291.87891 82.363281 C 303.5351 80.044429 314.99734 78.703151 326.17578 78.685547 z M 858.04883 508.3457 C 873.78027 508.64453 888.6875 512.44922 902.5 520.19922 C 920.3 532.49922 935.10078 547.69961 943.80078 573.59961 C 952.90078 606.59961 955.59961 640.70039 941.59961 683.90039 C 923.79961 739.00039 893.09961 793.80039 847.09961 823.90039 C 833.49961 832.60039 820.89922 839.4 806.19922 847.5 C 804.09922 848.6 804.20078 850.99922 805.30078 851.69922 C 878.70078 851.09922 956.39961 743.59922 975.59961 649.69922 C 963.79961 717.49922 928.2 783.50078 873.5 831.80078 C 842.5 858.60078 811.90078 871.59961 780.80078 875.09961 C 726.10078 879.29961 693.59922 830.9 696.69922 760.5 C 696.59922 758.1 695.50039 757.50039 694.40039 756.90039 C 688.70039 772.30039 685.09961 786.49922 684.59961 800.19922 C 683.15311 870.80636 723.40104 911.63389 777.53906 908.77344 C 757.57984 910.46578 738.70856 907.29881 721.59961 897.69922 C 703.79961 885.39922 691.10039 869.00039 681.40039 844.90039 C 674.90039 822.70039 672.79922 800.6 675.69922 772 C 682.39922 710.7 714.9 647.60078 762.5 606.80078 C 782.1 589.90078 801.00039 579.60078 819.90039 569.30078 L 819.80078 566.90039 C 804.70078 565.40039 789.89961 570.99922 773.09961 580.19922 C 712.39961 615.89922 665.50078 694.2 649.80078 769 C 656.70078 733.4 669.00078 698.39961 688.80078 663.09961 C 706.80078 633.69961 725.00078 609.00078 750.80078 586.30078 C 791.20078 553.20078 832.40039 536.80039 869.40039 545.90039 C 909.80039 556.90039 930.2 600.9 927.5 657.5 C 927.6 659.9 927.70078 662.29961 929.80078 661.09961 C 930.80078 659.29961 932.80078 655.8 933.80078 654 C 944.00078 620.2 939.3 589.70078 930.5 563.80078 C 914.4 519.90078 873.80039 504.1 828.90039 511.5 C 838.87539 509.25 848.60996 508.16641 858.04883 508.3457 z " />
<linearGradient
id="XMLID_16_"
gradientUnits="userSpaceOnUse"
x1="505.4734"
y1="-52.674"
x2="505.4734"
y2="155.1105">
<stop
offset="0"
style="stop-color:#FFFFFF;stop-opacity:0.4"
id="stop18" />
<stop
offset="1"
style="stop-color:#FFFFFF;stop-opacity:0"
id="stop20" />
</linearGradient>
<linearGradient
id="XMLID_17_"
gradientUnits="userSpaceOnUse"
x1="503.3253"
y1="-583.7885"
x2="503.3253"
y2="-376.4571"
gradientTransform="matrix(-1 0 0 -1 1017 456.5313)">
<stop
offset="5.263158e-03"
style="stop-color:#9E3A1D;stop-opacity:0.4"
id="stop24" />
<stop
offset="1"
style="stop-color:#9E3A1D;stop-opacity:0"
id="stop26" />
</linearGradient>
<linearGradient
id="XMLID_18_"
gradientUnits="userSpaceOnUse"
x1="506.1886"
y1="-38.7551"
x2="506.1886"
y2="169.0294"
gradientTransform="matrix(4.489700e-11 1 -1 4.489700e-11 1026.6101 -2.3899)">
<stop
offset="5.263158e-03"
style="stop-color:#9E3A1D;stop-opacity:0.4"
id="stop30" />
<stop
offset="1"
style="stop-color:#9E3A1D;stop-opacity:0"
id="stop32" />
</linearGradient>
<linearGradient
id="XMLID_19_"
gradientUnits="userSpaceOnUse"
x1="502.6101"
y1="-660.7308"
x2="502.6101"
y2="-450.373"
gradientTransform="matrix(-4.489700e-11 -1 1 -4.489700e-11 570.0789 1014.6101)">
<stop
offset="0"
style="stop-color:#FFFFFF;stop-opacity:0.4"
id="stop36" />
<stop
offset="1"
style="stop-color:#FFFFFF;stop-opacity:0"
id="stop38" />
</linearGradient>
<g
id="XMLID_39_"
class="st10">
<defs
id="defs43">
<ellipse
id="XMLID_36_"
transform="matrix(0.8019 -0.5974 0.5974 0.8019 -204.724 406.2339)"
class="st10"
cx="510.3"
cy="511.9"
ry="502.8"
rx="502.8" />
</defs>
<clipPath
id="XMLID_20_">
<use
xlink:href="#XMLID_36_"
style="overflow:visible;"
id="use45" />
</clipPath>
</g>
<g
id="XMLID_41_">
<linearGradient
id="XMLID_21_"
gradientUnits="userSpaceOnUse"
x1="64.4989"
y1="234.8705"
x2="401.1502"
y2="480.7042">
<stop
offset="0"
style="stop-color:#F4592D"
id="stop49" />
<stop
offset="1"
style="stop-color:#FFD900"
id="stop51" />
</linearGradient>
<linearGradient
id="XMLID_22_"
gradientUnits="userSpaceOnUse"
x1="438.1351"
y1="490.0881"
x2="196.6566"
y2="356.1772">
<stop
offset="0"
style="stop-color:#F64626"
id="stop55" />
<stop
offset="1"
style="stop-color:#FFD900"
id="stop57" />
</linearGradient>
</g>
<g
id="XMLID_11_">
<linearGradient
id="XMLID_23_"
gradientUnits="userSpaceOnUse"
x1="-316.9261"
y1="9.5862"
x2="-92.0354"
y2="173.8087"
gradientTransform="matrix(-0.9998 -2.148304e-02 2.148304e-02 -0.9998 625.9278 811.8477)">
<stop
offset="0"
style="stop-color:#F4592D"
id="stop62" />
<stop
offset="1"
style="stop-color:#FFD900"
id="stop64" />
</linearGradient>
<linearGradient
id="XMLID_24_"
gradientUnits="userSpaceOnUse"
x1="-52.9013"
y1="188.0779"
x2="-214.2144"
y2="98.6225"
gradientTransform="matrix(-0.9998 -2.148304e-02 2.148304e-02 -0.9998 625.9278 811.8477)">
<stop
offset="0"
style="stop-color:#F42C2D"
id="stop68" />
<stop
offset="1"
style="stop-color:#FFD900"
id="stop70" />
</linearGradient>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,301 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 1024 1024"
style="enable-background:new 0 0 1024 1024;"
xml:space="preserve"
sodipodi:docname="Antares-shape-1.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs80" /><sodipodi:namedview
id="namedview78"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.66015625"
inkscape:cx="512"
inkscape:cy="511.2426"
inkscape:window-width="2560"
inkscape:window-height="1009"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="g75" />
<style
type="text/css"
id="style2">
.st0{fill:url(#SVGID_1_);}
.st1{fill:url(#XMLID_15_);}
.st2{opacity:0.5;fill:#FFBC00;}
.st3{fill:#FFBC00;}
.st4{fill:url(#XMLID_16_);}
.st5{fill:url(#XMLID_17_);}
.st6{fill:url(#XMLID_18_);}
.st7{fill:url(#XMLID_19_);}
.st8{fill:url(#XMLID_20_);}
.st9{fill:url(#XMLID_21_);}
.st10{opacity:0.46;}
.st11{fill:url(#XMLID_23_);}
.st12{fill:url(#XMLID_24_);}
.st13{fill:url(#XMLID_25_);}
.st14{fill:url(#XMLID_27_);}
.st15{fill:url(#XMLID_28_);}
.st16{fill:url(#XMLID_29_);}
.st17{fill:url(#XMLID_30_);}
.st18{opacity:0.75;}
.st19{opacity:0.44;clip-path:url(#XMLID_31_);}
.st20{fill:url(#XMLID_32_);}
.st21{fill:url(#XMLID_33_);}
.st22{fill:url(#XMLID_34_);}
.st23{fill:url(#XMLID_35_);}
.st24{fill:url(#XMLID_37_);}
.st25{fill:url(#XMLID_38_);}
.st26{opacity:0.43;fill:#FFBC00;}
.st27{opacity:0.58;fill:#FFBC00;}
.st28{fill:#FFBE06;}
.st29{fill:none;}
.st30{fill:url(#XMLID_42_);}
.st31{fill:url(#XMLID_43_);}
.st32{fill:url(#XMLID_44_);}
.st33{fill:url(#XMLID_45_);}
.st34{fill:url(#XMLID_46_);}
.st35{fill:url(#SVGID_4_);}
.st36{fill:url(#SVGID_5_);}
.st37{fill:url(#SVGID_6_);}
.st38{fill:url(#SVGID_7_);}
.st39{fill:url(#SVGID_8_);}
.st40{fill:url(#SVGID_11_);}
.st41{fill:url(#SVGID_12_);}
.st42{fill:url(#SVGID_13_);}
.st43{fill:url(#SVGID_14_);}
.st44{fill:#C68D00;}
.st45{fill:#CE000F;}
</style>
<g
id="g75">
<radialGradient
id="XMLID_15_"
cx="358.2692"
cy="227.2655"
r="830.0055"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
style="stop-color:#F6971E"
id="stop4" />
<stop
offset="0.6338"
style="stop-color:#F4592D"
id="stop6" />
<stop
offset="0.7025"
style="stop-color:#EF4F29"
id="stop8" />
<stop
offset="0.8178"
style="stop-color:#E1351D"
id="stop10" />
<stop
offset="0.9647"
style="stop-color:#CA0B0B"
id="stop12" />
<stop
offset="1"
style="stop-color:#C40006"
id="stop14" />
</radialGradient>
<path
id="XMLID_124_"
style="fill:#000000;fill-opacity:0.5"
class="st1"
d="M 510.30078 9.0996094 A 502.79999 502.79999 0 0 0 7.5 511.90039 A 502.79999 502.79999 0 0 0 510.30078 1014.6992 A 502.79999 502.79999 0 0 0 1013.0996 511.90039 A 502.79999 502.79999 0 0 0 510.30078 9.0996094 z M 326.17578 78.685547 C 349.74023 78.648438 372.16211 83.875 393.09961 95 C 420.19961 112.9 439.6 136.99922 455 172.69922 C 465.4 205.69922 469.30078 238.79961 465.80078 281.59961 C 457.70078 373.59961 411.09922 469.09961 341.19922 531.59961 C 312.29922 557.49961 284.4 573.59961 256.5 589.59961 L 256.69922 593.19922 C 279.39922 594.99922 301.39922 586.10078 326.19922 571.80078 C 415.89922 516.50078 483.49922 397.69922 504.69922 285.19922 C 495.49922 338.79922 478.19922 391.4 449.69922 445 C 423.79922 489.6 397.30039 527.1 359.40039 562 C 300.00039 612.9 238.89961 638.80078 183.09961 626.30078 C 122.39961 611.00078 90.399609 545.8 92.599609 461 C 92.399609 457.4 92.1 453.89961 89 455.59961 C 87.6 458.29961 84.700781 463.60078 83.300781 466.30078 C 69.000781 517.30078 77.1 562.79922 91 601.19922 C 116.5 666.39922 177.79922 688.69922 244.69922 676.19922 C 204.99922 685.99922 167.69922 683.3 134.19922 665.5 C 107.09922 647.6 84.599609 625.30039 70.599609 586.90039 C 55.899609 537.80039 50.700391 486.89922 70.400391 421.69922 C 95.300391 338.69922 139.50078 255.59922 207.30078 209.19922 C 227.30078 195.79922 245.99922 185.09961 267.69922 172.59961 C 270.79922 170.79961 270.60039 167.20039 268.90039 166.40039 C 159.40039 170.00039 46.500391 333.30039 20.900391 474.40039 C 36.400391 372.60039 87.500391 272.6 167.90039 198.5 C 213.50039 157.4 258.79922 136.89922 305.19922 130.69922 C 386.89922 122.69922 437.10078 194.1 434.80078 299.5 C 435.00078 303.1 436.70039 304.00039 438.40039 304.90039 C 446.50039 281.70039 451.39961 260.29922 451.59961 239.69922 C 451.50293 126.5894 381.21362 66.112098 291.87891 82.363281 C 288.79216 82.977349 285.71662 83.531343 282.59961 84.300781 C 285.71508 83.559319 288.80642 82.922208 291.87891 82.363281 C 303.5351 80.044429 314.99734 78.703151 326.17578 78.685547 z M 858.04883 508.3457 C 873.78027 508.64453 888.6875 512.44922 902.5 520.19922 C 920.3 532.49922 935.10078 547.69961 943.80078 573.59961 C 952.90078 606.59961 955.59961 640.70039 941.59961 683.90039 C 923.79961 739.00039 893.09961 793.80039 847.09961 823.90039 C 833.49961 832.60039 820.89922 839.4 806.19922 847.5 C 804.09922 848.6 804.20078 850.99922 805.30078 851.69922 C 878.70078 851.09922 956.39961 743.59922 975.59961 649.69922 C 963.79961 717.49922 928.2 783.50078 873.5 831.80078 C 842.5 858.60078 811.90078 871.59961 780.80078 875.09961 C 726.10078 879.29961 693.59922 830.9 696.69922 760.5 C 696.59922 758.1 695.50039 757.50039 694.40039 756.90039 C 688.70039 772.30039 685.09961 786.49922 684.59961 800.19922 C 683.15311 870.80636 723.40104 911.63389 777.53906 908.77344 C 757.57984 910.46578 738.70856 907.29881 721.59961 897.69922 C 703.79961 885.39922 691.10039 869.00039 681.40039 844.90039 C 674.90039 822.70039 672.79922 800.6 675.69922 772 C 682.39922 710.7 714.9 647.60078 762.5 606.80078 C 782.1 589.90078 801.00039 579.60078 819.90039 569.30078 L 819.80078 566.90039 C 804.70078 565.40039 789.89961 570.99922 773.09961 580.19922 C 712.39961 615.89922 665.50078 694.2 649.80078 769 C 656.70078 733.4 669.00078 698.39961 688.80078 663.09961 C 706.80078 633.69961 725.00078 609.00078 750.80078 586.30078 C 791.20078 553.20078 832.40039 536.80039 869.40039 545.90039 C 909.80039 556.90039 930.2 600.9 927.5 657.5 C 927.6 659.9 927.70078 662.29961 929.80078 661.09961 C 930.80078 659.29961 932.80078 655.8 933.80078 654 C 944.00078 620.2 939.3 589.70078 930.5 563.80078 C 914.4 519.90078 873.80039 504.1 828.90039 511.5 C 838.87539 509.25 848.60996 508.16641 858.04883 508.3457 z " />
<linearGradient
id="XMLID_16_"
gradientUnits="userSpaceOnUse"
x1="505.4734"
y1="-52.674"
x2="505.4734"
y2="155.1105">
<stop
offset="0"
style="stop-color:#FFFFFF;stop-opacity:0.4"
id="stop18" />
<stop
offset="1"
style="stop-color:#FFFFFF;stop-opacity:0"
id="stop20" />
</linearGradient>
<linearGradient
id="XMLID_17_"
gradientUnits="userSpaceOnUse"
x1="503.3253"
y1="-583.7885"
x2="503.3253"
y2="-376.4571"
gradientTransform="matrix(-1 0 0 -1 1017 456.5313)">
<stop
offset="5.263158e-03"
style="stop-color:#9E3A1D;stop-opacity:0.4"
id="stop24" />
<stop
offset="1"
style="stop-color:#9E3A1D;stop-opacity:0"
id="stop26" />
</linearGradient>
<linearGradient
id="XMLID_18_"
gradientUnits="userSpaceOnUse"
x1="506.1886"
y1="-38.7551"
x2="506.1886"
y2="169.0294"
gradientTransform="matrix(4.489700e-11 1 -1 4.489700e-11 1026.6101 -2.3899)">
<stop
offset="5.263158e-03"
style="stop-color:#9E3A1D;stop-opacity:0.4"
id="stop30" />
<stop
offset="1"
style="stop-color:#9E3A1D;stop-opacity:0"
id="stop32" />
</linearGradient>
<linearGradient
id="XMLID_19_"
gradientUnits="userSpaceOnUse"
x1="502.6101"
y1="-660.7308"
x2="502.6101"
y2="-450.373"
gradientTransform="matrix(-4.489700e-11 -1 1 -4.489700e-11 570.0789 1014.6101)">
<stop
offset="0"
style="stop-color:#FFFFFF;stop-opacity:0.4"
id="stop36" />
<stop
offset="1"
style="stop-color:#FFFFFF;stop-opacity:0"
id="stop38" />
</linearGradient>
<g
id="XMLID_39_"
class="st10">
<defs
id="defs43">
<ellipse
id="XMLID_36_"
transform="matrix(0.8019 -0.5974 0.5974 0.8019 -204.724 406.2339)"
class="st10"
cx="510.3"
cy="511.9"
ry="502.8"
rx="502.8" />
</defs>
<clipPath
id="XMLID_20_">
<use
xlink:href="#XMLID_36_"
style="overflow:visible;"
id="use45" />
</clipPath>
</g>
<g
id="XMLID_41_">
<linearGradient
id="XMLID_21_"
gradientUnits="userSpaceOnUse"
x1="64.4989"
y1="234.8705"
x2="401.1502"
y2="480.7042">
<stop
offset="0"
style="stop-color:#F4592D"
id="stop49" />
<stop
offset="1"
style="stop-color:#FFD900"
id="stop51" />
</linearGradient>
<linearGradient
id="XMLID_22_"
gradientUnits="userSpaceOnUse"
x1="438.1351"
y1="490.0881"
x2="196.6566"
y2="356.1772">
<stop
offset="0"
style="stop-color:#F64626"
id="stop55" />
<stop
offset="1"
style="stop-color:#FFD900"
id="stop57" />
</linearGradient>
</g>
<g
id="XMLID_11_">
<linearGradient
id="XMLID_23_"
gradientUnits="userSpaceOnUse"
x1="-316.9261"
y1="9.5862"
x2="-92.0354"
y2="173.8087"
gradientTransform="matrix(-0.9998 -2.148304e-02 2.148304e-02 -0.9998 625.9278 811.8477)">
<stop
offset="0"
style="stop-color:#F4592D"
id="stop62" />
<stop
offset="1"
style="stop-color:#FFD900"
id="stop64" />
</linearGradient>
<linearGradient
id="XMLID_24_"
gradientUnits="userSpaceOnUse"
x1="-52.9013"
y1="188.0779"
x2="-214.2144"
y2="98.6225"
gradientTransform="matrix(-0.9998 -2.148304e-02 2.148304e-02 -0.9998 625.9278 811.8477)">
<stop
offset="0"
style="stop-color:#F42C2D"
id="stop68" />
<stop
offset="1"
style="stop-color:#FFD900"
id="stop70" />
</linearGradient>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

@ -0,0 +1,154 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:url(#XMLID_15_);}
.st2{opacity:0.5;fill:#FFBC00;}
.st3{fill:#FFBC00;}
.st4{fill:url(#XMLID_16_);}
.st5{fill:url(#XMLID_17_);}
.st6{fill:url(#XMLID_18_);}
.st7{fill:url(#XMLID_19_);}
.st8{fill:url(#XMLID_20_);}
.st9{fill:url(#XMLID_21_);}
.st10{opacity:0.46;}
.st11{fill:url(#XMLID_23_);}
.st12{fill:url(#XMLID_24_);}
.st13{fill:url(#XMLID_25_);}
.st14{fill:url(#XMLID_27_);}
.st15{fill:url(#XMLID_28_);}
.st16{fill:url(#XMLID_29_);}
.st17{fill:url(#XMLID_30_);}
.st18{opacity:0.75;}
.st19{opacity:0.44;clip-path:url(#XMLID_31_);}
.st20{fill:url(#XMLID_32_);}
.st21{fill:url(#XMLID_33_);}
.st22{fill:url(#XMLID_34_);}
.st23{fill:url(#XMLID_35_);}
.st24{fill:url(#XMLID_37_);}
.st25{fill:url(#XMLID_38_);}
.st26{opacity:0.43;fill:#FFBC00;}
.st27{opacity:0.58;fill:#FFBC00;}
.st28{fill:#FFBE06;}
.st29{fill:none;}
.st30{fill:url(#XMLID_42_);}
.st31{fill:url(#XMLID_43_);}
.st32{fill:url(#XMLID_44_);}
.st33{fill:url(#XMLID_45_);}
.st34{fill:url(#XMLID_46_);}
.st35{fill:url(#SVGID_4_);}
.st36{fill:url(#SVGID_5_);}
.st37{fill:url(#SVGID_6_);}
.st38{fill:url(#SVGID_7_);}
.st39{fill:url(#SVGID_8_);}
.st40{fill:url(#SVGID_11_);}
.st41{fill:url(#SVGID_12_);}
.st42{fill:url(#SVGID_13_);}
.st43{fill:url(#SVGID_14_);}
.st44{fill:#C68D00;}
.st45{fill:#CE000F;}
</style>
<g>
<radialGradient id="XMLID_15_" cx="358.2692" cy="227.2655" r="830.0055" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#F6971E"/>
<stop offset="0.6338" style="stop-color:#F4592D"/>
<stop offset="0.7025" style="stop-color:#EF4F29"/>
<stop offset="0.8178" style="stop-color:#E1351D"/>
<stop offset="0.9647" style="stop-color:#CA0B0B"/>
<stop offset="1" style="stop-color:#C40006"/>
</radialGradient>
<circle id="XMLID_124_" class="st1" cx="510.3" cy="511.9" r="502.8"/>
<linearGradient id="XMLID_16_" gradientUnits="userSpaceOnUse" x1="505.4734" y1="-52.674" x2="505.4734" y2="155.1105">
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.4"/>
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
</linearGradient>
<path id="XMLID_59_" class="st4" d="M861.5,198.8c0,87.2-170.7-23.4-344.5-23.4S149.4,294.2,149.4,207S336.5,9,510.3,9
S861.5,111.7,861.5,198.8z"/>
<linearGradient id="XMLID_17_" gradientUnits="userSpaceOnUse" x1="503.3253" y1="-583.7885" x2="503.3253" y2="-376.4571" gradientTransform="matrix(-1 0 0 -1 1017 456.5313)">
<stop offset="5.263158e-03" style="stop-color:#9E3A1D;stop-opacity:0.4"/>
<stop offset="1" style="stop-color:#9E3A1D;stop-opacity:0"/>
</linearGradient>
<path id="XMLID_61_" class="st5" d="M149.4,825.2c0-87.2,170.7,23.4,344.5,23.4s384.1-118.2,384.1-31S674.4,1015,500.7,1015
S149.4,912.3,149.4,825.2z"/>
<linearGradient id="XMLID_18_" gradientUnits="userSpaceOnUse" x1="506.1886" y1="-38.7551" x2="506.1886" y2="169.0294" gradientTransform="matrix(4.489700e-11 1 -1 4.489700e-11 1026.6101 -2.3899)">
<stop offset="5.263158e-03" style="stop-color:#9E3A1D;stop-opacity:0.4"/>
<stop offset="1" style="stop-color:#9E3A1D;stop-opacity:0"/>
</linearGradient>
<path id="XMLID_63_" class="st6" d="M826.8,859.9c-87.2,0,23.4-170.7,23.4-344.5s-118.8-367.7-31.6-367.7s198,187.1,198,360.9
S914,859.9,826.8,859.9z"/>
<linearGradient id="XMLID_19_" gradientUnits="userSpaceOnUse" x1="502.6101" y1="-660.7308" x2="502.6101" y2="-450.373" gradientTransform="matrix(-4.489700e-11 -1 1 -4.489700e-11 570.0789 1014.6101)">
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.4"/>
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
</linearGradient>
<path id="XMLID_62_" class="st7" d="M200.5,147.7c87.2,0-23.4,170.7-23.4,344.5s118.2,384.1,31,384.1S11.3,701,7.4,512
C3.9,338.3,113.3,147.7,200.5,147.7z"/>
<g id="XMLID_39_" class="st10">
<defs>
<ellipse id="XMLID_36_" transform="matrix(0.8019 -0.5974 0.5974 0.8019 -204.724 406.2339)" class="st10" cx="510.3" cy="511.9" rx="502.8" ry="502.8"/>
</defs>
<clipPath id="XMLID_20_">
<use xlink:href="#XMLID_36_" style="overflow:visible;"/>
</clipPath>
</g>
<g id="XMLID_41_">
<linearGradient id="XMLID_21_" gradientUnits="userSpaceOnUse" x1="64.4989" y1="234.8705" x2="401.1502" y2="480.7042">
<stop offset="0" style="stop-color:#F4592D"/>
<stop offset="1" style="stop-color:#FFD900"/>
</linearGradient>
<path id="XMLID_7_" class="st9" d="M20.9,474.4c15.5-101.8,66.6-201.8,147-275.9c45.6-41.1,90.9-61.6,137.3-67.8
c81.7-8,131.9,63.4,129.6,168.8c0.2,3.6,1.9,4.5,3.6,5.4c8.1-23.2,13-44.6,13.2-65.2c-0.1-117-75.3-177.7-169-155.4
c39.7-9.8,77-7.1,110.5,10.7c27.1,17.9,46.5,42,61.9,77.7c10.4,33,14.3,66.1,10.8,108.9c-8.1,92-54.7,187.5-124.6,250
c-28.9,25.9-56.8,42-84.7,58l0.2,3.6c22.7,1.8,44.7-7.1,69.5-21.4c89.7-55.3,157.3-174.1,178.5-286.6
c-9.2,53.6-26.5,106.2-55,159.8c-25.9,44.6-52.4,82.1-90.3,117c-59.4,50.9-120.5,76.8-176.3,64.3C122.4,611,90.4,545.8,92.6,461
c-0.2-3.6-0.5-7.1-3.6-5.4c-1.4,2.7-4.3,8-5.7,10.7C69,517.3,77.1,562.8,91,601.2c25.5,65.2,86.8,87.5,153.7,75
c-39.7,9.8-77,7.1-110.5-10.7c-27.1-17.9-49.6-40.2-63.6-78.6c-14.7-49.1-19.9-100-0.2-165.2c24.9-83,69.1-166.1,136.9-212.5
c20-13.4,38.7-24.1,60.4-36.6c3.1-1.8,2.9-5.4,1.2-6.2C159.4,170,46.5,333.3,20.9,474.4L20.9,474.4z"/>
<linearGradient id="XMLID_22_" gradientUnits="userSpaceOnUse" x1="438.1351" y1="490.0881" x2="196.6566" y2="356.1772">
<stop offset="0" style="stop-color:#F64626"/>
<stop offset="1" style="stop-color:#FFD900"/>
</linearGradient>
<path id="XMLID_40_" style="fill:url(#XMLID_22_);" d="M86.7,460.9C98,387.2,135,314.8,193.2,261.1c33-29.7,65.9-44.6,99.4-49.1
c59.2-5.8,95.5,45.9,93.8,122.2c0.2,2.6,1.4,3.2,2.6,3.9c5.8-16.8,9.4-32.3,9.6-47.2c-0.1-84.7-54.6-128.7-122.4-112.5
c28.7-7.1,55.7-5.2,80,7.8c19.6,12.9,33.7,30.4,44.8,56.3c7.5,23.9,10.3,47.9,7.8,78.9c-5.9,66.6-39.6,135.8-90.2,181.1
c-20.9,18.8-41.1,30.4-61.4,42l0.2,2.6c16.5,1.3,32.4-5.2,50.3-15.5c65-40.1,113.9-126.1,129.3-207.6
c-6.6,38.8-19.2,77-39.9,115.8c-18.8,32.3-37.9,59.5-65.4,84.7c-43,36.9-87.3,55.6-127.7,46.6c-44-11-67.2-58.2-65.6-119.6
c-0.2-2.6-0.4-5.2-2.6-3.9c-1,1.9-3.1,5.8-4.1,7.8c-10.3,36.9-4.5,69.8,5.6,97.7c18.5,47.2,62.9,63.4,111.3,54.3
c-28.7,7.1-55.7,5.2-80-7.8c-19.6-12.9-35.9-29.1-46-56.9c-10.7-35.6-14.4-72.4-0.2-119.6c18-60.1,50.1-120.3,99.1-153.9
c14.5-9.7,28-17.5,43.7-26.5c2.2-1.3,2.1-3.9,0.9-4.5C187.1,240.4,105.3,358.8,86.7,460.9L86.7,460.9z"/>
</g>
<g id="XMLID_11_">
<linearGradient id="XMLID_23_" gradientUnits="userSpaceOnUse" x1="-316.9261" y1="9.5862" x2="-92.0354" y2="173.8087" gradientTransform="matrix(-0.9998 -2.148304e-02 2.148304e-02 -0.9998 625.9278 811.8477)">
<stop offset="0" style="stop-color:#F4592D"/>
<stop offset="1" style="stop-color:#FFD900"/>
</linearGradient>
<path id="XMLID_13_" class="st11" d="M975.6,649.7c-11.8,67.8-47.4,133.8-102.1,182.1c-31,26.8-61.6,39.8-92.7,43.3
c-54.7,4.2-87.2-44.2-84.1-114.6c-0.1-2.4-1.2-3-2.3-3.6c-5.7,15.4-9.3,29.6-9.8,43.3c-1.6,78.1,47.8,119.8,110.6,106.2
c-26.6,6-51.5,3.7-73.6-8.7c-17.8-12.3-30.5-28.7-40.2-52.8c-6.5-22.2-8.6-44.3-5.7-72.9c6.7-61.3,39.2-124.4,86.8-165.2
c19.6-16.9,38.5-27.2,57.4-37.5l-0.1-2.4c-15.1-1.5-29.9,4.1-46.7,13.3c-60.7,35.7-107.6,114-123.3,188.8
c6.9-35.6,19.2-70.6,39-105.9c18-29.4,36.2-54.1,62-76.8c40.4-33.1,81.6-49.5,118.6-40.4c40.4,11,60.8,55,58.1,111.6
c0.1,2.4,0.2,4.8,2.3,3.6c1-1.8,3-5.3,4-7.1c10.2-33.8,5.5-64.3-3.3-90.2c-16.1-43.9-56.7-59.7-101.6-52.3
c26.6-6,51.5-3.7,73.6,8.7c17.8,12.3,32.6,27.5,41.3,53.4c9.1,33,11.8,67.1-2.2,110.3c-17.8,55.1-48.5,109.9-94.5,140
c-13.6,8.7-26.2,15.5-40.9,23.6c-2.1,1.1-2,3.5-0.9,4.2C878.7,851.1,956.4,743.6,975.6,649.7L975.6,649.7z"/>
<linearGradient id="XMLID_24_" gradientUnits="userSpaceOnUse" x1="-52.9013" y1="188.0779" x2="-214.2144" y2="98.6225" gradientTransform="matrix(-0.9998 -2.148304e-02 2.148304e-02 -0.9998 625.9278 811.8477)">
<stop offset="0" style="stop-color:#F42C2D"/>
<stop offset="1" style="stop-color:#FFD900"/>
</linearGradient>
<path id="XMLID_12_" class="st12" d="M931.4,657.8c-8.6,49.1-34.3,96.9-74,131.9c-22.5,19.4-44.6,28.9-67.1,31.4
c-39.6,3-63.2-32-60.9-83c-0.1-1.7-0.9-2.2-1.7-2.6c-4.1,11.1-6.8,21.5-7.1,31.4c-1.2,56.6,34.6,86.7,80.1,76.9
c-19.3,4.3-37.3,2.7-53.3-6.3c-12.9-8.9-22.1-20.8-29.1-38.2c-4.7-16.1-6.2-32.1-4.1-52.8c4.9-44.4,28.4-90.1,62.9-119.6
c14.2-12.2,27.9-19.7,41.6-27.2l-0.1-1.7c-11-1.1-21.7,3-33.8,9.6c-44,25.8-77.9,82.6-89.3,136.8c5-25.8,13.9-51.1,28.3-76.7
c13-21.3,26.2-39.2,44.9-55.6c29.3-24,59.1-35.9,85.9-29.3c29.2,8,44,39.8,42.1,80.8c0.1,1.7,0.2,3.5,1.7,2.6
c0.7-1.3,2.2-3.8,2.9-5.1c7.4-24.5,4-46.6-2.4-65.3c-11.7-31.8-41.1-43.2-73.6-37.9c19.3-4.3,37.3-2.7,53.3,6.3
c12.9,8.9,23.6,20,29.9,38.7c6.6,23.9,8.6,48.6-1.6,79.9c-12.9,39.9-35.2,79.6-68.4,101.4c-9.8,6.3-19,11.3-29.6,17.1
c-1.5,0.8-1.4,2.6-0.6,3C861.2,803.6,917.5,725.7,931.4,657.8L931.4,657.8z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.1 KiB

22
src/renderer/index.ejs Normal file
View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title></title>
<script>
global = globalThis
</script>
<% if (htmlWebpackPlugin.options.nodeModules) { %>
<script>
require('module').globalPaths.push(
`<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>`
)
</script>
<% } %>
</head>
<body>
<div id="app"></div>
<!-- webpack builds are automatically injected -->
</body>
</html>

98
src/renderer/index.ts Normal file
View File

@ -0,0 +1,98 @@
'use strict';
import { ipcRenderer } from 'electron';
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import { VueMaskDirective } from 'v-mask';
import * as FloatingVue from 'floating-vue';
import '@mdi/font/css/materialdesignicons.css';
import 'floating-vue/dist/style.css';
import 'leaflet/dist/leaflet.css';
import '@/scss/main.scss';
import { useApplicationStore } from '@/stores/application';
import { useSettingsStore } from '@/stores/settings';
import { useNotificationsStore } from '@/stores/notifications';
import { useConsoleStore } from '@/stores/console';
import App from '@/App.vue';
import { i18n } from '@/i18n';
// https://github.com/probil/v-mask/issues/498#issuecomment-827027834
const vMaskV2 = VueMaskDirective;
const vMaskV3 = {
beforeMount: vMaskV2.bind,
updated: vMaskV2.componentUpdated,
unmounted: vMaskV2.unbind
};
createApp(App)
.directive('mask', vMaskV3)
.use(createPinia())
.use(i18n)
.use(FloatingVue)
.mount('#app');
const { locale } = useSettingsStore();
i18n.global.locale = locale;
// IPC exceptions
ipcRenderer.on('unhandled-exception', (event, error) => {
useNotificationsStore().addNotification({ status: 'error', message: error.message });
});
// IPC query logs
ipcRenderer.on('query-log', (event, logRecord) => {
useConsoleStore().putLog(logRecord);
});
ipcRenderer.on('toggle-console', () => {
useConsoleStore().toggleConsole();
});
// IPC app updates
ipcRenderer.on('checking-for-update', () => {
useApplicationStore().updateStatus = 'checking';
});
ipcRenderer.on('update-available', () => {
useApplicationStore().updateStatus = 'available';
});
ipcRenderer.on('update-not-available', () => {
useApplicationStore().updateStatus = 'noupdate';
});
ipcRenderer.on('check-failed', () => {
useApplicationStore().updateStatus = 'nocheck';
});
ipcRenderer.on('no-auto-update', () => {
useApplicationStore().updateStatus = 'disabled';
});
ipcRenderer.on('download-progress', (event, data) => {
useApplicationStore().updateStatus = 'downloading';
useApplicationStore().downloadProgress = data.percent;
});
ipcRenderer.on('update-downloaded', () => {
useApplicationStore().updateStatus = 'downloaded';
});
ipcRenderer.on('link-to-download', () => {
useApplicationStore().updateStatus = 'link';
});
// IPC shortcuts
ipcRenderer.on('toggle-preferences', () => {
useApplicationStore().showSettingModal('general');
});
ipcRenderer.on('open-updates-preferences', () => {
useApplicationStore().showSettingModal('update');
ipcRenderer.send('check-for-updates');
});
ipcRenderer.on('update-shortcuts', (event, shortcuts) => {
useSettingsStore().updateShortcuts(shortcuts);
});

View File

@ -0,0 +1,50 @@
import { ConnectionParams } from 'common/interfaces/antares';
import * as formatter from 'pg-connection-string'; // parses a connection string
const formatHost = (host: string) => {
const results = host === 'localhost' ? '127.0.0.1' : host;
return results;
};
const checkForSSl = (conn: string) => {
return conn.includes('ssl=true');
};
const connStringConstruct = (args: ConnectionParams & { pgConnString?: string }): ConnectionParams => {
if (!args.pgConnString)
return args;
if (typeof args.pgConnString !== 'string')
return args;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const stringArgs: any = formatter.parse(args.pgConnString);
const client = args.client || 'pg';
args.client = client;
args.host = formatHost(stringArgs.host);
args.database = stringArgs.database;
args.port = stringArgs.port || '5432';
args.user = stringArgs.user;
args.password = stringArgs.password;
// ssh
args.ssh = stringArgs.ssh || args.ssh;
args.sshHost = stringArgs.sshHost;
args.sshUser = stringArgs.sshUser;
args.sshPass = stringArgs.sshPass;
args.sshKey = stringArgs.sshKey;
args.sshPort = stringArgs.sshPort;
// ssl mode
args.ssl = checkForSSl(args.pgConnString);
args.cert = stringArgs.sslcert;
args.key = stringArgs.sslkey;
args.ca = stringArgs.sslrootcert;
args.ciphers = stringArgs.ciphers;
return args;
};
export default connStringConstruct;

View File

@ -0,0 +1,63 @@
import { ClientCode } from 'common/interfaces/antares';
import { jsonToSqlInsert } from 'common/libs/sqlUtils';
export const exportRows = (args: {
type: 'csv' | 'json'| 'sql';
content: object[];
table: string;
client?: ClientCode;
fields?: {
[key: string]: {type: string; datePrecision: number};
};
}) => {
let mime;
let content;
switch (args.type) {
case 'csv': {
mime = 'text/csv';
const csv = [];
if (args.content.length)
csv.push(Object.keys(args.content[0]).join(';'));
for (const row of args.content)
csv.push(Object.values(row).map(col => typeof col === 'string' ? `"${col}"` : col).join(';'));
content = csv.join('\n');
break;
}
case 'sql': {
mime = 'text/sql';
const sql = [];
for (const row of args.content) {
sql.push(jsonToSqlInsert({
json: row,
client:
args.client,
fields: args.fields,
table: args.table
}));
}
content = sql.join('\n');
break;
}
case 'json':
mime = 'application/json';
content = JSON.stringify(args.content, null, 3);
break;
default:
break;
}
const file = new Blob([content], { type: mime });
const downloadLink = document.createElement('a');
downloadLink.download = `${args.table}.${args.type}`;
downloadLink.href = window.URL.createObjectURL(file);
downloadLink.style.display = 'none';
document.body.appendChild(downloadLink);
downloadLink.click();
downloadLink.remove();
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
export const getContrast = (hexcolor: string) => {
if (!hexcolor) return '';
return (parseInt(hexcolor.replace('#', ''), 16) > 0xffffff / 2) ? 'dark' : 'light';
};

View File

@ -0,0 +1,22 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { toRaw } from 'vue';
/**
* @param {*} val
* @param {Boolean} json converts the value in JSON object (default true)
*/
export function unproxify (val: any, json = true): any {
if (json)// JSON conversion
return JSON.parse(JSON.stringify(val));
else if (Array.isArray(val))// If array
return toRaw(val);
else if (typeof val === 'object') { // If object
const result: any = {};
for (const key in val)
result[key] = toRaw(val[key]);
return result;
}
else
return toRaw(val);
}

View File

@ -0,0 +1,9 @@
.mdi {
display: flex;
align-items: center;
justify-content: center;
&::before {
line-height: 1;
}
}

View File

@ -0,0 +1,82 @@
.fade-slide-down-enter-active,
.fade-slide-down-leave-active {
transition: opacity 0.15s ease, transform 0.15s ease;
opacity: 1;
transform: translateY(0);
}
.fade-slide-down-enter-from,
.fade-slide-down-leave-to {
opacity: 0;
transform: translateY(-1.8rem);
}
.slide-fade-enter-active {
transition: all 0.3s ease;
}
.slide-fade-leave-active {
transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter-from,
.slide-fade-leave-to {
transform: translateX(10px);
opacity: 0;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.jump-down-enter-active {
animation: jump-down-in 0.2s;
}
.jump-down-leave-active {
animation: jump-down-in 0.2s reverse;
}
.flip-list-move {
transition: transform 0.5s;
}
.no-move {
transition: transform 0s;
}
.pulse {
animation-name: pulse;
animation-duration: 2s;
animation-iteration-count: infinite;
}
@keyframes jump-down-in {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes pulse {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
100% {
opacity: 0;
}
}

View File

@ -0,0 +1,33 @@
/* Colors */
$body-bg: #fdfdfd;
$body-bg-dark: #1d1d1d;
$body-font-color-dark: #fff;
$bg-color-dark: #1d1d1d;
$bg-color-light-dark: #3f3f3f;
$bg-color-gray: #272727;
$bg-color-light-gray: #f1f1f1;
$light-color: #fdfdfd;
$primary-color: #e36929;
$success-color: #32b643;
$error-color: #de3b28;
$warning-color: #e0a40c;
/* Sizes */
$border-radius: 0.3rem;
$titlebar-height: 1.5rem;
$settingbar-width: 3.5rem;
$explorebar-width: 14rem;
$footer-height: 1.5rem;
@function get-excluding-size() {
@if $platform == linux {
@return $footer-height;
}
@else {
@return $footer-height + $titlebar-height;
}
}
/* stylelint-disable-next-line function-no-unknown */
$excluding-size: get-excluding-size();

425
src/renderer/scss/main.scss Normal file
View File

@ -0,0 +1,425 @@
/* stylelint-disable selector-class-pattern */
@import "~spectre.css/src/variables";
@import "variables";
@import "transitions";
@import "data-types";
@import "table-keys";
@import "fake-tables";
@import "mdi-additions";
@import "db-icons";
@import "themes/dark-theme";
@import "themes/light-theme";
@import "~spectre.css/src/spectre";
@import "~spectre.css/src/spectre-exp";
body {
user-select: none;
}
::selection,
option:hover,
option:focus,
option:active,
option:checked {
background-color: $primary-color;
color: $light-color;
}
/* Additions */
@include margin-variant(3, $unit-3);
@include margin-variant(4, $unit-4);
@include padding-variant(3, $unit-3);
@include padding-variant(4, $unit-4);
.p-vcentered {
display: flex !important;
align-items: center;
}
.c-help {
cursor: help;
}
.no-outline {
outline: none !important;
}
.no-radius {
border-radius: 0 !important;
}
.no-border {
outline: none !important;
border: none !important;
box-shadow: none !important;
}
.cut-text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.cancellable {
color: transparent !important;
min-height: 0.8rem;
position: relative;
> .mdi,
> .span {
visibility: hidden;
}
&::after {
content: "\2715";
color: $light-color;
font-weight: 700;
top: 36%;
display: block;
height: 0.8rem;
left: 50%;
margin-left: -0.4rem;
margin-top: -0.4rem;
opacity: 1;
padding: 0;
position: absolute;
width: 0.8rem;
z-index: 1;
}
}
.workspace-query-results {
overflow: auto;
white-space: nowrap;
.table {
width: auto;
border-collapse: separate;
.th {
position: sticky;
top: 0;
border: 2px solid;
border-left: none;
border-bottom-width: 2px;
padding: 0;
font-weight: 700;
font-size: 0.7rem;
z-index: 1;
> div {
padding: 0.1rem 0.2rem;
/* stylelint-disable-next-line value-no-vendor-prefix */
min-width: -webkit-fill-available;
}
}
.td {
border-right: 2px solid;
border-bottom: 2px solid;
padding: 0 0.2rem;
text-overflow: ellipsis;
max-width: 200px;
white-space: nowrap;
overflow: hidden;
font-size: 0.7rem;
position: relative;
&:focus {
outline: none;
}
}
}
}
.workspace-tabs {
align-content: baseline;
.workspace-query-runner {
.workspace-query-runner-footer {
display: flex;
justify-content: space-between;
padding: 0.3rem 0.6rem 0.4rem;
align-items: center;
.workspace-query-buttons {
display: flex;
.btn {
display: flex;
align-self: center;
margin-right: 0.4rem;
}
}
.workspace-query-info {
display: flex;
overflow: hidden;
white-space: nowrap;
> div + div {
padding-left: 0.6rem;
}
}
}
}
}
.process-row .td:last-child {
width: 100%;
}
/* Scrollbars */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
/* Animations */
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
.rotate {
animation: rotation 0.8s infinite linear;
}
/* Override */
.modal {
.modal-container,
.modal-sm .modal-container {
padding: 0;
border-radius: $border-radius;
.modal-header {
padding: 0.4rem 0.8rem;
text-transform: uppercase;
display: flex;
justify-content: space-between;
align-items: center;
border-radius: $border-radius $border-radius 0 0;
.modal-title {
overflow: hidden;
}
}
}
.modal-overlay {
background: rgb(255 255 255 / 10%);
box-shadow: 0 8px 32px 0 rgb(31 38 135 / 37%);
}
}
#wrapper:not(.no-blur) {
.modal-overlay {
backdrop-filter: blur(4px);
}
}
.tab {
.tab-item {
position: relative;
display: flex;
justify-content: center;
.tab-link {
min-width: 0;
transition: color 0.2s;
}
&.active {
.tab-link {
border-color: transparent;
}
&::after {
width: 100%;
}
}
&::after {
content: "";
height: 2px;
width: 0;
transition: width 0.2s;
background-color: $primary-color;
position: absolute;
bottom: 0;
}
.btn-clear {
margin-top: -0.1rem;
font-size: 0.6rem;
}
}
}
.panel {
border: none;
}
.tooltip:hover {
&::after {
opacity: 1;
}
}
.badge {
&[data-badge],
&:not([data-badge]) {
&::after {
box-shadow: none;
}
}
&.badge-connected::after {
background: $success-color;
}
&.badge-connecting::after {
background: $warning-color;
animation-name: pulse;
animation-duration: 2s;
animation-iteration-count: infinite;
}
&.badge-failed::after {
background: $error-color;
}
}
.form-select {
cursor: pointer;
&.small-select {
height: 21px;
font-size: 0.7rem;
padding: 1px 0.4rem 0;
}
&.select {
&.select--open {
border-color: $primary-color !important;
@include control-shadow();
}
}
}
.select__list {
margin: 0;
li {
margin: 0;
padding: 0.3rem 0.8rem;
.select-sm &,
.small-select & {
padding: 0.05rem 0.3rem;
}
}
}
.select__list-wrapper {
z-index: 401 !important;
border: 1px solid transparent;
border-radius: $border-radius;
box-shadow: 0 8px 17px 0 rgb(0 0 0 / 20%), 0 6px 20px 0 rgb(0 0 0 / 19%);
}
.select__option--selected {
background: rgba($primary-color, 0.25);
}
.select__option--highlight {
background: $primary-color;
}
.form-input[type="file"] {
overflow: hidden;
}
.input-group .input-group-addon {
z-index: 1;
}
.menu {
font-size: 0.7rem;
.menu-item {
+ .menu-item {
margin-top: 0;
}
}
}
.accordion-body {
max-height: 5000rem !important;
}
.btn {
&:focus {
box-shadow: 0 0 3px 1px rgba($primary-color, 90%);
}
&.btn-success:focus {
border-color: $primary-color;
box-shadow: 0 0 3px 1px rgba($primary-color, 90%);
}
}
.btn-group {
flex-wrap: nowrap;
}
.btn.loading {
> .mdi,
> span {
visibility: hidden;
}
}
.divider {
margin: 0.15rem 0.3rem;
}
.table-dropdown {
.menu {
min-width: 100%;
padding: 0;
.menu-item {
padding: 0;
> a {
margin: 0.2rem;
padding: 0.1rem 0.3rem;
&:hover {
color: inherit;
}
}
}
}
}
/* Ace Editor */
.ace_editor {
&.ace_autocomplete {
border-radius: $border-radius;
.ace_marker-layer {
.ace_active-line,
.ace_line-hover {
border-radius: $border-radius;
}
}
}
}

View File

@ -0,0 +1,484 @@
/* stylelint-disable selector-class-pattern */
/* stylelint-disable function-no-unknown */
.theme-dark {
color: $body-font-color-dark;
background: $body-bg-dark;
::-webkit-scrollbar-track {
background: $bg-color-light-dark;
}
::-webkit-scrollbar-thumb {
background: rgba($color: #fff, $alpha: 50%);
&:hover {
background: rgba($color: #fff, $alpha: 100%);
}
}
:disabled {
.file-uploader {
background-color: #151515;
}
}
option,
optgroup {
background-color: $bg-color-gray;
}
/* Override Spectre.css */
.menu {
background: $bg-color-light-dark;
.menu-item a {
&:hover {
color: $primary-color;
background: $bg-color-gray;
}
}
}
.btn {
&.btn-link {
color: rgba($body-font-color-dark, 0.8);
&:hover {
color: $body-font-color-dark;
}
}
&.btn-gray {
color: #fff;
background: $bg-color-gray;
&:hover {
background: $bg-color-dark;
}
}
&.btn-dark {
color: #fff;
background: $bg-color-light-dark;
border-color: $bg-color-light-dark;
&:hover {
background: $bg-color-gray;
}
&.active {
background-color: $primary-color;
}
}
&.btn-clear {
&:hover {
background: rgba($light-color, 20%);
}
}
}
.modal {
.modal-overlay,
&.active .modal-overlay {
background: rgb(255 255 255 / 15%);
}
.modal-container,
.modal-sm .modal-container {
box-shadow: 0 0 1px 0 #000;
background: $bg-color-dark;
.modal-header {
background: $bg-color-gray;
color: #fff;
}
}
}
.tab {
border-color: #272727;
}
.form-select,
.form-input,
.form-select:not([multiple], [size]),
.form-checkbox .form-icon,
.form-radio .form-icon {
border-color: $bg-color-light-dark;
background-color: $bg-color-gray;
color: $body-font-color-dark;
}
.form-select:not([multiple], [size]) {
background-color: $bg-color-gray !important;
}
.form-input.is-error,
.form-select.is-error {
background-color: $bg-color-gray;
}
.form-input:not(:placeholder-shown):invalid:focus {
background: $bg-color-gray;
}
.form-select:not([multiple], [size]):focus {
border-color: $primary-color;
}
.select {
&__list-wrapper {
border-color: $bg-color-gray;
background-color: $bg-color-light-dark;
}
&__group {
background: rgba($bg-color-gray, 0.65);
color: rgba($bg-color-light-gray, 0.7);
}
}
.form-input[readonly] {
background-color: $bg-color-dark;
cursor: default;
}
.input-group .input-group-addon {
border-color: #3f3f3f;
background: $bg-color-dark;
}
.empty {
color: $body-font-color-dark;
background: transparent;
}
.divider {
border-top: 0.05rem solid rgba($body-font-color-dark, 0.1);
}
.form-switch .form-icon::before {
background: $bg-color-light-dark;
}
code {
background-color: #111;
border: 1px solid #444;
color: rgba($body-font-color-dark, 0.7);
}
/* Antares */
.workspace {
.workspace-explorebar {
background: $bg-color-gray;
box-shadow: 0 0 1px 0 #000;
.workspace-explorebar-database {
.database-name {
background: $bg-color-gray;
}
.database-name,
.misc-name {
&:hover {
color: $body-font-color-dark;
background: $bg-color-light-dark;
}
}
a.table-name {
&:hover {
color: inherit;
background: inherit;
}
}
.menu-item {
&:hover,
&.selected {
color: $body-font-color-dark;
background: rgba($color: #fff, $alpha: 5%);
}
}
}
}
.workspace-tabs {
.tab-block {
background: $bg-color-light-dark;
.tab-item {
background: $bg-color-light-dark;
> a {
color: $body-font-color-dark;
}
&.tools-dropdown {
background-color: $bg-color-light-dark;
}
}
.workspace-query-runner .workspace-query-runner-footer .workspace-query-buttons .btn {
color: $body-font-color-dark;
}
}
}
}
.workspace-query-results {
.table {
.th {
border-color: darken($bg-color-light-gray, 80%);
background-color: $body-bg-dark;
}
.tr {
background-color: darken($bg-color-light-gray, 80%);
.td:first-child {
border-left: 2px solid $body-bg-dark;
}
.td {
border-color: $body-bg-dark;
&:focus,
&.selected {
box-shadow: inset 0 0 0 2px darken($body-font-color-dark, 40%);
background-color: rgba($color: #000, $alpha: 30%);
}
.editable-field {
box-shadow: inset 0 0 0 2px darken($body-font-color-dark, 40%);
background-color: rgba($color: #000, $alpha: 30%);
}
}
}
}
}
.connection-panel {
.panel {
background: rgba($bg-color-light-dark, 50%);
}
}
.connection-block {
&:hover {
background: $bg-color-light-dark;
}
}
.bg-checkered {
background-image:
linear-gradient(to right, rgb(192 192 192 / 75%), rgb(192 192 192 / 75%)),
linear-gradient(to right, black 50%, white 50%),
linear-gradient(to bottom, black 50%, white 50%);
background-blend-mode: normal, difference, normal;
background-size: 2em 2em;
}
.context {
color: $body-font-color-dark;
.context-container {
box-shadow: 0 0 2px 0 #000;
background: #1d1d1d;
.context-element {
.context-submenu {
background: #1d1d1d;
box-shadow: 0 0 2px 0 #000;
}
&:hover {
background: rgba($light-color, 15%);
}
}
}
}
.editor-wrapper {
border-bottom: 1px solid #444;
}
.file-uploader {
border: 0.05rem solid $bg-color-light-dark;
background-color: $bg-color-gray;
.file-uploader-message {
border-right: 0.05rem solid $bg-color-light-dark;
background-color: $bg-color-dark;
}
}
.query-console {
border-top: 1px solid #444;
background-color: $bg-color-dark;
.query-console-log {
&:hover,
&:focus {
background: $bg-color-gray;
}
}
}
.tile {
transition: background 0.2s;
&:focus {
background: rgba($bg-color-light-dark, 60%);
}
&:hover {
background: $bg-color-light-dark;
}
&.selected-element {
background: $bg-color-light-dark;
}
}
.editor-col {
border-left: 0.05rem solid rgba($bg-color-light-dark, 60%);
}
.table {
.td,
.th {
border-bottom: $border-width solid $border-color;
}
&,
&.table-striped {
.tbody {
.tr {
&.selected {
background: #333 !important;
}
&.active {
background: $bg-color-dark;
}
}
}
}
&.table-hover {
.tbody {
.tr {
&:hover {
background: #151515;
}
}
}
}
&.table-striped {
.tbody {
.tr:nth-of-type(odd) {
background: $bg-color;
}
}
}
}
#titlebar {
background: $bg-color-light-dark;
box-shadow: 0 0 1px 0 #000;
.titlebar-elements {
.titlebar-element {
&:hover {
opacity: 1;
background: rgba($color: #fff, $alpha: 10%);
}
&.close-button:hover {
background: red;
}
}
}
}
#settingbar {
width: $settingbar-width;
height: calc(100vh - #{$excluding-size});
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
background: $bg-color-light-dark;
padding: 0;
box-shadow: 0 0 1px 0 #000;
z-index: 9;
.settingbar-top-elements {
overflow-x: hidden;
overflow-y: overlay;
max-height: calc((100vh - 3.5rem) - #{$excluding-size});
&::-webkit-scrollbar {
width: 3px;
}
}
.settingbar-bottom-elements {
background: $bg-color-light-dark;
}
.settingbar-elements {
list-style: none;
text-align: center;
width: $settingbar-width;
padding: 0;
margin: 0;
.settingbar-element {
.settingbar-element-icon {
&.badge-update::after {
background: $primary-color;
}
}
}
}
}
.ex-tooltip {
.ex-tooltip-content {
background: rgb(48 55 66 / 95%);
color: #fff;
}
}
#footer {
background: $primary-color;
box-shadow: 0 0 1px 0 #000;
.footer-elements {
.footer-element {
&.footer-link {
&:hover {
background: rgba($color: #fff, $alpha: 10%);
}
}
}
}
}
}
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line {
background-color: #c9561a99;
}
.ace_dark.ace_editor.ace_autocomplete .ace_marker-layer .ace_line-hover {
background-color: #c9571a33;
border: none;
}
.ace_dark.ace_editor.ace_autocomplete .ace_completion-highlight {
color: #e0d00c;
}

View File

@ -0,0 +1,373 @@
/* stylelint-disable function-no-unknown */
.theme-light {
::-webkit-scrollbar-track {
background: #fff;
}
::-webkit-scrollbar-thumb {
background: rgba($color: $bg-color-light-dark, $alpha: 50%);
&:hover {
background: rgba($color: $bg-color-light-dark, $alpha: 100%);
}
}
.form-input:disabled,
.form-input.disabled,
.form-select:disabled,
.form-select.disabled {
background: #ababab;
}
.select {
&__list-wrapper {
border: #bcc3ce;
background-color: $body-bg;
}
&__group {
background: $bg-color-light-gray;
color: $unknown-color;
}
&__option--highlight {
color: $light-color;
}
}
.menu {
.menu-item a {
&:hover {
color: $body-font-color;
background: rgba($color: #000, $alpha: 10%);
}
}
}
.btn {
&.btn-link {
color: rgba($body-font-color, 0.8);
&:hover {
color: $body-font-color;
}
}
&.btn-gray {
color: #fff;
background: $bg-color-gray;
&:hover {
background: $bg-color-dark;
}
}
&.btn-dark {
color: #fff;
background: lighten($bg-color-light-dark, 20%);
border-color: lighten($bg-color-light-dark, 20%);
&:hover {
background: $bg-color-gray;
}
&.active {
background-color: $primary-color;
}
}
}
.modal {
color: $body-font-color;
&:target .modal-overlay,
&.active .modal-overlay {
background: rgba($bg-color-dark, 0.75);
}
.modal-container .modal-header {
background: $bg-color-light-dark;
color: #fff;
}
}
.empty {
background: transparent;
}
.divider {
border-top: 0.05rem solid rgba($body-font-color-dark, 0.1);
}
.tile {
transition: background 0.2s;
&:focus {
background: rgba($bg-color-light-gray, 70%);
}
&:hover {
background: $bg-color-light-gray;
}
&.selected-element {
background: $bg-color-light-gray;
}
}
.editor-col {
border-left: 0.05rem solid darken($bg-color-light-gray, 15%);
}
.file-uploader {
border: 0.05rem solid $border-color-dark;
background-color: $bg-color-light;
.file-uploader-message {
border-right: 0.05rem solid $border-color-dark;
background-color: $bg-color-light;
}
}
.query-console {
border-top: 1px solid darken($bg-color-light-gray, 15%);
background-color: $bg-color-light;
.query-console-log {
&:hover,
&:focus {
background: $bg-color-light-gray;
}
}
}
#titlebar {
background: $bg-color-light;
box-shadow: 0 0 1px 0 #000;
.titlebar-elements {
.titlebar-element {
&:hover {
opacity: 1;
background: rgba($color: rgb(172 172 172), $alpha: 30%);
}
&.close-button:hover {
background: red;
}
}
}
}
#settingbar {
width: $settingbar-width;
height: calc(100vh - #{$excluding-size});
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
background: $bg-color-light-dark;
padding: 0;
box-shadow: 0 0 1px 0 #000;
z-index: 9;
.settingbar-top-elements {
overflow-x: hidden;
overflow-y: overlay;
max-height: calc((100vh - 3.5rem) - #{$excluding-size});
&::-webkit-scrollbar {
width: 3px;
}
}
.settingbar-bottom-elements {
background: $bg-color-light-dark;
}
.settingbar-elements {
list-style: none;
text-align: center;
width: $settingbar-width;
padding: 0;
margin: 0;
.settingbar-element {
.settingbar-element-icon {
&.badge-update::after {
background: $primary-color;
}
}
}
}
}
.ex-tooltip {
.ex-tooltip-content {
background: rgb(48 55 66 / 95%);
color: #fff;
}
}
code {
background-color: #eee;
border: 1px solid #ddd;
}
.workspace {
.workspace-explorebar {
background: $bg-color-light-gray;
box-shadow: 0 0 1px 0 #000;
.workspace-explorebar-database {
.database-name {
background: $bg-color-light-gray;
}
.menu-item {
&:hover,
&.selected {
background: rgba($color: #000, $alpha: 5%);
}
}
.table-size {
opacity: 0.4;
&:hover {
opacity: 1;
}
}
}
}
.workspace-tabs {
.tab-block {
.tab-item {
&.tools-dropdown {
background-color: $body-bg;
}
}
}
}
}
.workspace-query-results {
.table {
.th {
background: $body-bg;
border-color: lighten($bg-color-light-gray, 2%);
}
.tr {
background-color: lighten($bg-color-light-gray, 2%);
.td:first-child {
border-left: 2px solid $body-bg;
}
.td {
border-color: $body-bg;
&:focus,
&.selected {
box-shadow: inset 0 0 0 2px lighten($body-font-color, 10%);
background-color: $body-font-color-dark;
}
.editable-field {
box-shadow: inset 0 0 0 2px lighten($body-font-color, 10%);
background-color: $body-font-color-dark;
}
}
}
}
}
.connection-panel {
.panel {
background: rgba($bg-color-light-gray, 100%);
}
}
.connection-block {
&:hover {
background: $bg-color-light-gray;
}
}
.context {
color: $body-font-color-dark;
.context-container {
box-shadow: 0 0 2px 0 #000;
background: #1d1d1d;
.context-element {
.context-submenu {
background: #1d1d1d;
box-shadow: 0 0 2px 0 #000;
}
&:hover {
background: rgba($light-color, 15%);
}
}
}
}
.table {
.td,
.th {
border-bottom: $border-width solid $border-color;
}
&,
&.table-striped {
.tbody {
.tr {
&.selected {
background: rgba($bg-color-gray, 0.2) !important;
}
&.active {
background: $bg-color;
}
}
}
}
&.table-hover {
.tbody {
.tr {
&:hover {
background: $bg-color-light-gray;
}
}
}
}
&.table-striped {
.tbody {
.tr:nth-of-type(odd) {
background: $bg-color;
}
}
}
}
#footer {
background: $primary-color;
box-shadow: 0 0 1px 0 #000;
.footer-elements {
.footer-element {
&.footer-link {
&:hover {
background: rgba($color: #fff, $alpha: 10%);
}
}
}
}
}
}

View File

@ -0,0 +1,61 @@
import { defineStore } from 'pinia';
import * as Store from 'electron-store';
import { Ace } from 'ace-builds';
const persistentStore = new Store({ name: 'settings' });
export const useApplicationStore = defineStore('application', {
state: () => ({
appName: 'Antares - SQL Client',
appVersion: process.env.PACKAGE_VERSION || '0',
cachedVersion: persistentStore.get('cached_version', '0') as string,
isLoading: false,
isNewModal: false,
isSettingModal: false,
isScratchpad: false,
selectedSettingTab: 'general',
selectedConection: {},
updateStatus: 'noupdate', // 'noupdate' | 'available' | 'checking' | 'nocheck' | 'downloading' | 'downloaded' | 'disabled'
downloadProgress: 0,
baseCompleter: [] as Ace.Completer[] // Needed to reset ace editor, due global-only ace completer
}),
getters: {
getBaseCompleter: state => state.baseCompleter,
getSelectedConnection: state => state.selectedConection,
getDownloadProgress: state => Number(state.downloadProgress.toFixed(1))
},
actions: {
checkVersionUpdate () {
if (this.appVersion !== this.cachedVersion) {
this.showSettingModal('changelog');
this.cachedVersion = this.appVersion;
persistentStore.set('cached_version', this.cachedVersion);
}
},
setLoadingStatus (payload: boolean) {
this.isLoading = payload;
},
setBaseCompleters (payload: Ace.Completer[]) {
this.baseCompleter = payload;
},
// Modals
showNewConnModal () {
this.isNewModal = true;
},
hideNewConnModal () {
this.isNewModal = false;
},
showSettingModal (tab: string) {
this.selectedSettingTab = tab;
this.isSettingModal = true;
},
hideSettingModal () {
this.isSettingModal = false;
},
showScratchpad () {
this.isScratchpad = true;
},
hideScratchpad () {
this.isScratchpad = false;
}
}
});

View File

@ -0,0 +1,224 @@
import { defineStore } from 'pinia';
import * as Store from 'electron-store';
import * as crypto from 'crypto';
import { ConnectionParams } from 'common/interfaces/antares';
import { uidGen } from 'common/libs/uidGen';
const key = localStorage.getItem('key');
export interface SidebarElement {
isFolder: boolean;
uid: string;
client?: string;
connections?: string[];
color?: string;
name?: string;
icon?: null | string;
}
if (!key)
localStorage.setItem('key', crypto.randomBytes(16).toString('hex'));
else
localStorage.setItem('key', key);
const persistentStore = new Store({
name: 'connections',
encryptionKey: key,
clearInvalidConfig: true
});
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[]
}),
getters: {
getConnectionByUid: state => (uid:string) => state.connections.find(connection => connection.uid === uid),
getConnectionName: state => (uid: string) => {
const connection = state.connections.find(connection => connection.uid === uid);
let connectionName = '';
if (connection) {
if (connection.name)
connectionName = connection.name;
else if (connection.ask)
connectionName = `${connection.host}:${connection.port}`;
else if (connection.databasePath) {
let string = connection.databasePath.split(/[/\\]+/).pop();
if (string.length >= 30)
string = `...${string.slice(-30)}`;
connectionName = string;
}
else
connectionName = `${connection.user + '@'}${connection.host}:${connection.port}`;
}
return connectionName;
},
getConnectionOrderByUid: state => (uid:string) => state.connectionsOrder
.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))
},
actions: {
addConnection (connection: ConnectionParams) {
this.connections.push(connection);
persistentStore.set('connections', this.connections);
this.connectionsOrder.push({
isFolder: false,
uid: connection.uid,
client: connection.client,
icon: null,
name: null
});
persistentStore.set('connectionsOrder', this.connectionsOrder);
},
addFolder (params: {after: string; connections: [string, string]}) {
const index = this.connectionsOrder.findIndex((conn: SidebarElement) => conn.uid === params.after);
this.connectionsOrder.splice(index, 0, {
isFolder: true,
uid: uidGen('F'),
name: '',
color: '#E36929',
connections: params.connections
});
persistentStore.set('connectionsOrder', this.connectionsOrder);
},
addToFolder (params: {folder: string; connection: string}) {
this.connectionsOrder = this.connectionsOrder.map((conn: SidebarElement) => {
if (conn.uid === params.folder)
conn.connections.push(params.connection);
return conn;
});
persistentStore.set('connectionsOrder', this.connectionsOrder);
this.clearEmptyFolders();
},
deleteConnection (connection: SidebarElement | ConnectionParams) {
this.connectionsOrder = (this.connectionsOrder as SidebarElement[]).map(el => { // Removes connection from folders
if (el.isFolder && el.connections.includes(connection.uid))
el.connections = el.connections.filter(uid => uid !== connection.uid);
return el;
});
this.connectionsOrder = (this.connectionsOrder as SidebarElement[]).filter(el => el.uid !== connection.uid);
this.connections = (this.connections as SidebarElement[]).filter(el => el.uid !== connection.uid);
persistentStore.set('connections', this.connections);
this.clearEmptyFolders();
},
editConnection (connection: ConnectionParams) {
const editedConnections = (this.connections as ConnectionParams[]).map(conn => {
if (conn.uid === connection.uid) return connection;
return conn;
});
this.connections = editedConnections;
persistentStore.set('connections', this.connections);
const editedConnectionsOrder = (this.connectionsOrder as SidebarElement[]).map(conn => {
if (conn.uid === connection.uid) {
return {
isFolder: false,
uid: connection.uid,
client: connection.client,
icon: conn.icon,
name: conn.name
};
}
return conn;
});
this.connectionsOrder = editedConnectionsOrder;
persistentStore.set('connectionsOrder', this.connectionsOrder);
},
updateConnections (connections: ConnectionParams[]) {
this.connections = connections;
persistentStore.set('connections', this.connections);
},
initConnectionsOrder () {
this.connectionsOrder = (this.connections as ConnectionParams[]).map<SidebarElement>(conn => {
return {
isFolder: false,
uid: conn.uid,
client: conn.client,
icon: null,
name: null
};
});
persistentStore.set('connectionsOrder', this.connectionsOrder);
},
updateConnectionsOrder (connections: SidebarElement[]) {
const invalidElements = connections.reduce<{index: number; uid: string}[]>((acc, curr, i) => {
if (typeof curr === 'string')
acc.push({ index: i, uid: curr });
return acc;
}, []);
if (invalidElements.length) {
invalidElements.forEach(el => {
let connIndex = connections.findIndex(conn => conn.uid === el.uid);
const conn = connections[connIndex];
if (connIndex === -1) return;
connections.splice(el.index, 1, { // Move to new position
isFolder: false,
client: conn.client,
uid: conn.uid,
icon: conn.icon,
name: conn.name
});
connIndex = connections.findIndex((conn, i) => conn.uid === el.uid && i !== el.index);
connections.splice(connIndex, 1);// Delete old object
});
}
// Clear empty folders
const emptyFolders = connections.reduce<string[]>((acc, curr) => {
if (curr.connections && curr.connections.length === 0)
acc.push(curr.uid);
return acc;
}, []);
connections = connections.filter(el => !emptyFolders.includes(el.uid));
this.connectionsOrder = connections;
persistentStore.set('connectionsOrder', this.connectionsOrder);
},
updateConnectionOrder (element: SidebarElement) {
this.connectionsOrder = (this.connectionsOrder as SidebarElement[]).map(el => {
if (el.uid === element.uid)
el = element;
return el;
});
persistentStore.set('connectionsOrder', this.connectionsOrder);
},
updateLastConnection (uid: string) {
const cIndex = (this.lastConnections as {uid: string; time: number}[]).findIndex((c) => c.uid === uid);
if (cIndex >= 0)
this.lastConnections[cIndex].time = new Date().getTime();
else
this.lastConnections.push({ uid, time: new Date().getTime() });
persistentStore.set('lastConnections', this.lastConnections);
},
clearEmptyFolders () {
// Clear empty folders
const emptyFolders = (this.connectionsOrder as SidebarElement[]).reduce<string[]>((acc, curr) => {
if (curr.connections && curr.connections.length === 0)
acc.push(curr.uid);
return acc;
}, []);
this.connectionsOrder = (this.connectionsOrder as SidebarElement[]).filter(el => !emptyFolders.includes(el.uid));
persistentStore.set('connectionsOrder', this.connectionsOrder);
}
}
});

View File

@ -0,0 +1,58 @@
import { defineStore } from 'pinia';
import { useWorkspacesStore } from './workspaces';
const logsSize = 1000;
export interface ConsoleRecord {
cUid: string;
sql: string;
date: Date;
}
export const useConsoleStore = defineStore('console', {
state: () => ({
records: [] as ConsoleRecord[],
consolesHeight: new Map<string, number>(),
consolesOpened: new Set([])
}),
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;
}
},
actions: {
putLog (record: ConsoleRecord) {
this.records.push(record);
if (this.records.length > logsSize)
this.records = this.records.slice(0, logsSize);
},
openConsole () {
const uid = useWorkspacesStore().getSelected;
this.consolesOpened.add(uid);
this.consolesHeight.set(uid, 250);
},
closeConsole () {
const uid = useWorkspacesStore().getSelected;
this.consolesOpened.delete(uid);
this.consolesHeight.set(uid, 0);
},
resizeConsole (height: number) {
const uid = useWorkspacesStore().getSelected;
if (height < 30)
this.closeConsole();
else
this.consolesHeight.set(uid, height);
},
toggleConsole () {
const uid = useWorkspacesStore().getSelected;
if (this.consolesOpened.has(uid))
this.closeConsole();
else
this.openConsole();
}
}
});

View File

@ -0,0 +1,52 @@
import { defineStore } from 'pinia';
import * as Store from 'electron-store';
import { uidGen } from 'common/libs/uidGen';
const persistentStore = new Store({ name: 'history' });
const historySize = 1000;
export interface HistoryRecord {
uid: string;
sql: string;
date: Date;
schema?: string;
}
export const useHistoryStore = defineStore('history', {
state: () => ({
history: persistentStore.get('history', {}) as {[key: string]: HistoryRecord[]},
favorites: persistentStore.get('favorites', {})
}),
getters: {
getHistoryByWorkspace: state => (uid: string) => state.history[uid]
},
actions: {
saveHistory (args: { uid: string; query: string; schema: string; tabUid: string }) {
if (this.getHistoryByWorkspace(args.uid) &&
this.getHistoryByWorkspace(args.uid).length &&
this.getHistoryByWorkspace(args.uid)[0].sql === args.query
) return;
if (!(args.uid in this.history))
this.history[args.uid] = [];
this.history[args.uid] = [
{
uid: uidGen('H'),
sql: args.query,
date: new Date(),
schema: args.schema
},
...this.history[args.uid]
];
if (this.history[args.uid].length > historySize)
this.history[args.uid] = this.history[args.uid].slice(0, historySize);
persistentStore.set('history', this.history);
},
deleteQueryFromHistory (query: Partial<HistoryRecord> & { workspace: string}) {
this.history[query.workspace] = (this.history[query.workspace] as HistoryRecord[]).filter(q => q.uid !== query.uid);
persistentStore.set('history', this.history);
}
}
});

View File

@ -0,0 +1,23 @@
import { defineStore } from 'pinia';
import { uidGen } from 'common/libs/uidGen';
export interface Notification {
uid: string;
status: string;
message: string;
}
export const useNotificationsStore = defineStore('notifications', {
state: () => ({
notifications: [] as Notification[]
}),
actions: {
addNotification (payload: { status: string; message: string }) {
const notification: Notification = { uid: uidGen('N'), ...payload };
this.notifications.unshift(notification);
},
removeNotification (uid: string) {
this.notifications = (this.notifications as Notification[]).filter(item => item.uid !== uid);
}
}
});

View File

@ -0,0 +1,15 @@
import { defineStore } from 'pinia';
import * as Store from 'electron-store';
const persistentStore = new Store({ name: 'notes' });
export const useScratchpadStore = defineStore('scratchpad', {
state: () => ({
notes: persistentStore.get('notes', '# HOW TO SUPPORT ANTARES\n\n- [ ] Leave a star to Antares [GitHub repo](https://github.com/antares-sql/antares)\n- [ ] Send feedbacks and advices\n- [ ] Report for bugs\n- [ ] If you enjoy, share Antares with friends\n\n# ABOUT SCRATCHPAD\n\nThis is a scratchpad where you can save your **personal notes**. It supports `markdown` format, but you are free to use plain text.\nThis content is just a placeholder, feel free to clear it to make space for your notes.\n') as string
}),
actions: {
changeNotes (notes: string) {
this.notes = notes;
persistentStore.set('notes', this.notes);
}
}
});

View File

@ -0,0 +1,107 @@
import { defineStore } from 'pinia';
import { ipcRenderer } from 'electron';
import { i18n, AvailableLocale } from '@/i18n';
import * as Store from 'electron-store';
import { ShortcutRecord } from 'common/shortcuts';
const settingsStore = new Store({ name: 'settings' });
const shortcutsStore = new Store({ name: 'shortcuts' });
const isDarkTheme = window.matchMedia('(prefers-color-scheme: dark)');
const defaultAppTheme = isDarkTheme.matches ? 'dark' : 'light';
const defaultEditorTheme = isDarkTheme.matches ? 'twilight' : 'sqlserver';
export type EditorFontSize = 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge';
export type ApplicationTheme = 'light' | 'dark';
export const useSettingsStore = defineStore('settings', {
state: () => ({
locale: settingsStore.get('locale', 'en-US') as AvailableLocale,
allowPrerelease: settingsStore.get('allow_prerelease', true) as boolean,
explorebarSize: settingsStore.get('explorebar_size', null) as number,
notificationsTimeout: settingsStore.get('notifications_timeout', 5) as number,
showTableSize: settingsStore.get('show_table_size', false) as boolean,
dataTabLimit: settingsStore.get('data_tab_limit', 1000) as number,
autoComplete: settingsStore.get('auto_complete', true) as boolean,
lineWrap: settingsStore.get('line_wrap', true) as boolean,
executeSelected: settingsStore.get('execute_selected', true) as boolean,
applicationTheme: settingsStore.get('application_theme', defaultAppTheme) as ApplicationTheme,
editorTheme: settingsStore.get('editor_theme', defaultEditorTheme) as string,
editorFontSize: settingsStore.get('editor_font_size', 'medium') as EditorFontSize,
restoreTabs: settingsStore.get('restore_tabs', true) as boolean,
disableBlur: settingsStore.get('disable_blur', false) as boolean,
disableScratchpad: settingsStore.get('disable_scratchpad', false) as boolean,
shortcuts: shortcutsStore.get('shortcuts', []) as ShortcutRecord[],
defaultCopyType: settingsStore.get('default_copy_type', 'cell') as string
}),
actions: {
changeLocale (locale: AvailableLocale) {
this.locale = locale;
i18n.global.locale = locale;
settingsStore.set('locale', this.locale);
},
changePageSize (limit: number) {
this.dataTabLimit = limit;
settingsStore.set('data_tab_limit', this.dataTabLimit);
},
changeAllowPrerelease (allow: boolean) {
this.allowPrerelease = allow;
settingsStore.set('allow_prerelease', this.allowPrerelease);
},
updateNotificationsTimeout (timeout: number) {
this.notificationsTimeout = timeout;
settingsStore.set('notifications_timeout', this.notificationsTimeout);
},
changeShowTableSize (show: boolean) {
this.showTableSize = show;
settingsStore.set('show_table_size', this.showTableSize);
},
changeExplorebarSize (size: number) {
this.explorebarSize = size;
settingsStore.set('explorebar_size', this.explorebarSize);
},
changeAutoComplete (val: boolean) {
this.autoComplete = val;
settingsStore.set('auto_complete', this.autoComplete);
},
changeLineWrap (val: boolean) {
this.lineWrap = val;
settingsStore.set('line_wrap', this.lineWrap);
},
changeExecuteSelected (val: boolean) {
this.executeSelected = val;
settingsStore.set('execute_selected', this.executeSelected);
},
changeApplicationTheme (theme: string) {
this.applicationTheme = theme;
settingsStore.set('application_theme', this.applicationTheme);
ipcRenderer.send('refresh-theme-settings');
},
changeEditorTheme (theme: string) {
this.editorTheme = theme;
settingsStore.set('editor_theme', this.editorTheme);
},
changeEditorFontSize (size: EditorFontSize) {
this.editorFontSize = size;
settingsStore.set('editor_font_size', this.editorFontSize);
},
changeRestoreTabs (val: boolean) {
this.restoreTabs = val;
settingsStore.set('restore_tabs', this.restoreTabs);
},
changeDisableBlur (val: boolean) {
this.disableBlur = val;
settingsStore.set('disable_blur', this.disableBlur);
},
changeDisableScratchpad (val: boolean) {
this.disableScratchpad = val;
settingsStore.set('disable_scratchpad', this.disableScratchpad);
},
updateShortcuts (shortcuts: ShortcutRecord[]) {
this.shortcuts = shortcuts;
},
changeDefaultCopyType (type: string) {
this.defaultCopyType = type;
settingsStore.set('default_copy_type', this.defaultCopyType);
}
}
});

View File

@ -0,0 +1,745 @@
import { defineStore } from 'pinia';
import * as Store from 'electron-store';
import Connection from '@/ipc-api/Connection';
import Schema from '@/ipc-api/Schema';
import Users from '@/ipc-api/Users';
import { uidGen } from 'common/libs/uidGen';
import customizations from 'common/customizations';
import { useConnectionsStore } from '@/stores/connections';
import { useNotificationsStore } from '@/stores/notifications';
import { useSettingsStore } from '@/stores/settings';
import {
ClientCode,
CollationInfos,
ConnectionParams,
EventInfos,
FunctionInfos,
RoutineInfos,
TableInfos,
TriggerFunctionInfos,
TriggerInfos,
TypesGroup
} from 'common/interfaces/antares';
import { Customizations } from 'common/interfaces/customizations';
export interface WorkspaceTab {
uid: string;
tab?: string;
index?: number;
selected?: boolean;
type?: string;
schema?: string;
elementName?: string;
elementNewName?: string;
elementType?: string;
isChanged?: boolean;
content?: string;
autorun?: boolean;
}
export interface WorkspaceStructure {
name: string;
functions: FunctionInfos[];
procedures: RoutineInfos[];
schedulers: EventInfos[];
tables: TableInfos[];
triggers: TriggerInfos[];
triggerFunctions: TriggerFunctionInfos[];
size: number;
}
export interface Breadcrumb {
function?: string;
routine?: string;
query?: string;
scheduler?: string;
schema?: string;
table?: string;
trigger?: string;
triggerFunction?: string;
view?: string;
}
export interface Workspace {
uid: string;
client?: ClientCode;
connectionStatus: string;
selectedTab: string | number;
searchTerm: string;
tabs: WorkspaceTab[];
structure: WorkspaceStructure[];
variables: { name: string; value: string }[];
collations: CollationInfos[];
users: { host: string; name: string; password?: string }[];
breadcrumbs: Breadcrumb;
loadingElements: { name: string; schema: string; type: string }[];
loadedSchemas: Set<string>;
dataTypes?: { [key: string]: TypesGroup[] };
indexTypes?: string[];
customizations?: Customizations;
version?: {
number: string;
name: string;
arch: string;
os: string;
};
engines?: {[key: string]: string | boolean | number}[];
}
const persistentStore = new Store({ name: 'tabs' });
const tabIndex: {[key: string]: number} = {};
export const useWorkspacesStore = defineStore('workspaces', {
state: () => ({
workspaces: [] as Workspace[],
selectedWorkspace: null as string
}),
getters: {
getSelected: state => {
if (!state.workspaces.length) return 'NEW';
if (state.selectedWorkspace) return state.selectedWorkspace;
const connectionsStore = useConnectionsStore();
if (connectionsStore.lastConnections.length) {
return connectionsStore.lastConnections.sort((a, b) => {
if (a.time < b.time) return 1;
else if (a.time > b.time) return -1;
return 0;
})[0].uid;
}
return state.workspaces[0].uid;
},
getWorkspace: state => (uid: string) => {
return state.workspaces.find(workspace => workspace.uid === uid);
},
getDatabaseVariable: state => (uid: string, name: string) => {
return state.workspaces.find(workspace => workspace.uid === uid).variables.find(variable => variable.name === name);
},
getWorkspaceTab (state) {
return (tUid: string) => {
if (!this.getSelected) return;
const workspace = state.workspaces.find(workspace => workspace.uid === this.getSelected);
if ('tabs' in workspace)
return workspace.tabs.find(tab => tab.uid === tUid);
return {};
};
},
getConnected: state => {
return state.workspaces
.filter(workspace => workspace.connectionStatus === 'connected')
.map(workspace => workspace.uid);
},
getLoadedSchemas: state => (uid: string) => {
return state.workspaces.find(workspace => workspace.uid === uid).loadedSchemas;
},
getSearchTerm: state => (uid: string) => {
return state.workspaces.find(workspace => workspace.uid === uid).searchTerm;
}
},
actions: {
selectWorkspace (uid: string) {
if (!uid)
this.selectedWorkspace = this.workspaces.length ? this.workspaces[0].uid : 'NEW';
else
this.selectedWorkspace = uid;
},
async connectWorkspace (connection: ConnectionParams & { pgConnString?: string }) {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === connection.uid
? {
...workspace,
structure: {},
breadcrumbs: {},
loadedSchemas: new Set(),
connectionStatus: 'connecting'
}
: workspace);
const connectionsStore = useConnectionsStore();
const notificationsStore = useNotificationsStore();
const settingsStore = useSettingsStore();
try {
const { status, response } = await Connection.connect(connection);
if (status === 'error') {
notificationsStore.addNotification({ status, message: response });
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === connection.uid
? {
...workspace,
structure: {},
breadcrumbs: {},
loadedSchemas: new Set(),
connectionStatus: 'failed'
}
: workspace);
}
else {
let clientCustomizations: Customizations;
const { updateLastConnection } = connectionsStore;
updateLastConnection(connection.uid);
switch (connection.client) {
case 'mysql':
case 'maria':
clientCustomizations = customizations.mysql;
break;
case 'pg':
clientCustomizations = customizations.pg;
break;
case 'sqlite':
clientCustomizations = customizations.sqlite;
break;
case 'firebird':
clientCustomizations = customizations.firebird;
break;
}
const dataTypes = clientCustomizations.dataTypes;
const indexTypes = clientCustomizations.indexTypes;
const { status, response: version } = await Schema.getVersion(connection.uid);
if (status === 'error')
notificationsStore.addNotification({ status, message: version });
// Check if Maria or MySQL
const isMySQL = version.name.includes('MySQL');
const isMaria = version.name.includes('Maria');
if (isMySQL && connection.client !== 'mysql') {
const connProxy = Object.assign({}, connection);
connProxy.client = 'mysql';
connectionsStore.editConnection(connProxy);
}
else if (isMaria && connection.client === 'mysql') {
const connProxy = Object.assign({}, connection);
connProxy.client = 'maria';
connectionsStore.editConnection(connProxy);
}
const cachedTabs: WorkspaceTab[] = settingsStore.restoreTabs ? persistentStore.get(connection.uid, []) as WorkspaceTab[] : [];
if (cachedTabs.length) {
tabIndex[connection.uid] = cachedTabs.reduce((acc: number, curr) => {
if (curr.index > acc) acc = curr.index;
return acc;
}, null);
}
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === connection.uid
? {
...workspace,
client: connection.client,
dataTypes,
indexTypes,
customizations: clientCustomizations,
structure: response,
connectionStatus: 'connected',
tabs: cachedTabs,
selectedTab: cachedTabs.length ? cachedTabs[0].uid : null,
version
}
: workspace);
this.refreshCollations(connection.uid);
this.refreshVariables(connection.uid);
this.refreshEngines(connection.uid);
this.refreshUsers(connection.uid);
}
}
catch (err) {
notificationsStore.addNotification({ status: 'error', message: err.stack });
}
},
async refreshStructure (uid: string) {
const notificationsStore = useNotificationsStore();
try {
const { status, response } = await Schema.getStructure({ uid, schemas: this.getLoadedSchemas(uid) });
if (status === 'error')
notificationsStore.addNotification({ status, message: response });
else {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === uid
? {
...workspace,
structure: response
}
: workspace);
}
}
catch (err) {
notificationsStore.addNotification({ status: 'error', message: err.stack });
}
},
async refreshSchema ({ uid, schema }: {uid: string; schema: string}) {
const notificationsStore = useNotificationsStore();
try {
const { status, response } = await Schema.getStructure({ uid, schemas: new Set([schema]) });
if (status === 'error')
notificationsStore.addNotification({ status, message: response });
else {
const schemaElements = (response as WorkspaceStructure[]).find(_schema => _schema.name === schema);
this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
if (workspace.uid === uid) {
const schemaIndex = workspace.structure.findIndex(s => s.name === schema);
if (schemaIndex !== -1)
workspace.structure[schemaIndex] = schemaElements;
else
workspace.structure.push(schemaElements);
}
return workspace;
});
}
}
catch (err) {
notificationsStore.addNotification({ status: 'error', message: err.stack });
}
},
async refreshCollations (uid: string) {
const notificationsStore = useNotificationsStore();
try {
const { status, response } = await Schema.getCollations(uid);
if (status === 'error')
notificationsStore.addNotification({ status, message: response });
else {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === uid
? {
...workspace,
collations: response
}
: workspace);
}
}
catch (err) {
notificationsStore.addNotification({ status: 'error', message: err.stack });
}
},
async refreshVariables (uid: string) {
const notificationsStore = useNotificationsStore();
try {
const { status, response } = await Schema.getVariables(uid);
if (status === 'error')
notificationsStore.addNotification({ status, message: response });
else {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === uid
? {
...workspace,
variables: response
}
: workspace);
}
}
catch (err) {
notificationsStore.addNotification({ status: 'error', message: err.stack });
}
},
async refreshEngines (uid: string) {
const notificationsStore = useNotificationsStore();
try {
const { status, response } = await Schema.getEngines(uid);
if (status === 'error')
notificationsStore.addNotification({ status, message: response });
else {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === uid
? {
...workspace,
engines: response
}
: workspace);
}
}
catch (err) {
notificationsStore.addNotification({ status: 'error', message: err.stack });
}
},
async refreshUsers (uid: string) {
const notificationsStore = useNotificationsStore();
try {
const { status, response } = await Users.getUsers(uid);
if (status === 'error')
notificationsStore.addNotification({ status, message: response });
else {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === uid
? {
...workspace,
users: response
}
: workspace);
}
}
catch (err) {
notificationsStore.addNotification({ status: 'error', message: err.stack });
}
},
removeConnected (uid: string) {
Connection.disconnect(uid);
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === uid
? {
...workspace,
structure: {},
breadcrumbs: {},
loadedSchemas: new Set(),
connectionStatus: 'disconnected'
}
: workspace);
this.selectTab({ uid, tab: 0 });
},
addWorkspace (uid: string) {
const workspace: Workspace = {
uid,
connectionStatus: 'disconnected',
selectedTab: 0,
searchTerm: '',
tabs: [],
structure: [],
variables: [],
collations: [],
users: [],
breadcrumbs: {},
loadingElements: [],
loadedSchemas: new Set()
};
this.workspaces.push(workspace);
},
changeBreadcrumbs (payload: Breadcrumb) {
const breadcrumbsObj: Breadcrumb = {
schema: null,
table: null,
trigger: null,
triggerFunction: null,
routine: null,
function: null,
scheduler: null,
view: null,
query: null
};
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === this.getSelected
? {
...workspace,
breadcrumbs: { ...breadcrumbsObj, ...payload }
}
: workspace);
},
addLoadedSchema (schema: string) {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
if (workspace.uid === this.getSelected)
workspace.loadedSchemas.add(schema);
return workspace;
});
},
addLoadingElement (element: { name: string; schema: string; type: string }) {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
if (workspace.uid === this.getSelected)
workspace.loadingElements.push(element);
return workspace;
});
},
removeLoadingElement (element: { name: string; schema: string; type: string }) {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
if (workspace.uid === this.getSelected) {
const loadingElements = workspace.loadingElements.filter(el =>
el.schema !== element.schema &&
el.name !== element.name &&
el.type !== element.type
);
workspace = { ...workspace, loadingElements };
}
return workspace;
});
},
setSearchTerm (term: string) {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === this.getSelected
? {
...workspace,
searchTerm: term
}
: workspace);
},
_addTab ({ uid, tab, content, type, autorun, schema, elementName, elementType }: WorkspaceTab) {
if (type === 'query')
tabIndex[uid] = tabIndex[uid] ? ++tabIndex[uid] : 1;
const newTab: WorkspaceTab = {
uid: tab,
index: type === 'query' ? tabIndex[uid] : null,
selected: false,
type,
schema,
elementName,
elementType,
content: content || '',
autorun: !!autorun
};
this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
if (workspace.uid === uid) {
return {
...workspace,
tabs: [...workspace.tabs, newTab]
};
}
else
return workspace;
});
persistentStore.set(uid, (this.workspaces as Workspace[]).find(workspace => workspace.uid === uid).tabs);
},
_replaceTab ({ uid, tab: tUid, type, schema, content, elementName, elementType }: WorkspaceTab) {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
if (workspace.uid === uid) {
return {
...workspace,
tabs: workspace.tabs.map(tab => {
if (tab.uid === tUid)
return { ...tab, type, schema, content, elementName, elementType };
return tab;
})
};
}
else
return workspace;
});
persistentStore.set(uid, (this.workspaces as Workspace[]).find(workspace => workspace.uid === uid).tabs);
},
newTab ({ uid, content, type, autorun, schema, elementName, elementType }: WorkspaceTab) {
let tabUid;
const workspaceTabs = (this.workspaces as Workspace[]).find(workspace => workspace.uid === uid);
switch (type) {
case 'new-table':
case 'new-trigger':
case 'new-trigger-function':
case 'new-function':
case 'new-routine':
case 'new-scheduler':
tabUid = uidGen('T');
this._addTab({
uid,
tab: tabUid,
content,
type,
autorun,
schema,
elementName,
elementType
});
break;
case 'temp-data':
case 'temp-trigger-props':
case 'temp-trigger-function-props':
case 'temp-function-props':
case 'temp-routine-props':
case 'temp-scheduler-props': {
const existentTab = workspaceTabs
? workspaceTabs.tabs.find(tab =>
tab.schema === schema &&
tab.elementName === elementName &&
tab.elementType === elementType &&
[type, type.replace('temp-', '')].includes(tab.type))
: false;
if (existentTab) { // if tab exists
tabUid = existentTab.uid;
}
else {
const tempTabs = workspaceTabs ? workspaceTabs.tabs.filter(tab => tab.type.includes('temp-')) : false;
if (tempTabs && tempTabs.length) { // if temp tab already opened
for (const tab of tempTabs) {
if (tab.isChanged) {
this._replaceTab({ // make permanent a temp table with unsaved changes
uid,
tab: tab.uid,
type: tab.type.replace('temp-', ''),
schema: tab.schema,
elementName: tab.elementName,
elementType: tab.elementType
});
tabUid = uidGen('T');
this._addTab({ uid, tab: tabUid, content, type, autorun, schema, elementName, elementType });
}
else {
this._replaceTab({ uid, tab: tab.uid, type, schema, elementName, elementType });
tabUid = tab.uid;
}
}
}
else {
tabUid = uidGen('T');
this._addTab({ uid, tab: tabUid, content, type, autorun, schema, elementName, elementType });
}
}
}
break;
case 'data':
case 'table-props':
case 'trigger-props':
case 'trigger-function-props':
case 'function-props':
case 'routine-props':
case 'scheduler-props': {
const existentTab = workspaceTabs
? workspaceTabs.tabs.find(tab =>
tab.schema === schema &&
tab.elementName === elementName &&
tab.elementType === elementType &&
[`temp-${type}`, type].includes(tab.type))
: false;
if (existentTab) {
this._replaceTab({ uid, tab: existentTab.uid, type, schema, elementName, elementType });
tabUid = existentTab.uid;
}
else {
tabUid = uidGen('T');
this._addTab({ uid, tab: tabUid, content, type, autorun, schema, elementName, elementType });
}
}
break;
default:
tabUid = uidGen('T');
this._addTab({ uid, tab: tabUid, content, type, autorun, schema, elementName, elementType });
break;
}
this.selectTab({ uid, tab: tabUid });
},
checkSelectedTabExists (uid: string) {
const workspace = (this.workspaces as Workspace[]).find(workspace => workspace.uid === uid);
const isSelectedExistent = workspace
? workspace.tabs.some(tab => tab.uid === workspace.selectedTab)
: false;
if (!isSelectedExistent && workspace.tabs.length)
this.selectTab({ uid, tab: workspace.tabs[workspace.tabs.length - 1].uid });
},
updateTabContent ({ uid, tab, type, schema, content }: WorkspaceTab) {
this._replaceTab({ uid, tab, type, schema, content });
},
renameTabs ({ uid, schema, elementName, elementNewName }: WorkspaceTab) {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
if (workspace.uid === uid) {
return {
...workspace,
tabs: workspace.tabs.map(tab => {
if (tab.elementName === elementName && tab.schema === schema) {
return {
...tab,
elementName: elementNewName
};
}
return tab;
})
};
}
else
return workspace;
});
persistentStore.set(uid, (this.workspaces as Workspace[]).find(workspace => workspace.uid === uid).tabs);
},
removeTab ({ uid, tab: tUid }: {uid: string; tab: string}) {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
if (workspace.uid === uid) {
return {
...workspace,
tabs: workspace.tabs.filter(tab => tab.uid !== tUid)
};
}
else
return workspace;
});
persistentStore.set(uid, (this.workspaces as Workspace[]).find(workspace => workspace.uid === uid).tabs);
this.checkSelectedTabExists(uid);
},
removeTabs ({ uid, schema, elementName, elementType }: WorkspaceTab) { // Multiple tabs based on schema and element name
if (elementType === 'procedure') elementType = 'routine'; // TODO: pass directly "routine"
this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
if (workspace.uid === uid) {
return {
...workspace,
tabs: workspace.tabs.filter(tab =>
tab.schema !== schema ||
tab.elementName !== elementName ||
tab.elementType !== elementType
)
};
}
else
return workspace;
});
persistentStore.set(uid, (this.workspaces as Workspace[]).find(workspace => workspace.uid === uid).tabs);
this.checkSelectedTabExists(uid);
},
selectTab ({ uid, tab }: {uid: string; tab: string}) {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === uid
? { ...workspace, selectedTab: tab }
: workspace
);
},
selectNextTab ({ uid }: {uid: string }) {
const workspace = (this.workspaces as Workspace[]).find(workspace => workspace.uid === uid);
let newIndex = workspace.tabs.findIndex(tab => tab.selected || tab.uid === workspace.selectedTab) + 1;
if (newIndex > workspace.tabs.length -1)
newIndex = 0;
this.selectTab({ uid, tab: workspace.tabs[newIndex].uid });
},
selectPrevTab ({ uid }: {uid: string }) {
const workspace = (this.workspaces as Workspace[]).find(workspace => workspace.uid === uid);
let newIndex = workspace.tabs.findIndex(tab => tab.selected || tab.uid === workspace.selectedTab) - 1;
if (newIndex < 0)
newIndex = workspace.tabs.length -1;
this.selectTab({ uid, tab: workspace.tabs[newIndex].uid });
},
updateTabs ({ uid, tabs }: {uid: string; tabs: WorkspaceTab[]}) {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => workspace.uid === uid
? { ...workspace, tabs }
: workspace
);
persistentStore.set(uid, (this.workspaces as Workspace[]).find(workspace => workspace.uid === uid).tabs);
},
setUnsavedChanges ({ uid, tUid, isChanged }: { uid: string; tUid: string; isChanged: boolean }) {
this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
if (workspace.uid === uid) {
return {
...workspace,
tabs: workspace.tabs.map(tab => {
if (tab.uid === tUid)
return { ...tab, isChanged };
return tab;
})
};
}
else
return workspace;
});
}
}
});

80
src/renderer/untyped.d.ts vendored Normal file
View File

@ -0,0 +1,80 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/ban-types */
declare module '@/App.vue';
declare module 'v-mask';
declare module 'vuedraggable' {// <- to export as default
const draggableComponent: import('vue').DefineComponent<{
list: {
type: ArrayConstructor;
required: boolean;
default: any;
};
modelValue: {
type: ArrayConstructor;
required: boolean;
default: any;
};
itemKey: {
type: (FunctionConstructor | StringConstructor)[];
required: boolean;
};
clone: {
type: FunctionConstructor;
default: (original: any) => any;
};
tag: {
type: StringConstructor;
default: string;
};
move: {
type: FunctionConstructor;
default: any;
};
componentData: {
type: ObjectConstructor;
required: boolean;
default: any;
};
}, unknown, {
error: boolean;
}, {
realList(): any;
getKey(): any;
}, {
getUnderlyingVm(domElement: any): any;
getUnderlyingPotencialDraggableComponent(htmElement: any): any;
emitChanges(evt: any): void;
alterList(onList: any): void;
spliceList(): void;
updatePosition(oldIndex: any, newIndex: any): void;
getRelatedContextFromMoveEvent({ to, related }: {
to: any;
related: any;
}): any;
getVmIndexFromDomIndex(domIndex: any): any;
onDragStart(evt: any): void;
onDragAdd(evt: any): void;
onDragRemove(evt: any): void;
onDragUpdate(evt: any): void;
computeFutureIndex(relatedContext: any, evt: any): any;
onDragMove(evt: any, originalEvent: any): any;
onDragEnd(): void;
}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, any[], any, import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, Readonly<{
move: Function;
tag: string;
clone: Function;
list: unknown[];
modelValue: unknown[];
componentData: Record<string, any>;
} & {
itemKey?: string | Function;
}>, {
move: Function;
tag: string;
clone: Function;
list: unknown[];
modelValue: unknown[];
componentData: Record<string, any>;
}>;
export = draggableComponent;
}

28
tsconfig.json Normal file
View File

@ -0,0 +1,28 @@
{
"include": [
"./tests/**/*",
"./src/main/**/*",
"./src/renderer/**/*",
"./src/common/**/*",
],
"exclude": ["./src/renderer/libs/ext-language_tools.js"],
"compilerOptions": {
"baseUrl": "./",
"target": "es2021",
"allowJs": true,
"module": "CommonJS",
"noImplicitAny": true,
"jsx": "preserve",
"types": [
"node"
],
"sourceMap": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"removeComments": true,
"paths": {
"common/*": ["./src/common/*"],
"@/*": ["./src/renderer/*"],
}
}
}

76
webpack.main.config.js Normal file
View File

@ -0,0 +1,76 @@
const path = require('path');
const webpack = require('webpack');
const ProgressPlugin = require('progress-webpack-plugin');
const { dependencies, devDependencies, version } = require('./package.json');
const externals = Object.keys(dependencies).concat(Object.keys(devDependencies));
const isDevMode = process.env.NODE_ENV === 'development';
const whiteListedModules = [];
module.exports = { // Main
name: 'main',
mode: process.env.NODE_ENV,
devtool: isDevMode ? 'eval-source-map' : false,
entry: {
main: path.join(__dirname, './src/main/main.ts')
},
target: 'electron-main',
output: {
libraryTarget: 'commonjs2',
path: path.join(__dirname, 'dist'),
filename: '[name].js'
},
node: {
global: true,
__dirname: isDevMode,
__filename: isDevMode
},
externals: externals.filter((d) => !whiteListedModules.includes(d)),
resolve: {
extensions: ['.js', '.json', '.ts'],
alias: {
src: path.join(__dirname, 'src/')
},
fallback: {
'pg-native': false,
'cpu-features': false,
cardinal: false
}
},
plugins: [
new ProgressPlugin(true),
new webpack.DefinePlugin({
'process.env': {
PACKAGE_VERSION: `"${version}"`
}
})
],
module: {
rules: [
{
test: /\.node$/,
loader: 'node-loader',
options: {
name: '[path][name].[ext]'
}
},
{
test: /\.ts$/,
exclude: /node_modules/,
loader: 'ts-loader'
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
{
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'file-loader'
}]
}
]
}
};

170
webpack.renderer.config.js Normal file
View File

@ -0,0 +1,170 @@
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ProgressPlugin = require('progress-webpack-plugin');
const { version } = require('./package.json');
const { contributors } = JSON.parse(fs.readFileSync('./.all-contributorsrc', 'utf-8'));
const parsedContributors = contributors.reduce((acc, c) => {
acc.push(c.name);
return acc;
}, []).join(',');
const isDevMode = process.env.NODE_ENV !== 'production';
const whiteListedModules = ['.bin'];
const externals = {};
fs.readdirSync('node_modules')
.filter(x => whiteListedModules.indexOf(x) === -1)
.forEach(mod => {
externals[mod] = `commonjs ${mod}`;
});
const config = {
name: 'renderer',
mode: process.env.NODE_ENV,
devtool: isDevMode ? 'eval-source-map' : false,
entry: path.join(__dirname, './src/renderer/index.ts'),
target: 'electron-renderer',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'renderer.js'
},
node: {
global: true,
__dirname: isDevMode,
__filename: isDevMode
},
externals: externals,
resolve: {
alias: {
'@': path.resolve(__dirname, 'src/renderer')
},
extensions: ['', '.js', '.vue', '.ts', '.json'],
fallback: {
fs: false,
path: false,
util: false,
crypto: false,
assert: false,
os: false
}
},
plugins: [
new ProgressPlugin(true),
new HtmlWebpackPlugin({
excludeChunks: ['processTaskWorker'],
filename: 'index.html',
template: path.resolve(__dirname, 'src/renderer/index.ejs'),
nodeModules: isDevMode
? path.resolve(__dirname, '../node_modules')
: false
}),
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css'
}),
new VueLoaderPlugin(),
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: isDevMode,
__VUE_I18N_LEGACY_API__: true,
__VUE_I18N_FULL_INSTALL__: true,
__INTLIFY_PROD_DEVTOOLS__: isDevMode,
'process.env': {
PACKAGE_VERSION: `"${version}"`,
APP_CONTRIBUTORS: `"${parsedContributors}"`
}
})
],
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.node$/,
use: 'node-loader'
},
{
test: /\.ts$/,
exclude: /node_modules/,
loader: 'ts-loader',
options: {
appendTsSuffixTo: [/.vue$/],
transpileOnly: true
}
},
{
test: /\.s(c|a)ss$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{ loader: 'css-loader' },
{
loader: 'sass-loader',
options: {
additionalData: `
$platform: ${process.platform};
@import "@/scss/_variables.scss";`,
sassOptions: { quietDeps: true }
}
}
]
},
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: ''
}
},
{
loader: 'css-loader',
options: {
url: true
}
}
]
},
{
test: /\.(png|jpe?g|gif|tif?f|bmp|webp|svg)(\?.*)?$/,
type: 'asset/resource',
generator: {
filename: 'images/[hash][ext][query]'
}
},
{
test: /\.(woff|woff2|ttf|eot)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024
}
},
generator: {
filename: 'fonts/[hash][ext][query]'
}
}
]
}
};
if (isDevMode) {
// any dev only config
config.plugins.push(
new webpack.HotModuleReplacementPlugin()
);
}
module.exports = config;

86
webpack.workers.config.js Normal file
View File

@ -0,0 +1,86 @@
const path = require('path');
const webpack = require('webpack');
const ProgressPlugin = require('progress-webpack-plugin');
const { dependencies, devDependencies, version } = require('./package.json');
const externals = Object.keys(dependencies).concat(Object.keys(devDependencies));
const isDevMode = process.env.NODE_ENV === 'development';
const whiteListedModules = [];
const config = {
name: 'workers',
mode: process.env.NODE_ENV,
devtool: isDevMode ? 'eval-source-map' : false,
entry: {
exporter: path.join(__dirname, './src/main/workers/exporter.ts'),
importer: path.join(__dirname, './src/main/workers/importer.ts')
},
target: 'node',
output: {
libraryTarget: 'commonjs2',
path: path.join(__dirname, 'dist'),
filename: '[name].js'
},
node: {
global: true,
__dirname: isDevMode,
__filename: isDevMode
},
externals: externals.filter((d) => !whiteListedModules.includes(d)),
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
loader: 'ts-loader'
},
{
test: /\.js$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.node$/,
use: 'node-loader'
}
]
},
resolve: {
extensions: ['.js', '.json', '.ts'],
alias: {
src: path.join(__dirname, 'src/'),
common: path.resolve(__dirname, 'src/common')
},
fallback: {
'pg-native': false,
'cpu-features': false,
cardinal: false
}
},
plugins: [
new ProgressPlugin(true),
new webpack.DefinePlugin({
'process.env': {
PACKAGE_VERSION: `"${version}"`
}
})
]
};
/**
* Adjust rendererConfig for production settings
*/
if (isDevMode) {
// any dev only config
config.plugins.push(new webpack.HotModuleReplacementPlugin());
}
else {
config.plugins.push(
new webpack.LoaderOptionsPlugin({
minimize: true
})
);
}
module.exports = config;