Compare commits
176 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d681ab4e79 | |||
| de06d42781 | |||
| 132e1603de | |||
| 7fe6438292 | |||
| 51c9f09286 | |||
| 84cc2c6b8e | |||
| 077136814d | |||
| 287d29bbdb | |||
| 6ac2122c76 | |||
| cc317c2433 | |||
| 4c4ad638d5 | |||
| a78570a2f5 | |||
| f57805123f | |||
| 18370cbd97 | |||
| 71832cbb5e | |||
| fc4b97adcb | |||
| 162874f383 | |||
| 463b179a6b | |||
| 878c47e58c | |||
| b033658444 | |||
| fc782304fd | |||
| bb39ec6543 | |||
| 131db1af29 | |||
| 1bded8473d | |||
| 1736934a1b | |||
| 1b44050c37 | |||
| b3e25150e9 | |||
| 6b80d72c03 | |||
| db7eb96360 | |||
| fc79f5f07c | |||
| fbd254bafc | |||
| 4cc1da3319 | |||
| 7a5187e283 | |||
| d71f27f8b3 | |||
| 99ab1b38cd | |||
| 351ac5e6a7 | |||
| 5a42e8c9ae | |||
| 0fdf333c0b | |||
| 2f6c749941 | |||
| b87f51c5b5 | |||
| 1de9b9f1fb | |||
| 4962d81661 | |||
| 290acdb68a | |||
| ed11b9e5a1 | |||
| 9d10068799 | |||
| cd03bb09fe | |||
| 0055f03c49 | |||
| 3f318fdef5 | |||
| 10a213d8a3 | |||
| 5558869ff4 | |||
| 60fd1c38ae | |||
| c10c6afbc5 | |||
| 79759b1665 | |||
| 0747614e00 | |||
| ab0a551d67 | |||
| 8fccaffedb | |||
| f211450b06 | |||
| 891a0a99e4 | |||
| 370412ea1d | |||
| 586a17f64e | |||
| 9aa58c8de7 | |||
| 1090d4effe | |||
| 96a52750e2 | |||
| e8ffaaab6f | |||
| a18f98bf28 | |||
| 95e0905f01 | |||
| 54bdaac64f | |||
| ca985330f6 | |||
| 7d5d2d131f | |||
| 8f5d866905 | |||
| ed15db4c20 | |||
| a7926a1a71 | |||
| 3a89d1a07b | |||
| 808267d97a | |||
| 4ab59e09d7 | |||
| c108c7d787 | |||
| fa5e128d45 | |||
| 1a181ff714 | |||
| 0f16f842bf | |||
| e502f5e72c | |||
| 2978063ac8 | |||
| ae0606cc84 | |||
| 0c0c0356a6 | |||
| 1e447a8937 | |||
| 3dcc761c14 | |||
| b1a2093e6b | |||
| ed98a9e2da | |||
| 737298c6f3 | |||
| 0081deb844 | |||
| 0857757ce2 | |||
| f7bab744e6 | |||
| 0316cb16eb | |||
| adcab4ae69 | |||
| 728353d60a | |||
| ac4aa94976 | |||
| d502dc0dfd | |||
| 55d0c77536 | |||
| 79ddbd439f | |||
| 7a0883ea03 | |||
| 5256deb567 | |||
| f993e82b0b | |||
| 698756b9d2 | |||
| 3921913742 | |||
| 576bdd64a0 | |||
| 27a78facf5 | |||
| ee50604112 | |||
| 1c30eb337b | |||
| 6a78272cc5 | |||
| e05f01dce8 | |||
| a29026321f | |||
| f8ee3b92cf | |||
| e0c91214fd | |||
| cc11e63cd7 | |||
| b47c9f81d2 | |||
| fcb93015cc | |||
| fc6355126f | |||
| fd2747d166 | |||
| 3bb22ddc36 | |||
| 0c4d5b5356 | |||
| 61217a944b | |||
| 5434302aa5 | |||
| 23d9c9279c | |||
| 4eae30c022 | |||
| ac7816fc4b | |||
| 10dc7343ae | |||
| a3837083da | |||
| 1644587072 | |||
| 6471141926 | |||
| 65197cf038 | |||
| 785c7e54ab | |||
| 9a2a945762 | |||
| 56eecb0836 | |||
| 8d9cb51baa | |||
| f55049f212 | |||
| dc6093e4fc | |||
| 5df650bc51 | |||
| d23a013579 | |||
| fc5c0eb239 | |||
| 9a42d1d6bd | |||
| 942115acef | |||
| 22b2a62209 | |||
| 0a3a1c9468 | |||
| 655429693a | |||
| 2afd46dc91 | |||
| 9bf755ff25 | |||
| d693cb734b | |||
| 836f48c810 | |||
| 327f2140cf | |||
| e4b605162e | |||
| e952d5c6f8 | |||
| 203e490321 | |||
| 7264e52e6c | |||
| 40bca24dfb | |||
| 56c6ddd392 | |||
| 2cd370d519 | |||
| 42545a027f | |||
| d1ed62ded9 | |||
| 2c9d3abe8c | |||
| a03a3416c2 | |||
| 45138b2bc1 | |||
| 3e57aab717 | |||
| 9aced8f01f | |||
| 3e1e6f7164 | |||
| 5aff88630a | |||
| 7e3555d84a | |||
| 6d7e7f97c7 | |||
| 0785c375a5 | |||
| 0d68eeac63 | |||
| 634e63a3dc | |||
| f60fd9cc63 | |||
| 49628ad3cf | |||
| 13a18eb556 | |||
| 76ec548b4f | |||
| 9c9c82a547 | |||
| b1ccd16870 | |||
| fedf2db847 |
@@ -33,9 +33,14 @@ jobs:
|
||||
- name: yarn install
|
||||
run: |
|
||||
yarn install
|
||||
- name: setCurrentVersion
|
||||
run: |
|
||||
yarn setCurrentVersion
|
||||
- name: Publish
|
||||
run: |
|
||||
yarn run build:app
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }} # token for electron publish
|
||||
|
||||
- name: Copy artifacts Linux, MacOs
|
||||
if: matrix.os != 'windows-2016'
|
||||
|
||||
@@ -37,6 +37,9 @@ jobs:
|
||||
- name: yarn install
|
||||
run: |
|
||||
yarn install
|
||||
- name: setCurrentVersion
|
||||
run: |
|
||||
yarn setCurrentVersion
|
||||
- name: Prepare docker image
|
||||
run: |
|
||||
yarn run prepare:docker
|
||||
|
||||
@@ -21,6 +21,7 @@ DbGate is fast and efficient database administration tool. It is focused to work
|
||||
* Archives - backup your data in JSON files on local filesystem (or on DbGate server, when using web application)
|
||||
* Light and dark theme
|
||||
* For detailed info, how to run DbGate in docker container, visit [docker hub](https://hub.docker.com/r/dbgate/dbgate)
|
||||
* Extensible plugin architecture
|
||||
|
||||

|
||||
|
||||
@@ -34,14 +35,23 @@ DbGate is fast and efficient database administration tool. It is focused to work
|
||||
* There is plan to incorporate SQLite to support work with local datasets
|
||||
* Platform independed - will run as web application in single docker container on server, or as application using Electron platform on Linux, Windows and Mac
|
||||
|
||||
## Plugins
|
||||
Plugins are standard NPM packages published on [npmjs.com](https://www.npmjs.com).
|
||||
See all [existing DbGate plugins](https://www.npmjs.com/search?q=keywords:dbgateplugin).
|
||||
Visit [dbgate generator homepage](https://github.com/dbshell/generator-dbgate) to see, how to create your own plugin.
|
||||
|
||||
Currently following extensions can be implemented using plugins:
|
||||
- File format parsers/writers
|
||||
- Database engine connectors
|
||||
|
||||
## How Can I Contribute?
|
||||
You're welcome to contribute to this project! Below are some ideas, how to contribute:
|
||||
|
||||
* Create plugins for new import/export formats
|
||||
* Bug fixing
|
||||
* Test Mac edition
|
||||
* Improve linux package build, add to APT repository
|
||||
* Auto-upgrade of electron application
|
||||
* Support for new import/export formats
|
||||
|
||||
Any help is appreciated!
|
||||
|
||||
@@ -99,9 +109,10 @@ Some dbgate packages can be used also without DbGate. You can find them on [NPM
|
||||
* [api](https://github.com/dbshell/dbgate/tree/master/packages/api) - backend, Javascript, ExpressJS [](https://www.npmjs.com/package/dbgate-api)
|
||||
* [datalib](https://github.com/dbshell/dbgate/tree/master/packages/datalib) - TypeScript library for utility classes
|
||||
* [app](https://github.com/dbshell/dbgate/tree/master/app) - application (JavaScript)
|
||||
* [engines](https://github.com/dbshell/dbgate/tree/master/packages/engines) - drivers for database engine (mssql, mysql, postgres), analysing database structure, creating specific queries (JavaScript) [](https://www.npmjs.com/package/dbgate-engines)
|
||||
structure, creating specific queries (JavaScript) [](https://www.npmjs.com/package/dbgate-engines)
|
||||
* [filterparser](https://github.com/dbshell/dbgate/tree/master/packages/filterparser) - TypeScript library for parsing data filter expressions using parsimmon
|
||||
* [sqltree](https://github.com/dbshell/dbgate/tree/master/packages/sqltree) - JSON representation of SQL query, functions converting to SQL (TypeScript) [](https://www.npmjs.com/package/dbgate-sqltree)
|
||||
* [types](https://github.com/dbshell/dbgate/tree/master/packages/types) - common TypeScript definitions [](https://www.npmjs.com/package/dbgate-types)
|
||||
* [web](https://github.com/dbshell/dbgate/tree/master/packages/web) - frontend in React (JavaScript)
|
||||
* [tools](https://github.com/dbshell/dbgate/tree/master/packages/tools) - various tools [](https://www.npmjs.com/package/dbgate-tools)
|
||||
|
||||
|
||||
+674
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
+22
-8
@@ -1,30 +1,44 @@
|
||||
{
|
||||
"name": "dbgate",
|
||||
"version": "3.7.33",
|
||||
"version": "3.8.20",
|
||||
"private": true,
|
||||
"author": "Jan Prochazka <jenasoft.database@gmail.com>",
|
||||
"dependencies": {
|
||||
"electron-store": "^5.1.1",
|
||||
"electron-updater": "^4.3.1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbshell/dbgate.git"
|
||||
},
|
||||
"build": {
|
||||
"appId": "org.dbgate",
|
||||
"mac": {
|
||||
"category": "database",
|
||||
"icon": "icon512.png"
|
||||
"icon": "icon512.png",
|
||||
"publish": [
|
||||
"github"
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
"AppImage",
|
||||
"deb"
|
||||
"AppImage"
|
||||
],
|
||||
"icon": "icon512.png"
|
||||
"icon": "icon.png",
|
||||
"category": "Development",
|
||||
"synopsis": "Database administration tool for MS SQL, MySQL and PostgreSQL",
|
||||
"publish": [
|
||||
"github"
|
||||
]
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
"nsis"
|
||||
],
|
||||
"icon": "icon.ico"
|
||||
"icon": "icon.ico",
|
||||
"publish": [
|
||||
"github"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"packages",
|
||||
@@ -45,7 +59,7 @@
|
||||
"devDependencies": {
|
||||
"copyfiles": "^2.2.0",
|
||||
"cross-env": "^6.0.3",
|
||||
"electron": "8.1.1",
|
||||
"electron-builder": "22.4.1"
|
||||
"electron": "11.1.1",
|
||||
"electron-builder": "22.9.1"
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -2,7 +2,7 @@ const electron = require('electron');
|
||||
const os = require('os');
|
||||
const { Menu } = require('electron');
|
||||
const { fork } = require('child_process');
|
||||
var { autoUpdater } = require('electron-updater');
|
||||
const { autoUpdater } = require('electron-updater');
|
||||
const Store = require('electron-store');
|
||||
// Module to control application life.
|
||||
const app = electron.app;
|
||||
@@ -99,6 +99,12 @@ function buildMenu() {
|
||||
require('electron').shell.openExternal('https://hub.docker.com/r/dbgate/dbgate');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'About',
|
||||
click() {
|
||||
mainWindow.webContents.executeJavaScript(`dbgate_showAbout()`);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -108,15 +114,17 @@ function buildMenu() {
|
||||
|
||||
function createWindow() {
|
||||
const bounds = store.get('winBounds');
|
||||
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
title: 'DbGate',
|
||||
icon: os.platform() == 'win32' ? 'icon.ico' : 'icon512.png',
|
||||
...bounds,
|
||||
icon: os.platform() == 'win32' ? 'icon.ico' : path.resolve(__dirname, '../icon.png'),
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
enableRemoteModule: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+290
-216
@@ -7,13 +7,13 @@
|
||||
resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.0.3.tgz#bc5b5532ecafd923a61f2fb097e3b108c0106a3f"
|
||||
integrity sha512-GLyWIFBbGvpKPGo55JyRZAo4lVbnBiD52cKlw/0Vt+wnmKvWJkpZvsjVoaIolyBXDeAQKSicRtqFNPem9w0WYA==
|
||||
|
||||
"@develar/schema-utils@~2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@develar/schema-utils/-/schema-utils-2.1.0.tgz#eceb1695bfbed6f6bb84666d5d3abe5e1fd54e17"
|
||||
integrity sha512-qjCqB4ctMig9Gz5bd6lkdFr3bO6arOdQqptdBSpF1ZpCnjofieCciEzkoS9ujY9cMGyllYSCSmBJ3x9OKHXzoA==
|
||||
"@develar/schema-utils@~2.6.5":
|
||||
version "2.6.5"
|
||||
resolved "https://registry.yarnpkg.com/@develar/schema-utils/-/schema-utils-2.6.5.tgz#3ece22c5838402419a6e0425f85742b961d9b6c6"
|
||||
integrity sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==
|
||||
dependencies:
|
||||
ajv "^6.1.0"
|
||||
ajv-keywords "^3.1.0"
|
||||
ajv "^6.12.0"
|
||||
ajv-keywords "^3.4.1"
|
||||
|
||||
"@electron/get@^1.0.1":
|
||||
version "1.9.0"
|
||||
@@ -53,10 +53,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd"
|
||||
integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==
|
||||
|
||||
"@types/fs-extra@^8.1.0":
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.0.tgz#1114834b53c3914806cd03b3304b37b3bd221a4d"
|
||||
integrity sha512-UoOfVEzAUpeSPmjm7h1uk5MH6KZma2z2O7a75onTGjnNvAvMVrPzPL/vBbT65iIGHWj6rokwfmYcmxmlSf2uwg==
|
||||
"@types/fs-extra@^9.0.1":
|
||||
version "9.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.6.tgz#488e56b77299899a608b8269719c1d133027a6ab"
|
||||
integrity sha512-ecNRHw4clCkowNOBJH1e77nvbPxHYnWIXMv1IAoG/9+MYGkgoyr3Ppxr7XYFNL41V422EDhyV4/4SSK8L2mlig==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
@@ -82,19 +82,19 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
|
||||
integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==
|
||||
|
||||
"@types/yargs@^15.0.4":
|
||||
version "15.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.4.tgz#7e5d0f8ca25e9d5849f2ea443cf7c402decd8299"
|
||||
integrity sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg==
|
||||
"@types/yargs@^15.0.5":
|
||||
version "15.0.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.12.tgz#6234ce3e3e3fa32c5db301a170f96a599c960d74"
|
||||
integrity sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
ajv-keywords@^3.1.0:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
|
||||
integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
|
||||
ajv-keywords@^3.4.1:
|
||||
version "3.5.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
|
||||
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
|
||||
|
||||
ajv@^6.1.0, ajv@^6.10.2:
|
||||
ajv@^6.10.2:
|
||||
version "6.12.0"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7"
|
||||
integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==
|
||||
@@ -104,6 +104,16 @@ ajv@^6.1.0, ajv@^6.10.2:
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ajv@^6.12.0:
|
||||
version "6.12.6"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.1"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ansi-align@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb"
|
||||
@@ -121,7 +131,7 @@ ansi-regex@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
|
||||
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
|
||||
|
||||
ansi-styles@^3.2.0:
|
||||
ansi-styles@^3.2.0, ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
|
||||
@@ -136,37 +146,37 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
|
||||
"@types/color-name" "^1.1.1"
|
||||
color-convert "^2.0.1"
|
||||
|
||||
app-builder-bin@3.5.5:
|
||||
version "3.5.5"
|
||||
resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-3.5.5.tgz#c83200dccd8df5ccb2a5adcd41b2a76bacfb531a"
|
||||
integrity sha512-ZcHzJ9Xl+azPqdKzXZKdRZmkNmbxHHZyl4cbobNf8qMQpoPChpcov8riVrZSbu/0cT/JqJ8LOwJjy1OAwbChaQ==
|
||||
app-builder-bin@3.5.10:
|
||||
version "3.5.10"
|
||||
resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-3.5.10.tgz#4a7f9999fccc0c435b6284ae1366bc76a17c4a7d"
|
||||
integrity sha512-Jd+GW68lR0NeetgZDo47PdWBEPdnD+p0jEa7XaxjRC8u6Oo/wgJsfKUkORRgr2NpkD19IFKN50P6JYy04XHFLQ==
|
||||
|
||||
app-builder-lib@22.4.1, app-builder-lib@~22.4.1:
|
||||
version "22.4.1"
|
||||
resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-22.4.1.tgz#9d73b2834e434fb65fc9571ae3fed3c1470b6915"
|
||||
integrity sha512-epwUzIM+2pcdy/If9koTP74CKx4v7xGPj75a2Z5cM4rrGN9yVZ3eDUBbfF0e0qE4Qmcv5pd0BAZJ26bGm8NWsQ==
|
||||
app-builder-lib@22.9.1:
|
||||
version "22.9.1"
|
||||
resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-22.9.1.tgz#ccb8f1a02b628514a5dfab9401fa2a976689415c"
|
||||
integrity sha512-KfXim/fiNwFW2SKffsjEMdAU7RbbEXn62x5YyXle1b4j9X/wEHW9iwox8De6y0hJdR+/kCC/49lI+VgNwLhV7A==
|
||||
dependencies:
|
||||
"7zip-bin" "~5.0.3"
|
||||
"@develar/schema-utils" "~2.1.0"
|
||||
"@develar/schema-utils" "~2.6.5"
|
||||
async-exit-hook "^2.0.1"
|
||||
bluebird-lst "^1.0.9"
|
||||
builder-util "22.4.1"
|
||||
builder-util-runtime "8.6.2"
|
||||
builder-util "22.9.1"
|
||||
builder-util-runtime "8.7.2"
|
||||
chromium-pickle-js "^0.2.0"
|
||||
debug "^4.1.1"
|
||||
ejs "^3.0.1"
|
||||
electron-publish "22.4.1"
|
||||
fs-extra "^8.1.0"
|
||||
hosted-git-info "^3.0.4"
|
||||
debug "^4.3.0"
|
||||
ejs "^3.1.5"
|
||||
electron-publish "22.9.1"
|
||||
fs-extra "^9.0.1"
|
||||
hosted-git-info "^3.0.5"
|
||||
is-ci "^2.0.0"
|
||||
isbinaryfile "^4.0.4"
|
||||
js-yaml "^3.13.1"
|
||||
isbinaryfile "^4.0.6"
|
||||
js-yaml "^3.14.0"
|
||||
lazy-val "^1.0.4"
|
||||
minimatch "^3.0.4"
|
||||
normalize-package-data "^2.5.0"
|
||||
read-config-file "5.0.2"
|
||||
read-config-file "6.0.0"
|
||||
sanitize-filename "^1.6.3"
|
||||
semver "^7.1.3"
|
||||
semver "^7.3.2"
|
||||
temp-file "^3.3.7"
|
||||
|
||||
argparse@^1.0.7:
|
||||
@@ -181,6 +191,11 @@ async-exit-hook@^2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz#8bd8b024b0ec9b1c01cccb9af9db29bd717dfaf3"
|
||||
integrity sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==
|
||||
|
||||
async@0.9.x:
|
||||
version "0.9.2"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
|
||||
integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=
|
||||
|
||||
at-least-node@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
|
||||
@@ -235,14 +250,6 @@ buffer-from@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
||||
|
||||
builder-util-runtime@8.6.2:
|
||||
version "8.6.2"
|
||||
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.6.2.tgz#8270e15b012d8d3b110f3e327b0fd8b0e07b1686"
|
||||
integrity sha512-9QnIBISfhgQ2BxtRLidVqf/v5HD73vSKZDllpUmGd2L6VORGQk7cZAPmPtw4HQM3gPBelyVJ5yIjMNZ8xjmd1A==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
sax "^1.2.4"
|
||||
|
||||
builder-util-runtime@8.7.0:
|
||||
version "8.7.0"
|
||||
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.7.0.tgz#e48ad004835c8284662e8eaf47a53468c66e8e8d"
|
||||
@@ -251,23 +258,31 @@ builder-util-runtime@8.7.0:
|
||||
debug "^4.1.1"
|
||||
sax "^1.2.4"
|
||||
|
||||
builder-util@22.4.1, builder-util@~22.4.1:
|
||||
version "22.4.1"
|
||||
resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-22.4.1.tgz#49cce9f06a62cdccda66d5efa82077040fa1f462"
|
||||
integrity sha512-+ysLc7cC4w6P7rBxmZ5X2aU3QvcwFoWCl1us+mcUKdsGmJAtFUMPJqueeptdxjyPrPShIUOKHzA8uk5A3d1fHg==
|
||||
builder-util-runtime@8.7.2:
|
||||
version "8.7.2"
|
||||
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.7.2.tgz#d93afc71428a12789b437e13850e1fa7da956d72"
|
||||
integrity sha512-xBqv+8bg6cfnzAQK1k3OGpfaHg+QkPgIgpEkXNhouZ0WiUkyZCftuRc2LYzQrLucFywpa14Xbc6+hTbpq83yRA==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
sax "^1.2.4"
|
||||
|
||||
builder-util@22.9.1:
|
||||
version "22.9.1"
|
||||
resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-22.9.1.tgz#b7087a5cde477f90d718ca5d7fafb6ae261b16af"
|
||||
integrity sha512-5hN/XOaYu4ZQUS6F+5CXE6jTo+NAnVqAxDuKGSaHWb9bejfv/rluChTLoY3/nJh7RFjkoyVjvFJv7zQDB1QmHw==
|
||||
dependencies:
|
||||
"7zip-bin" "~5.0.3"
|
||||
"@types/debug" "^4.1.5"
|
||||
"@types/fs-extra" "^8.1.0"
|
||||
app-builder-bin "3.5.5"
|
||||
"@types/fs-extra" "^9.0.1"
|
||||
app-builder-bin "3.5.10"
|
||||
bluebird-lst "^1.0.9"
|
||||
builder-util-runtime "8.6.2"
|
||||
chalk "^3.0.0"
|
||||
debug "^4.1.1"
|
||||
fs-extra "^8.1.0"
|
||||
builder-util-runtime "8.7.2"
|
||||
chalk "^4.1.0"
|
||||
debug "^4.3.0"
|
||||
fs-extra "^9.0.1"
|
||||
is-ci "^2.0.0"
|
||||
js-yaml "^3.13.1"
|
||||
source-map-support "^0.5.16"
|
||||
js-yaml "^3.14.0"
|
||||
source-map-support "^0.5.19"
|
||||
stat-mode "^1.0.0"
|
||||
temp-file "^3.3.7"
|
||||
|
||||
@@ -289,6 +304,15 @@ camelcase@^5.0.0, camelcase@^5.3.1:
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
|
||||
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
|
||||
|
||||
chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.1"
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
|
||||
@@ -297,6 +321,14 @@ chalk@^3.0.0:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
|
||||
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chromium-pickle-js@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz#04a106672c18b085ab774d983dfa3ea138f22205"
|
||||
@@ -321,14 +353,14 @@ cliui@^5.0.0:
|
||||
strip-ansi "^5.2.0"
|
||||
wrap-ansi "^5.1.0"
|
||||
|
||||
cliui@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
|
||||
integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
|
||||
cliui@^7.0.2:
|
||||
version "7.0.4"
|
||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
|
||||
integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
|
||||
dependencies:
|
||||
string-width "^4.2.0"
|
||||
strip-ansi "^6.0.0"
|
||||
wrap-ansi "^6.2.0"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
clone-response@^1.0.2:
|
||||
version "1.0.2"
|
||||
@@ -476,6 +508,13 @@ debug@^4.1.0, debug@^4.1.1:
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@^4.3.0:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
|
||||
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
decamelize@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
@@ -510,17 +549,16 @@ detect-node@^2.0.4:
|
||||
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
|
||||
integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
|
||||
|
||||
dmg-builder@22.4.1:
|
||||
version "22.4.1"
|
||||
resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-22.4.1.tgz#ab80d3d6e4ed8a1d38beddbfe97c8f7a794dd932"
|
||||
integrity sha512-hEemh7n0zoVt7zPPwvn7iOttP03oENjJ4ApttPmt8oDnX8T4q42MjGWyDlLkPMplMJfoTxkkNqmm296f0OYM8Q==
|
||||
dmg-builder@22.9.1:
|
||||
version "22.9.1"
|
||||
resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-22.9.1.tgz#64647224f37ee47fc9bd01947c21cc010a30511f"
|
||||
integrity sha512-jc+DAirqmQrNT6KbDHdfEp8D1kD0DBTnsLhwUR3MX+hMBun5bT134LQzpdK0GKvd22GqF8L1Cz/NOgaVjscAXQ==
|
||||
dependencies:
|
||||
app-builder-lib "~22.4.1"
|
||||
bluebird-lst "^1.0.9"
|
||||
builder-util "~22.4.1"
|
||||
fs-extra "^8.1.0"
|
||||
iconv-lite "^0.5.1"
|
||||
js-yaml "^3.13.1"
|
||||
app-builder-lib "22.9.1"
|
||||
builder-util "22.9.1"
|
||||
fs-extra "^9.0.1"
|
||||
iconv-lite "^0.6.2"
|
||||
js-yaml "^3.14.0"
|
||||
sanitize-filename "^1.6.3"
|
||||
|
||||
dot-prop@^5.0.0, dot-prop@^5.2.0:
|
||||
@@ -545,44 +583,46 @@ duplexer3@^0.1.4:
|
||||
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
|
||||
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
|
||||
|
||||
ejs@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.0.1.tgz#30c8f6ee9948502cc32e85c37a3f8b39b5a614a5"
|
||||
integrity sha512-cuIMtJwxvzumSAkqaaoGY/L6Fc/t6YvoP9/VIaK0V/CyqKLEQ8sqODmYfy/cjXEdZ9+OOL8TecbJu+1RsofGDw==
|
||||
|
||||
electron-builder@22.4.1:
|
||||
version "22.4.1"
|
||||
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-22.4.1.tgz#7a7a2cbd9955c90cecc36de32ecba7f565265bab"
|
||||
integrity sha512-13CjZcGeJS+c3EKRwFT/Oty5Niif5g1FwDioBLEbjkPCPQgxdtDsr+rJtCu9qxkiKDYpAoPS+t/clNk0efONvQ==
|
||||
ejs@^3.1.5:
|
||||
version "3.1.5"
|
||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.5.tgz#aed723844dc20acb4b170cd9ab1017e476a0d93b"
|
||||
integrity sha512-dldq3ZfFtgVTJMLjOe+/3sROTzALlL9E34V4/sDtUd/KlBSS0s6U1/+WPE1B4sj9CXHJpL1M6rhNJnc9Wbal9w==
|
||||
dependencies:
|
||||
"@types/yargs" "^15.0.4"
|
||||
app-builder-lib "22.4.1"
|
||||
jake "^10.6.1"
|
||||
|
||||
electron-builder@22.9.1:
|
||||
version "22.9.1"
|
||||
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-22.9.1.tgz#a2962db6f2757bc01d02489f38fafe0809f68f60"
|
||||
integrity sha512-GXPt8l5Mxwm1QKYopUM6/Tdh9W3695G6Ax+IFyj5pQ51G4SD5L1uq4/RkPSsOgs3rP7jNSV6g6OfDzdtVufPdA==
|
||||
dependencies:
|
||||
"@types/yargs" "^15.0.5"
|
||||
app-builder-lib "22.9.1"
|
||||
bluebird-lst "^1.0.9"
|
||||
builder-util "22.4.1"
|
||||
builder-util-runtime "8.6.2"
|
||||
chalk "^3.0.0"
|
||||
dmg-builder "22.4.1"
|
||||
fs-extra "^8.1.0"
|
||||
builder-util "22.9.1"
|
||||
builder-util-runtime "8.7.2"
|
||||
chalk "^4.1.0"
|
||||
dmg-builder "22.9.1"
|
||||
fs-extra "^9.0.1"
|
||||
is-ci "^2.0.0"
|
||||
lazy-val "^1.0.4"
|
||||
read-config-file "5.0.2"
|
||||
read-config-file "6.0.0"
|
||||
sanitize-filename "^1.6.3"
|
||||
update-notifier "^4.1.0"
|
||||
yargs "^15.1.0"
|
||||
update-notifier "^4.1.1"
|
||||
yargs "^16.0.3"
|
||||
|
||||
electron-publish@22.4.1:
|
||||
version "22.4.1"
|
||||
resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-22.4.1.tgz#a7fcf166786f7d5957f19a70ee8389f219769ba5"
|
||||
integrity sha512-nwKNum3KXm+01rtWX2pc1jhazdzDy2zYnQx+zmXphZchjd6UOMX3ZN0xyZUCKugw5ZliflT6LkgbrcBXBtYD3A==
|
||||
electron-publish@22.9.1:
|
||||
version "22.9.1"
|
||||
resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-22.9.1.tgz#7cc76ac4cc53efd29ee31c1e5facb9724329068e"
|
||||
integrity sha512-ducLjRJLEeU87FaTCWaUyDjCoLXHkawkltP2zqS/n2PyGke54ZIql0tBuUheht4EpR8AhFbVJ11spSn1gy8r6w==
|
||||
dependencies:
|
||||
"@types/fs-extra" "^8.1.0"
|
||||
"@types/fs-extra" "^9.0.1"
|
||||
bluebird-lst "^1.0.9"
|
||||
builder-util "~22.4.1"
|
||||
builder-util-runtime "8.6.2"
|
||||
chalk "^3.0.0"
|
||||
fs-extra "^8.1.0"
|
||||
builder-util "22.9.1"
|
||||
builder-util-runtime "8.7.2"
|
||||
chalk "^4.1.0"
|
||||
fs-extra "^9.0.1"
|
||||
lazy-val "^1.0.4"
|
||||
mime "^2.4.4"
|
||||
mime "^2.4.6"
|
||||
|
||||
electron-store@^5.1.1:
|
||||
version "5.1.1"
|
||||
@@ -605,10 +645,10 @@ electron-updater@^4.3.1:
|
||||
lodash.isequal "^4.5.0"
|
||||
semver "^7.1.3"
|
||||
|
||||
electron@8.1.1:
|
||||
version "8.1.1"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-8.1.1.tgz#737a5af03c7b4af60b49dff7bfe1203fcbd5bf89"
|
||||
integrity sha512-t+5zzFo7VOgckJc9YpImHJkpqeWxwpmEjywWbAa4IT5MULS7h1XU52H9gMswK/y8xc5lBNwxLhJSty/15+gi1A==
|
||||
electron@11.1.1:
|
||||
version "11.1.1"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-11.1.1.tgz#188f036f8282798398dca9513e9bb3b10213e3aa"
|
||||
integrity sha512-tlbex3xosJgfileN6BAQRotevPRXB/wQIq48QeQ08tUJJrXwE72c8smsM/hbHx5eDgnbfJ2G3a60PmRjHU2NhA==
|
||||
dependencies:
|
||||
"@electron/get" "^1.0.1"
|
||||
"@types/node" "^12.0.12"
|
||||
@@ -646,11 +686,21 @@ es6-error@^4.1.1:
|
||||
resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
|
||||
integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
|
||||
|
||||
escalade@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
|
||||
|
||||
escape-goat@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
|
||||
integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==
|
||||
|
||||
escape-string-regexp@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||
|
||||
escape-string-regexp@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
|
||||
@@ -688,6 +738,13 @@ fd-slicer@~1.0.1:
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
filelist@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.1.tgz#f10d1a3ae86c1694808e8f20906f43d4c9132dbb"
|
||||
integrity sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ==
|
||||
dependencies:
|
||||
minimatch "^3.0.4"
|
||||
|
||||
find-up@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
|
||||
@@ -695,14 +752,6 @@ find-up@^3.0.0:
|
||||
dependencies:
|
||||
locate-path "^3.0.0"
|
||||
|
||||
find-up@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
|
||||
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
|
||||
dependencies:
|
||||
locate-path "^5.0.0"
|
||||
path-exists "^4.0.0"
|
||||
|
||||
fs-extra@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
|
||||
@@ -722,12 +771,22 @@ fs-extra@^9.0.0:
|
||||
jsonfile "^6.0.1"
|
||||
universalify "^1.0.0"
|
||||
|
||||
fs-extra@^9.0.1:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc"
|
||||
integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==
|
||||
dependencies:
|
||||
at-least-node "^1.0.0"
|
||||
graceful-fs "^4.2.0"
|
||||
jsonfile "^6.0.1"
|
||||
universalify "^1.0.0"
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
||||
|
||||
get-caller-file@^2.0.1:
|
||||
get-caller-file@^2.0.1, get-caller-file@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
@@ -817,6 +876,11 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
|
||||
integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
||||
|
||||
has-flag@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||
@@ -832,24 +896,24 @@ hosted-git-info@^2.1.4:
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
||||
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
|
||||
|
||||
hosted-git-info@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.4.tgz#be4973eb1fd2737b11c9c7c19380739bb249f60d"
|
||||
integrity sha512-4oT62d2jwSDBbLLFLZE+1vPuQ1h8p9wjrJ8Mqx5TjsyWmBMV5B13eJqn8pvluqubLf3cJPTfiYCIwNwDNmzScQ==
|
||||
hosted-git-info@^3.0.5:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.7.tgz#a30727385ea85acfcee94e0aad9e368c792e036c"
|
||||
integrity sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ==
|
||||
dependencies:
|
||||
lru-cache "^5.1.1"
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
http-cache-semantics@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
|
||||
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
|
||||
|
||||
iconv-lite@^0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.5.1.tgz#b2425d3c7b18f7219f2ca663d103bddb91718d64"
|
||||
integrity sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==
|
||||
iconv-lite@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01"
|
||||
integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
safer-buffer ">= 2.1.2 < 3.0.0"
|
||||
|
||||
import-lazy@^2.1.0:
|
||||
version "2.1.0"
|
||||
@@ -875,9 +939,9 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
|
||||
version "1.3.8"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
|
||||
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
|
||||
|
||||
is-ci@^2.0.0:
|
||||
version "2.0.0"
|
||||
@@ -939,16 +1003,26 @@ isarray@~1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
||||
|
||||
isbinaryfile@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.4.tgz#6803f81a8944201c642b6e17da041e24deb78712"
|
||||
integrity sha512-pEutbN134CzcjlLS1myKX/uxNjwU5eBVSprvkpv3+3dqhBHUZLIWJQowC40w5c0Zf19vBY8mrZl88y5J4RAPbQ==
|
||||
isbinaryfile@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b"
|
||||
integrity sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==
|
||||
|
||||
isexe@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
|
||||
|
||||
jake@^10.6.1:
|
||||
version "10.8.2"
|
||||
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b"
|
||||
integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==
|
||||
dependencies:
|
||||
async "0.9.x"
|
||||
chalk "^2.4.2"
|
||||
filelist "^1.0.1"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
js-yaml@^3.13.1:
|
||||
version "3.13.1"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
|
||||
@@ -957,6 +1031,14 @@ js-yaml@^3.13.1:
|
||||
argparse "^1.0.7"
|
||||
esprima "^4.0.0"
|
||||
|
||||
js-yaml@^3.14.0:
|
||||
version "3.14.1"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
|
||||
integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
|
||||
dependencies:
|
||||
argparse "^1.0.7"
|
||||
esprima "^4.0.0"
|
||||
|
||||
json-buffer@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
|
||||
@@ -977,12 +1059,12 @@ json-stringify-safe@^5.0.1:
|
||||
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
||||
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
|
||||
|
||||
json5@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.1.tgz#81b6cb04e9ba496f1c7005d07b4368a2638f90b6"
|
||||
integrity sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==
|
||||
json5@^2.1.2:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
|
||||
integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
|
||||
dependencies:
|
||||
minimist "^1.2.0"
|
||||
minimist "^1.2.5"
|
||||
|
||||
jsonfile@^4.0.0:
|
||||
version "4.0.0"
|
||||
@@ -1027,22 +1109,15 @@ locate-path@^3.0.0:
|
||||
p-locate "^3.0.0"
|
||||
path-exists "^3.0.0"
|
||||
|
||||
locate-path@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
|
||||
integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
|
||||
dependencies:
|
||||
p-locate "^4.1.0"
|
||||
|
||||
lodash.isequal@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
|
||||
|
||||
lodash@^4.17.10:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
version "4.17.20"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||
|
||||
lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
|
||||
version "1.0.1"
|
||||
@@ -1054,12 +1129,12 @@ lowercase-keys@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
|
||||
integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
|
||||
|
||||
lru-cache@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
|
||||
integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
|
||||
lru-cache@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
|
||||
dependencies:
|
||||
yallist "^3.0.2"
|
||||
yallist "^4.0.0"
|
||||
|
||||
make-dir@^3.0.0:
|
||||
version "3.0.2"
|
||||
@@ -1075,10 +1150,10 @@ matcher@^2.1.0:
|
||||
dependencies:
|
||||
escape-string-regexp "^2.0.0"
|
||||
|
||||
mime@^2.4.4:
|
||||
version "2.4.4"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5"
|
||||
integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==
|
||||
mime@^2.4.6:
|
||||
version "2.4.7"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.7.tgz#962aed9be0ed19c91fd7dc2ece5d7f4e89a90d74"
|
||||
integrity sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA==
|
||||
|
||||
mimic-fn@^2.1.0:
|
||||
version "2.1.0"
|
||||
@@ -1102,7 +1177,7 @@ minimist@0.0.8:
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
|
||||
|
||||
minimist@^1.2.0:
|
||||
minimist@^1.2.0, minimist@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
@@ -1119,7 +1194,7 @@ ms@2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||
|
||||
ms@^2.1.1:
|
||||
ms@2.1.2, ms@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
@@ -1179,7 +1254,7 @@ p-cancelable@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
|
||||
integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
|
||||
|
||||
p-limit@^2.0.0, p-limit@^2.2.0:
|
||||
p-limit@^2.0.0:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e"
|
||||
integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==
|
||||
@@ -1193,13 +1268,6 @@ p-locate@^3.0.0:
|
||||
dependencies:
|
||||
p-limit "^2.0.0"
|
||||
|
||||
p-locate@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
|
||||
integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
|
||||
dependencies:
|
||||
p-limit "^2.2.0"
|
||||
|
||||
p-try@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||
@@ -1220,11 +1288,6 @@ path-exists@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
|
||||
integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
|
||||
|
||||
path-exists@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
|
||||
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
|
||||
|
||||
path-is-absolute@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||
@@ -1307,16 +1370,15 @@ rc@^1.2.8:
|
||||
minimist "^1.2.0"
|
||||
strip-json-comments "~2.0.1"
|
||||
|
||||
read-config-file@5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/read-config-file/-/read-config-file-5.0.2.tgz#55e005e5a447a9ce5806358d7b22cb7cefb6436d"
|
||||
integrity sha512-tVt1lsiSjs+FtL/vtfCivqtKR1UNk3BB3uPJQvJqkgtAYDvZjo0xyXFYSVmzaTcO+Jdi5G7O2K2vDV+p1M/oug==
|
||||
read-config-file@6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/read-config-file/-/read-config-file-6.0.0.tgz#224b5dca6a5bdc1fb19e63f89f342680efdb9299"
|
||||
integrity sha512-PHjROSdpceKUmqS06wqwP92VrM46PZSTubmNIMJ5DrMwg1OgenSTSEHIkCa6TiOJ+y/J0xnG1fFwG3M+Oi1aNA==
|
||||
dependencies:
|
||||
dotenv "^8.2.0"
|
||||
dotenv-expand "^5.1.0"
|
||||
fs-extra "^8.1.0"
|
||||
js-yaml "^3.13.1"
|
||||
json5 "^2.1.1"
|
||||
json5 "^2.1.2"
|
||||
lazy-val "^1.0.4"
|
||||
|
||||
readable-stream@^2.2.2, readable-stream@~2.3.6:
|
||||
@@ -1397,7 +1459,7 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3":
|
||||
"safer-buffer@>= 2.1.2 < 3.0.0":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
@@ -1441,6 +1503,13 @@ semver@^7.1.2, semver@^7.1.3:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.3.tgz#e4345ce73071c53f336445cfc19efb1c311df2a6"
|
||||
integrity sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==
|
||||
|
||||
semver@^7.3.2:
|
||||
version "7.3.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
|
||||
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
serialize-error@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-5.0.0.tgz#a7ebbcdb03a5d71a6ed8461ffe0fc1a1afed62ac"
|
||||
@@ -1470,10 +1539,10 @@ signal-exit@^3.0.2:
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
||||
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
|
||||
|
||||
source-map-support@^0.5.16:
|
||||
version "0.5.16"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
|
||||
integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==
|
||||
source-map-support@^0.5.19:
|
||||
version "0.5.19"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
|
||||
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
source-map "^0.6.0"
|
||||
@@ -1580,6 +1649,13 @@ sumchecker@^3.0.1:
|
||||
dependencies:
|
||||
debug "^4.1.0"
|
||||
|
||||
supports-color@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
|
||||
@@ -1664,10 +1740,10 @@ universalify@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d"
|
||||
integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==
|
||||
|
||||
update-notifier@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3"
|
||||
integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew==
|
||||
update-notifier@^4.1.1:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3"
|
||||
integrity sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==
|
||||
dependencies:
|
||||
boxen "^4.2.0"
|
||||
chalk "^3.0.0"
|
||||
@@ -1743,10 +1819,10 @@ wrap-ansi@^5.1.0:
|
||||
string-width "^3.0.0"
|
||||
strip-ansi "^5.0.0"
|
||||
|
||||
wrap-ansi@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
|
||||
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
@@ -1782,10 +1858,15 @@ y18n@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
|
||||
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
|
||||
|
||||
yallist@^3.0.2:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
||||
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
|
||||
y18n@^5.0.5:
|
||||
version "5.0.5"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.5.tgz#8769ec08d03b1ea2df2500acef561743bbb9ab18"
|
||||
integrity sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==
|
||||
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yargs-parser@^13.1.2:
|
||||
version "13.1.2"
|
||||
@@ -1795,13 +1876,10 @@ yargs-parser@^13.1.2:
|
||||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs-parser@^18.1.0:
|
||||
version "18.1.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.0.tgz#1b0ab1118ebd41f68bb30e729f4c83df36ae84c3"
|
||||
integrity sha512-o/Jr6JBOv6Yx3pL+5naWSoIA2jJ+ZkMYQG/ie9qFbukBe4uzmBatlXFOiu/tNKRWEtyf+n5w7jc/O16ufqOTdQ==
|
||||
dependencies:
|
||||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
yargs-parser@^20.2.2:
|
||||
version "20.2.4"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"
|
||||
integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==
|
||||
|
||||
yargs@^13.2.4:
|
||||
version "13.3.2"
|
||||
@@ -1819,22 +1897,18 @@ yargs@^13.2.4:
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^13.1.2"
|
||||
|
||||
yargs@^15.1.0:
|
||||
version "15.3.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.0.tgz#403af6edc75b3ae04bf66c94202228ba119f0976"
|
||||
integrity sha512-g/QCnmjgOl1YJjGsnUg2SatC7NUYEiLXJqxNOQU9qSpjzGtGXda9b+OKccr1kLTy8BN9yqEyqfq5lxlwdc13TA==
|
||||
yargs@^16.0.3:
|
||||
version "16.2.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
|
||||
integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
|
||||
dependencies:
|
||||
cliui "^6.0.0"
|
||||
decamelize "^1.2.0"
|
||||
find-up "^4.1.0"
|
||||
get-caller-file "^2.0.1"
|
||||
cliui "^7.0.2"
|
||||
escalade "^3.1.1"
|
||||
get-caller-file "^2.0.5"
|
||||
require-directory "^2.1.1"
|
||||
require-main-filename "^2.0.0"
|
||||
set-blocking "^2.0.0"
|
||||
string-width "^4.2.0"
|
||||
which-module "^2.0.0"
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^18.1.0"
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^20.2.2"
|
||||
|
||||
yauzl@2.4.1:
|
||||
version "2.4.1"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"scripts": {
|
||||
"start:api": "yarn workspace dbgate-api start",
|
||||
"start:api:portal": "yarn workspace dbgate-api start:portal",
|
||||
"start:api:covid": "yarn workspace dbgate-api start:covid",
|
||||
"start:web": "yarn workspace dbgate-web start",
|
||||
"start:sqltree": "yarn workspace dbgate-sqltree start",
|
||||
"start:tools": "yarn workspace dbgate-tools start",
|
||||
@@ -22,6 +23,7 @@
|
||||
"build:web:docker": "yarn workspace dbgate-web build:docker",
|
||||
"build:app:local": "cd app && yarn build:local",
|
||||
"start:app:local": "cd app && yarn start:local",
|
||||
"setCurrentVersion": "node setCurrentVersion",
|
||||
|
||||
"copy:docker:build": "copyfiles packages/api/dist/* docker -f && copyfiles packages/web/build/* docker -u 2 && copyfiles \"packages/web/build/**/*\" docker -u 2",
|
||||
"prepare:docker": "yarn build:web:docker && yarn build:api && yarn copy:docker:build",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
CONNECTIONS=mysql
|
||||
|
||||
LABEL_mysql=MySql
|
||||
SERVER_mysql=dbgate.org
|
||||
USER_mysql=reader
|
||||
PASSWORD_mysql=CovidReader2020
|
||||
PORT_mysql=3326
|
||||
ENGINE_mysql=mysql@dbgate-plugin-mysql
|
||||
SINGLE_CONNECTION=mysql
|
||||
SINGLE_DATABASE=covid
|
||||
|
||||
PERMISSIONS=files/charts/read
|
||||
@@ -0,0 +1,2 @@
|
||||
version-tag-prefix packages-api-v
|
||||
version-git-message "packages-api v%s"
|
||||
+8
-40
@@ -11,15 +11,19 @@ Allows run DbGate data-manipulation scripts.
|
||||
This example exports table Customer info CSV file.
|
||||
|
||||
```javascript
|
||||
|
||||
const dbgateApi = require('dbgate-api');
|
||||
const dbgatePluginMssql = require("dbgate-plugin-mssql");
|
||||
const dbgatePluginCsv = require("dbgate-plugin-csv");
|
||||
|
||||
dbgateApi.registerPlugins(dbgatePluginMssql);
|
||||
|
||||
async function run() {
|
||||
const reader = await dbgateApi.tableReader({
|
||||
connection: { server: 'localhost', engine: 'mssql', user: 'sa', password: 'xxxx', database: 'Chinook' },
|
||||
schemaName: 'dbo',
|
||||
pureName: 'Customer',
|
||||
});
|
||||
const writer = await dbgateApi.csvWriter({ fileName: 'Customer.csv' });
|
||||
const writer = await dbgatePluginCsv.shellApi.writer({ fileName: 'Customer.csv' });
|
||||
await dbgateApi.copyStream(reader, writer);
|
||||
|
||||
console.log('Finished job script');
|
||||
@@ -88,36 +92,11 @@ Imports data into table. Options are optional, default values are false.
|
||||
});
|
||||
```
|
||||
|
||||
### dbgateApi.csvReader
|
||||
Reads CSV file
|
||||
```js
|
||||
const reader = await dbgateApi.csvReader({
|
||||
fileName: '/home/root/test.csv',
|
||||
encoding: 'utf-8',
|
||||
header: true,
|
||||
delimiter: ',',
|
||||
quoted: false,
|
||||
limitRows: null
|
||||
});
|
||||
```
|
||||
|
||||
### dbgateApi.csvWriter
|
||||
Writes CSV file
|
||||
```js
|
||||
const reader = await dbgateApi.csvWriter({
|
||||
fileName: '/home/root/test.csv',
|
||||
encoding: 'utf-8',
|
||||
header: true,
|
||||
delimiter: ',',
|
||||
quoted: false
|
||||
});
|
||||
```
|
||||
|
||||
### dbgateApi.jsonLinesReader
|
||||
Reads JSON lines data file. On first line could be structure. Every line contains one row as JSON serialized object.
|
||||
```js
|
||||
const reader = await dbgateApi.jsonLinesReader({
|
||||
fileName: '/home/root/test.jsonl',
|
||||
fileName: 'test.jsonl',
|
||||
encoding: 'utf-8',
|
||||
header: true,
|
||||
limitRows: null
|
||||
@@ -128,19 +107,8 @@ Reads JSON lines data file. On first line could be structure. Every line contain
|
||||
Writes JSON lines data file. On first line could be structure. Every line contains one row as JSON serialized object.
|
||||
```js
|
||||
const reader = await dbgateApi.jsonLinesWriter({
|
||||
fileName: '/home/root/test.jsonl',
|
||||
fileName: 'test.jsonl',
|
||||
encoding: 'utf-8',
|
||||
header: true
|
||||
});
|
||||
```
|
||||
|
||||
### dbgateApi.excelSheetReader
|
||||
Reads tabular data from one sheet in MS Excel file.
|
||||
```js
|
||||
const reader = await dbgateApi.excelSheetReader({
|
||||
fileName: '/home/root/test.xlsx',
|
||||
sheetName: 'Album',
|
||||
limitRows: null
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "dbgate-api",
|
||||
"main": "src/index.js",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.7",
|
||||
"homepage": "https://dbgate.org/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -38,11 +38,14 @@
|
||||
"lodash": "^4.17.15",
|
||||
"ncp": "^2.0.0",
|
||||
"nedb-promises": "^4.0.1",
|
||||
"tar": "^6.0.5"
|
||||
"node-cron": "^2.0.3",
|
||||
"tar": "^6.0.5",
|
||||
"uuid": "^3.4.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "nodemon src/index.js",
|
||||
"start:portal": "env-cmd nodemon src/index.js",
|
||||
"start:covid": "env-cmd -f .covid-env nodemon src/index.js",
|
||||
"ts": "tsc",
|
||||
"build": "webpack"
|
||||
},
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
const currentVersion = require('../currentVersion');
|
||||
|
||||
module.exports = {
|
||||
get_meta: 'get',
|
||||
async get() {
|
||||
const toolbarButtons = process.env.TOOLBAR;
|
||||
const toolbar = toolbarButtons
|
||||
? toolbarButtons.split(',').map((name) => ({
|
||||
name,
|
||||
icon: process.env[`ICON_${name}`],
|
||||
title: process.env[`TITLE_${name}`],
|
||||
page: process.env[`PAGE_${name}`],
|
||||
}))
|
||||
: null;
|
||||
const startupPages = process.env.STARTUP_PAGES ? process.env.STARTUP_PAGES.split(',') : [];
|
||||
// const toolbarButtons = process.env.TOOLBAR;
|
||||
// const toolbar = toolbarButtons
|
||||
// ? toolbarButtons.split(',').map((name) => ({
|
||||
// name,
|
||||
// icon: process.env[`ICON_${name}`],
|
||||
// title: process.env[`TITLE_${name}`],
|
||||
// page: process.env[`PAGE_${name}`],
|
||||
// }))
|
||||
// : null;
|
||||
// const startupPages = process.env.STARTUP_PAGES ? process.env.STARTUP_PAGES.split(',') : [];
|
||||
const permissions = process.env.PERMISSIONS ? process.env.PERMISSIONS.split(',') : null;
|
||||
const singleDatabase =
|
||||
process.env.SINGLE_CONNECTION && process.env.SINGLE_DATABASE
|
||||
? {
|
||||
conid: process.env.SINGLE_CONNECTION,
|
||||
database: process.env.SINGLE_DATABASE,
|
||||
}
|
||||
: null;
|
||||
|
||||
return {
|
||||
runAsPortal: !!process.env.CONNECTIONS,
|
||||
toolbar,
|
||||
startupPages,
|
||||
// toolbar,
|
||||
// startupPages,
|
||||
singleDatabase,
|
||||
permissions,
|
||||
...currentVersion,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ const { DatabaseAnalyser } = require('dbgate-tools');
|
||||
module.exports = {
|
||||
/** @type {import('dbgate-types').OpenedDatabaseConnection[]} */
|
||||
opened: [],
|
||||
closed: [],
|
||||
closed: {},
|
||||
requests: {},
|
||||
|
||||
handle_structure(conid, database, { structure }) {
|
||||
@@ -39,7 +39,7 @@ module.exports = {
|
||||
if (existing) return existing;
|
||||
const connection = await connections.get({ conid });
|
||||
const subprocess = fork(process.argv[1], ['databaseConnectionProcess']);
|
||||
const lastClosed = this.closed.find((x) => x.conid == conid && x.database == database);
|
||||
const lastClosed = this.closed[`${conid}/${database}`];
|
||||
const newOpened = {
|
||||
conid,
|
||||
database,
|
||||
@@ -89,6 +89,8 @@ module.exports = {
|
||||
async status({ conid, database }) {
|
||||
const existing = this.opened.find((x) => x.conid == conid && x.database == database);
|
||||
if (existing) return existing.status;
|
||||
const lastClosed = this.closed[`${conid}/${database}`];
|
||||
if (lastClosed) return lastClosed.status;
|
||||
return {
|
||||
name: 'error',
|
||||
message: 'Not connected',
|
||||
@@ -118,7 +120,7 @@ module.exports = {
|
||||
existing.disconnected = true;
|
||||
if (kill) existing.subprocess.kill();
|
||||
this.opened = this.opened.filter((x) => x.conid != conid || x.database != database);
|
||||
this.closed[conid] = {
|
||||
this.closed[`${conid}/${database}`] = {
|
||||
status: {
|
||||
...existing.status,
|
||||
name: 'error',
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const { filesdir } = require('../utility/directories');
|
||||
const hasPermission = require('../utility/hasPermission');
|
||||
const socket = require('../utility/socket');
|
||||
const scheduler = require('./scheduler');
|
||||
|
||||
function serialize(format, data) {
|
||||
if (format == 'text') return data;
|
||||
if (format == 'json') return JSON.stringify(data);
|
||||
throw new Error(`Invalid format: ${format}`);
|
||||
}
|
||||
|
||||
function deserialize(format, text) {
|
||||
if (format == 'text') return text;
|
||||
if (format == 'json') return JSON.parse(text);
|
||||
throw new Error(`Invalid format: ${format}`);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
list_meta: 'get',
|
||||
async list({ folder }) {
|
||||
if (!hasPermission(`files/${folder}/read`)) return [];
|
||||
const dir = path.join(filesdir(), folder);
|
||||
if (!(await fs.exists(dir))) return [];
|
||||
const files = (await fs.readdir(dir)).map((file) => ({ folder, file }));
|
||||
return files;
|
||||
},
|
||||
|
||||
listAll_meta: 'get',
|
||||
async listAll() {
|
||||
const folders = await fs.readdir(filesdir());
|
||||
const res = [];
|
||||
for (const folder of folders) {
|
||||
if (!hasPermission(`files/${folder}/read`)) continue;
|
||||
const dir = path.join(filesdir(), folder);
|
||||
const files = (await fs.readdir(dir)).map((file) => ({ folder, file }));
|
||||
res.push(...files);
|
||||
}
|
||||
return res;
|
||||
},
|
||||
|
||||
delete_meta: 'post',
|
||||
async delete({ folder, file }) {
|
||||
if (!hasPermission(`files/${folder}/write`)) return;
|
||||
await fs.unlink(path.join(filesdir(), folder, file));
|
||||
socket.emitChanged(`files-changed-${folder}`);
|
||||
socket.emitChanged(`all-files-changed`);
|
||||
},
|
||||
|
||||
rename_meta: 'post',
|
||||
async rename({ folder, file, newFile }) {
|
||||
if (!hasPermission(`files/${folder}/write`)) return;
|
||||
await fs.rename(path.join(filesdir(), folder, file), path.join(filesdir(), folder, newFile));
|
||||
socket.emitChanged(`files-changed-${folder}`);
|
||||
socket.emitChanged(`all-files-changed`);
|
||||
},
|
||||
|
||||
load_meta: 'post',
|
||||
async load({ folder, file, format }) {
|
||||
if (!hasPermission(`files/${folder}/read`)) return null;
|
||||
const text = await fs.readFile(path.join(filesdir(), folder, file), { encoding: 'utf-8' });
|
||||
return deserialize(format, text);
|
||||
},
|
||||
|
||||
save_meta: 'post',
|
||||
async save({ folder, file, data, format }) {
|
||||
if (!hasPermission(`files/${folder}/write`)) return;
|
||||
const dir = path.join(filesdir(), folder);
|
||||
if (!(await fs.exists(dir))) {
|
||||
await fs.mkdir(dir);
|
||||
}
|
||||
await fs.writeFile(path.join(dir, file), serialize(format, data));
|
||||
socket.emitChanged(`files-changed-${folder}`);
|
||||
socket.emitChanged(`all-files-changed`);
|
||||
if (folder == 'shell') {
|
||||
scheduler.reload();
|
||||
}
|
||||
},
|
||||
|
||||
favorites_meta: 'get',
|
||||
async favorites() {
|
||||
if (!hasPermission(`files/favorites/read`)) return [];
|
||||
const dir = path.join(filesdir(), 'favorites');
|
||||
if (!(await fs.exists(dir))) return [];
|
||||
const files = await fs.readdir(dir);
|
||||
const res = [];
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dir, file);
|
||||
const text = await fs.readFile(filePath, { encoding: 'utf-8' });
|
||||
res.push({
|
||||
file,
|
||||
folder: 'favorites',
|
||||
...JSON.parse(text),
|
||||
});
|
||||
}
|
||||
return res;
|
||||
},
|
||||
};
|
||||
@@ -5,6 +5,7 @@ const { pluginsdir, datadir } = require('../utility/directories');
|
||||
const socket = require('../utility/socket');
|
||||
const requirePlugin = require('../shell/requirePlugin');
|
||||
const downloadPackage = require('../utility/downloadPackage');
|
||||
const hasPermission = require('../utility/hasPermission');
|
||||
|
||||
// async function loadPackageInfo(dir) {
|
||||
// const readmeFile = path.join(dir, 'README.md');
|
||||
@@ -106,6 +107,7 @@ module.exports = {
|
||||
|
||||
install_meta: 'post',
|
||||
async install({ packageName }) {
|
||||
if (!hasPermission(`plugins/install`)) return;
|
||||
const dir = path.join(pluginsdir(), packageName);
|
||||
if (!(await fs.exists(dir))) {
|
||||
await downloadPackage(packageName, dir);
|
||||
@@ -115,6 +117,7 @@ module.exports = {
|
||||
|
||||
uninstall_meta: 'post',
|
||||
async uninstall({ packageName }) {
|
||||
if (!hasPermission(`plugins/install`)) return;
|
||||
const dir = path.join(pluginsdir(), packageName);
|
||||
await fs.rmdir(dir, { recursive: true });
|
||||
socket.emitChanged(`installed-plugins-changed`);
|
||||
@@ -139,6 +142,7 @@ module.exports = {
|
||||
}
|
||||
for (const packageName of preinstallPlugins) {
|
||||
if (this.removedPlugins.includes(packageName)) continue;
|
||||
if (installed.find((x) => x.name == packageName)) continue;
|
||||
try {
|
||||
console.log('Preinstalling plugin', packageName);
|
||||
await this.install({ packageName });
|
||||
|
||||
@@ -91,6 +91,7 @@ module.exports = {
|
||||
fs.mkdirSync(directory);
|
||||
const pluginNames = fs.readdirSync(pluginsdir());
|
||||
console.log(`RUNNING SCRIPT ${scriptFile}`);
|
||||
// const subprocess = fork(scriptFile, ['--checkParent', '--max-old-space-size=8192'], {
|
||||
const subprocess = fork(scriptFile, ['--checkParent'], {
|
||||
cwd: directory,
|
||||
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
const { filesdir } = require('../utility/directories');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const cron = require('node-cron');
|
||||
const runners = require('./runners');
|
||||
const hasPermission = require('../utility/hasPermission');
|
||||
|
||||
const scheduleRegex = /\s*\/\/\s*@schedule\s+([^\n]+)\n/;
|
||||
|
||||
module.exports = {
|
||||
tasks: [],
|
||||
|
||||
async unload() {
|
||||
this.tasks.forEach((x) => x.destroy());
|
||||
this.tasks = [];
|
||||
},
|
||||
|
||||
async processFile(file) {
|
||||
const text = await fs.readFile(file, { encoding: 'utf-8' });
|
||||
const match = text.match(scheduleRegex);
|
||||
if (!match) return;
|
||||
const pattern = match[1];
|
||||
if (!cron.validate(pattern)) return;
|
||||
console.log(`Schedule script ${file} with pattern ${pattern}`);
|
||||
const task = cron.schedule(pattern, () => runners.start({ script: text }));
|
||||
this.tasks.push(task);
|
||||
},
|
||||
|
||||
async reload() {
|
||||
if (!hasPermission('files/shell/read')) return;
|
||||
const shellDir = path.join(filesdir(), 'shell');
|
||||
await this.unload();
|
||||
if (!(await fs.exists(shellDir))) return;
|
||||
const files = await fs.readdir(shellDir);
|
||||
for (const file of files) {
|
||||
await this.processFile(path.join(shellDir, file));
|
||||
}
|
||||
},
|
||||
|
||||
async _init() {
|
||||
this.reload();
|
||||
},
|
||||
};
|
||||
@@ -19,9 +19,6 @@ module.exports = {
|
||||
existing.status = status;
|
||||
socket.emitChanged(`server-status-changed`);
|
||||
},
|
||||
handle_error(conid, { error }) {
|
||||
console.log(`Error in server connection ${conid}: ${error}`);
|
||||
},
|
||||
handle_ping() {},
|
||||
|
||||
async ensureOpened(conid) {
|
||||
|
||||
@@ -95,15 +95,15 @@ module.exports = {
|
||||
return { state: 'ok' };
|
||||
},
|
||||
|
||||
cancel_meta: 'post',
|
||||
async cancel({ sesid }) {
|
||||
const session = this.opened.find((x) => x.sesid == sesid);
|
||||
if (!session) {
|
||||
throw new Error('Invalid session');
|
||||
}
|
||||
session.subprocess.send({ msgtype: 'cancel' });
|
||||
return { state: 'ok' };
|
||||
},
|
||||
// cancel_meta: 'post',
|
||||
// async cancel({ sesid }) {
|
||||
// const session = this.opened.find((x) => x.sesid == sesid);
|
||||
// if (!session) {
|
||||
// throw new Error('Invalid session');
|
||||
// }
|
||||
// session.subprocess.send({ msgtype: 'cancel' });
|
||||
// return { state: 'ok' };
|
||||
// },
|
||||
|
||||
kill_meta: 'post',
|
||||
async kill({ sesid }) {
|
||||
@@ -112,6 +112,7 @@ module.exports = {
|
||||
throw new Error('Invalid session');
|
||||
}
|
||||
session.subprocess.kill();
|
||||
this.dispatchMessage(sesid, 'Connection closed');
|
||||
return { state: 'ok' };
|
||||
},
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
module.exports = {
|
||||
version: '3.8.6',
|
||||
buildTime: '2020-12-10T11:14:01.053Z'
|
||||
};
|
||||
@@ -23,6 +23,8 @@ const config = require('./controllers/config');
|
||||
const archive = require('./controllers/archive');
|
||||
const uploads = require('./controllers/uploads');
|
||||
const plugins = require('./controllers/plugins');
|
||||
const files = require('./controllers/files');
|
||||
const scheduler = require('./controllers/scheduler');
|
||||
|
||||
const { rundir } = require('./utility/directories');
|
||||
|
||||
@@ -67,10 +69,12 @@ function start(argument = null) {
|
||||
useController(app, '/archive', archive);
|
||||
useController(app, '/uploads', uploads);
|
||||
useController(app, '/plugins', plugins);
|
||||
useController(app, '/files', files);
|
||||
useController(app, '/scheduler', scheduler);
|
||||
|
||||
if (process.env.PAGES_DIRECTORY) {
|
||||
app.use('/pages', express.static(process.env.PAGES_DIRECTORY));
|
||||
}
|
||||
// if (process.env.PAGES_DIRECTORY) {
|
||||
// app.use('/pages', express.static(process.env.PAGES_DIRECTORY));
|
||||
// }
|
||||
|
||||
app.use('/runners/data', express.static(rundir()));
|
||||
|
||||
|
||||
@@ -96,8 +96,11 @@ function start() {
|
||||
process.on('message', async (message) => {
|
||||
try {
|
||||
await handleMessage(message);
|
||||
} catch (e) {
|
||||
process.send({ msgtype: 'error', error: e.message });
|
||||
} catch (err) {
|
||||
setStatus({
|
||||
name: 'error',
|
||||
message: err.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
let systemConnection;
|
||||
let storedConnection;
|
||||
let afterConnectCallbacks = [];
|
||||
let currentHandlers = [];
|
||||
// let currentHandlers = [];
|
||||
|
||||
class TableWriter {
|
||||
constructor(columns, resultIndex) {
|
||||
@@ -64,17 +64,20 @@ class TableWriter {
|
||||
}
|
||||
|
||||
class StreamHandler {
|
||||
constructor(resultIndex) {
|
||||
constructor(resultIndexHolder, resolve) {
|
||||
this.recordset = this.recordset.bind(this);
|
||||
this.row = this.row.bind(this);
|
||||
// this.error = this.error.bind(this);
|
||||
this.done = this.done.bind(this);
|
||||
this.info = this.info.bind(this);
|
||||
// use this for cancelling
|
||||
this.stream = null;
|
||||
|
||||
// use this for cancelling - not implemented
|
||||
// this.stream = null;
|
||||
|
||||
this.plannedStats = false;
|
||||
this.resultIndex = resultIndex;
|
||||
currentHandlers = [...currentHandlers, this];
|
||||
this.resultIndexHolder = resultIndexHolder;
|
||||
this.resolve = resolve;
|
||||
// currentHandlers = [...currentHandlers, this];
|
||||
}
|
||||
|
||||
closeCurrentWriter() {
|
||||
@@ -86,7 +89,8 @@ class StreamHandler {
|
||||
|
||||
recordset(columns) {
|
||||
this.closeCurrentWriter();
|
||||
this.currentWriter = new TableWriter(columns, this.resultIndex);
|
||||
this.currentWriter = new TableWriter(columns, this.resultIndexHolder.value);
|
||||
this.resultIndexHolder.value += 1;
|
||||
|
||||
// this.writeCurrentStats();
|
||||
|
||||
@@ -107,14 +111,21 @@ class StreamHandler {
|
||||
// }
|
||||
done(result) {
|
||||
this.closeCurrentWriter();
|
||||
process.send({ msgtype: 'done', result });
|
||||
currentHandlers = currentHandlers.filter((x) => x != this);
|
||||
// currentHandlers = currentHandlers.filter((x) => x != this);
|
||||
this.resolve();
|
||||
}
|
||||
info(info) {
|
||||
process.send({ msgtype: 'info', info });
|
||||
}
|
||||
}
|
||||
|
||||
function handleStream(driver, resultIndexHolder, sql) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const handler = new StreamHandler(resultIndexHolder, resolve);
|
||||
driver.stream(systemConnection, sql, handler);
|
||||
});
|
||||
}
|
||||
|
||||
async function handleConnect(connection) {
|
||||
storedConnection = connection;
|
||||
|
||||
@@ -126,11 +137,11 @@ async function handleConnect(connection) {
|
||||
afterConnectCallbacks = [];
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
for (const handler of currentHandlers) {
|
||||
if (handler.stream) handler.stream.cancel();
|
||||
}
|
||||
}
|
||||
// function handleCancel() {
|
||||
// for (const handler of currentHandlers) {
|
||||
// if (handler.stream) handler.stream.cancel();
|
||||
// }
|
||||
// }
|
||||
|
||||
function waitConnected() {
|
||||
if (systemConnection) return Promise.resolve();
|
||||
@@ -143,19 +154,23 @@ async function handleExecuteQuery({ sql }) {
|
||||
await waitConnected();
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
|
||||
let resultIndex = 0;
|
||||
const resultIndexHolder = {
|
||||
value: 0,
|
||||
};
|
||||
for (const sqlItem of goSplit(sql)) {
|
||||
const handler = new StreamHandler(resultIndex);
|
||||
const stream = await driver.stream(systemConnection, sqlItem, handler);
|
||||
handler.stream = stream;
|
||||
resultIndex += 1;
|
||||
await handleStream(driver, resultIndexHolder, sqlItem);
|
||||
// const handler = new StreamHandler(resultIndex);
|
||||
// const stream = await driver.stream(systemConnection, sqlItem, handler);
|
||||
// handler.stream = stream;
|
||||
// resultIndex = handler.resultIndex;
|
||||
}
|
||||
process.send({ msgtype: 'done' });
|
||||
}
|
||||
|
||||
const messageHandlers = {
|
||||
connect: handleConnect,
|
||||
executeQuery: handleExecuteQuery,
|
||||
cancel: handleCancel,
|
||||
// cancel: handleCancel,
|
||||
};
|
||||
|
||||
async function handleMessage({ msgtype, ...other }) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
const path = require('path');
|
||||
const uuidv1 = require('uuid/v1');
|
||||
const { uploadsdir } = require('../utility/directories');
|
||||
const { downloadFile } = require('../utility/downloader');
|
||||
|
||||
async function download(url) {
|
||||
if (url && url.match(/(^http:\/\/)|(^https:\/\/)/)) {
|
||||
const tmpFile = path.join(uploadsdir(), uuidv1());
|
||||
await downloadFile(url, tmpFile);
|
||||
return tmpFile;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
module.exports = download;
|
||||
@@ -0,0 +1,19 @@
|
||||
const goSplit = require('../utility/goSplit');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
|
||||
async function executeQuery({ connection, sql }) {
|
||||
console.log(`Execute query ${sql}`);
|
||||
|
||||
const driver = requireEngineDriver(connection);
|
||||
const pool = await driver.connect(connection);
|
||||
console.log(`Connected.`);
|
||||
|
||||
for (const sqlItem of goSplit(sql)) {
|
||||
console.log('Executing query', sqlItem);
|
||||
await driver.query(pool, sqlItem);
|
||||
}
|
||||
|
||||
console.log(`Query finished`);
|
||||
}
|
||||
|
||||
module.exports = executeQuery;
|
||||
@@ -14,6 +14,9 @@ const collectorWriter = require('./collectorWriter');
|
||||
const finalizer = require('./finalizer');
|
||||
const registerPlugins = require('./registerPlugins');
|
||||
const requirePlugin = require('./requirePlugin');
|
||||
const download = require('./download');
|
||||
const executeQuery = require('./executeQuery');
|
||||
const loadFile = require('./loadFile');
|
||||
|
||||
const dbgateApi = {
|
||||
queryReader,
|
||||
@@ -30,7 +33,10 @@ const dbgateApi = {
|
||||
archiveReader,
|
||||
collectorWriter,
|
||||
finalizer,
|
||||
download,
|
||||
registerPlugins,
|
||||
executeQuery,
|
||||
loadFile,
|
||||
};
|
||||
|
||||
requirePlugin.initialize(dbgateApi);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const { filesdir } = require('../utility/directories');
|
||||
|
||||
async function loadFile(file) {
|
||||
const text = await fs.readFile(path.join(filesdir(), file), { encoding: 'utf-8' });
|
||||
return text;
|
||||
}
|
||||
|
||||
module.exports = loadFile;
|
||||
@@ -1,4 +1,4 @@
|
||||
const { quoteFullName } = require('dbgate-tools');
|
||||
const { quoteFullName, fullNameToString } = require('dbgate-tools');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
|
||||
async function tableReader({ connection, pureName, schemaName }) {
|
||||
@@ -11,13 +11,15 @@ async function tableReader({ connection, pureName, schemaName }) {
|
||||
const table = await driver.analyseSingleObject(pool, fullName, 'tables');
|
||||
const query = `select * from ${quoteFullName(driver.dialect, fullName)}`;
|
||||
if (table) {
|
||||
console.log(`Reading table ${table.pureName}`);
|
||||
// @ts-ignore
|
||||
console.log(`Reading table ${fullNameToString(table)}`);
|
||||
// @ts-ignore
|
||||
return await driver.readQuery(pool, query, table);
|
||||
}
|
||||
const view = await driver.analyseSingleObject(pool, fullName, 'views');
|
||||
if (view) {
|
||||
console.log(`Reading view ${view.pureName}`);
|
||||
// @ts-ignore
|
||||
console.log(`Reading view ${fullNameToString(view)}`);
|
||||
// @ts-ignore
|
||||
return await driver.readQuery(pool, query, view);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
const requireEngineDriver = require("../utility/requireEngineDriver");
|
||||
const { fullNameToString } = require('dbgate-tools');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
|
||||
async function tableWriter({ connection, schemaName, pureName, ...options }) {
|
||||
console.log(`Write table ${schemaName}.${pureName}`);
|
||||
console.log(`Writing table ${fullNameToString({ schemaName, pureName })}`);
|
||||
|
||||
const driver = requireEngineDriver(connection);
|
||||
const pool = await driver.connect(connection);
|
||||
|
||||
@@ -37,6 +37,7 @@ const rundir = dirFunc('run', true);
|
||||
const uploadsdir = dirFunc('uploads', true);
|
||||
const pluginsdir = dirFunc('plugins');
|
||||
const archivedir = dirFunc('archive');
|
||||
const filesdir = dirFunc('files');
|
||||
|
||||
module.exports = {
|
||||
datadir,
|
||||
@@ -46,4 +47,5 @@ module.exports = {
|
||||
archivedir,
|
||||
ensureDirectory,
|
||||
pluginsdir,
|
||||
filesdir,
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ const zlib = require('zlib');
|
||||
const tar = require('tar');
|
||||
const ncp = require('ncp').ncp;
|
||||
const { uploadsdir } = require('./directories');
|
||||
const { downloadFile } = require('./downloader');
|
||||
|
||||
function extractTarball(tmpFile, destination) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -19,13 +20,6 @@ function extractTarball(tmpFile, destination) {
|
||||
});
|
||||
}
|
||||
|
||||
function saveStreamToFile(pipedStream, fileName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileStream = fs.createWriteStream(fileName);
|
||||
fileStream.on('close', () => resolve());
|
||||
pipedStream.pipe(fileStream);
|
||||
});
|
||||
}
|
||||
|
||||
function copyDirectory(source, target) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -46,13 +40,7 @@ async function downloadPackage(packageName, directory) {
|
||||
const tarball = infoResp.data.versions[latest].dist.tarball;
|
||||
|
||||
const tmpFile = path.join(uploadsdir(), uuidv1() + '.tgz');
|
||||
console.log(`Downloading tarball ${tarball} into ${tmpFile}`);
|
||||
const tarballResp = await axios.default({
|
||||
method: 'get',
|
||||
url: tarball,
|
||||
responseType: 'stream',
|
||||
});
|
||||
await saveStreamToFile(tarballResp.data, tmpFile);
|
||||
await downloadFile(tarball, tmpFile);
|
||||
const tmpDir = path.join(uploadsdir(), uuidv1());
|
||||
fs.mkdirSync(tmpDir);
|
||||
await extractTarball(tmpFile, tmpDir);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
|
||||
function saveStreamToFile(pipedStream, fileName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileStream = fs.createWriteStream(fileName);
|
||||
fileStream.on('close', () => resolve());
|
||||
pipedStream.pipe(fileStream);
|
||||
});
|
||||
}
|
||||
|
||||
async function downloadFile(url, file) {
|
||||
console.log(`Downloading ${url} into ${file}`);
|
||||
const tarballResp = await axios.default({
|
||||
method: 'get',
|
||||
url,
|
||||
responseType: 'stream',
|
||||
});
|
||||
await saveStreamToFile(tarballResp.data, file);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
saveStreamToFile,
|
||||
downloadFile,
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
const { compilePermissions, testPermission } = require('dbgate-tools');
|
||||
|
||||
let compiled = undefined;
|
||||
|
||||
function hasPermission(tested) {
|
||||
if (compiled === undefined) {
|
||||
compiled = compilePermissions(process.env.PERMISSIONS);
|
||||
}
|
||||
return testPermission(tested, compiled);
|
||||
}
|
||||
|
||||
module.exports = hasPermission;
|
||||
@@ -433,9 +433,10 @@ export abstract class GridDisplay {
|
||||
return sql;
|
||||
}
|
||||
|
||||
getExportQuery() {
|
||||
getExportQuery(postprocessSelect = null) {
|
||||
const select = this.createSelect({ isExport: true });
|
||||
if (!select) return null;
|
||||
if (postprocessSelect) postprocessSelect(select);
|
||||
const sql = treeToSql(this.driver, select, dumpSqlSelect);
|
||||
return sql;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
version-tag-prefix packages-sqlitree-v
|
||||
version-git-message "packages-sqlitree v%s"
|
||||
@@ -8,7 +8,7 @@ dbgate-sqltree hold query definition in RAW JSON objects.
|
||||
|
||||
```javascript
|
||||
const { treeToSql, dumpSqlSelect } = require("dbgate-sqltree");
|
||||
const engines = require("dbgate-engines");
|
||||
const dbgatePluginMysql = require("dbgate-plugin-mysql");
|
||||
|
||||
const select = {
|
||||
commandType: "select",
|
||||
@@ -32,7 +32,7 @@ const select = {
|
||||
],
|
||||
};
|
||||
|
||||
const sql = treeToSql(engines("mysql"), select, dumpSqlSelect);
|
||||
const sql = treeToSql(dbgatePluginMysql.driver, select, dumpSqlSelect);
|
||||
console.log("Generated query:", sql);
|
||||
|
||||
```
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"name": "dbgate-sqltree",
|
||||
"main": "lib/index.js",
|
||||
"typings": "lib/index.d.ts",
|
||||
|
||||
@@ -7,7 +7,7 @@ import { dumpSqlCondition } from './dumpSqlCondition';
|
||||
export function dumpSqlSelect(dmp: SqlDumper, cmd: Select) {
|
||||
dmp.put('^select ');
|
||||
if (cmd.topRecords) {
|
||||
dmp.put('^top %s ', cmd.topRecords);
|
||||
if (!dmp.dialect.rangeSelect || dmp.dialect.offsetFetchRangeSyntax) dmp.put('^top %s ', cmd.topRecords);
|
||||
}
|
||||
if (cmd.distinct) {
|
||||
dmp.put('^distinct ');
|
||||
@@ -36,6 +36,11 @@ export function dumpSqlSelect(dmp: SqlDumper, cmd: Select) {
|
||||
dmp.putCollection(', ', cmd.groupBy, (expr) => dumpSqlExpression(dmp, expr));
|
||||
dmp.put('&n');
|
||||
}
|
||||
if (cmd.having) {
|
||||
dmp.put('&n^having ');
|
||||
dumpSqlCondition(dmp, cmd.having);
|
||||
dmp.put('&n');
|
||||
}
|
||||
if (cmd.orderBy) {
|
||||
dmp.put('&n^order ^by ');
|
||||
dmp.putCollection(', ', cmd.orderBy, (expr) => {
|
||||
@@ -51,6 +56,9 @@ export function dumpSqlSelect(dmp: SqlDumper, cmd: Select) {
|
||||
dmp.put('^limit %s ^offset %s ', cmd.range.limit, cmd.range.offset);
|
||||
}
|
||||
}
|
||||
if (cmd.topRecords) {
|
||||
if (dmp.dialect.rangeSelect && !dmp.dialect.offsetFetchRangeSyntax) dmp.put('^limit %s ', cmd.topRecords);
|
||||
}
|
||||
}
|
||||
|
||||
export function dumpSqlUpdate(dmp: SqlDumper, cmd: Update) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { SqlDumper } from 'dbgate-types';
|
||||
import { Condition, BinaryCondition } from './types';
|
||||
import { dumpSqlExpression } from './dumpSqlExpression';
|
||||
import { link } from 'fs';
|
||||
import { dumpSqlSelect } from './dumpSqlCommand';
|
||||
|
||||
export function dumpSqlCondition(dmp: SqlDumper, condition: Condition) {
|
||||
switch (condition.conditionType) {
|
||||
@@ -30,7 +31,7 @@ export function dumpSqlCondition(dmp: SqlDumper, condition: Condition) {
|
||||
break;
|
||||
case 'and':
|
||||
case 'or':
|
||||
dmp.putCollection(` ^${condition.conditionType} `, condition.conditions, cond => {
|
||||
dmp.putCollection(` ^${condition.conditionType} `, condition.conditions, (cond) => {
|
||||
dmp.putRaw('(');
|
||||
dumpSqlCondition(dmp, cond);
|
||||
dmp.putRaw(')');
|
||||
@@ -51,5 +52,15 @@ export function dumpSqlCondition(dmp: SqlDumper, condition: Condition) {
|
||||
dumpSqlCondition(dmp, condition.condition);
|
||||
dmp.put(')');
|
||||
break;
|
||||
case 'exists':
|
||||
dmp.put('^exists (');
|
||||
dumpSqlSelect(dmp, condition.subQuery);
|
||||
dmp.put(')');
|
||||
break;
|
||||
case 'notExists':
|
||||
dmp.put('^not ^exists (');
|
||||
dumpSqlSelect(dmp, condition.subQuery);
|
||||
dmp.put(')');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ export function dumpSqlExpression(dmp: SqlDumper, expr: Expression) {
|
||||
break;
|
||||
|
||||
case 'call':
|
||||
dmp.put('%s(%s', expr.func, expr.argsPrefix);
|
||||
dmp.put('%s(', expr.func);
|
||||
if (expr.argsPrefix) dmp.put('%s ', expr.argsPrefix);
|
||||
dmp.putCollection(',', expr.args, (x) => dumpSqlExpression(dmp, x));
|
||||
dmp.put(')');
|
||||
break;
|
||||
|
||||
@@ -42,14 +42,14 @@ export function dumpSqlSourceRef(dmp: SqlDumper, source: Source) {
|
||||
export function dumpSqlRelation(dmp: SqlDumper, from: Relation) {
|
||||
dmp.put('&n %k ', from.joinType);
|
||||
dumpSqlSourceDef(dmp, from);
|
||||
if (from.conditions) {
|
||||
if (from.conditions && from.conditions.length > 0) {
|
||||
dmp.put(' ^on ');
|
||||
dmp.putCollection(' ^and ', from.conditions, cond => dumpSqlCondition(dmp, cond));
|
||||
dmp.putCollection(' ^and ', from.conditions, (cond) => dumpSqlCondition(dmp, cond));
|
||||
}
|
||||
}
|
||||
|
||||
export function dumpSqlFromDefinition(dmp: SqlDumper, from: FromDefinition) {
|
||||
dumpSqlSourceDef(dmp, from);
|
||||
dmp.put(' ');
|
||||
if (from.relations) from.relations.forEach(rel => dumpSqlRelation(dmp, rel));
|
||||
if (from.relations) from.relations.forEach((rel) => dumpSqlRelation(dmp, rel));
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface Select {
|
||||
orderBy?: OrderByExpression[];
|
||||
groupBy?: Expression[];
|
||||
where?: Condition;
|
||||
having?: Condition;
|
||||
}
|
||||
|
||||
export type UpdateField = Expression & { targetColumn: string };
|
||||
@@ -82,7 +83,23 @@ export interface CompoudCondition {
|
||||
conditions: Condition[];
|
||||
}
|
||||
|
||||
export type Condition = BinaryCondition | NotCondition | TestCondition | CompoudCondition | LikeCondition;
|
||||
export interface ExistsCondition {
|
||||
conditionType: 'exists';
|
||||
subQuery: Select;
|
||||
}
|
||||
export interface NotExistsCondition {
|
||||
conditionType: 'notExists';
|
||||
subQuery: Select;
|
||||
}
|
||||
|
||||
export type Condition =
|
||||
| BinaryCondition
|
||||
| NotCondition
|
||||
| TestCondition
|
||||
| CompoudCondition
|
||||
| LikeCondition
|
||||
| ExistsCondition
|
||||
| NotExistsCondition;
|
||||
|
||||
export interface Source {
|
||||
name?: NamedObjectInfo;
|
||||
@@ -91,10 +108,10 @@ export interface Source {
|
||||
subQueryString?: string;
|
||||
}
|
||||
|
||||
export type JoinType = 'LEFT JOIN' | 'INNER JOIN' | 'RIGHT JOIN';
|
||||
export type JoinType = 'LEFT JOIN' | 'INNER JOIN' | 'RIGHT JOIN' | 'CROSS JOIN';
|
||||
|
||||
export type Relation = Source & {
|
||||
conditions: Condition[];
|
||||
conditions?: Condition[];
|
||||
joinType: JoinType;
|
||||
};
|
||||
export type FromDefinition = Source & { relations?: Relation[] };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EngineDriver, SqlDumper } from 'dbgate-types';
|
||||
import { Command } from './types';
|
||||
import { Command, Condition } from './types';
|
||||
import { dumpSqlCommand } from './dumpSqlCommand';
|
||||
|
||||
export function treeToSql<T>(driver: EngineDriver, object: T, func: (dmp: SqlDumper, obj: T) => void) {
|
||||
@@ -16,3 +16,30 @@ export function scriptToSql(driver: EngineDriver, script: Command[]): string {
|
||||
}
|
||||
return dmp.s;
|
||||
}
|
||||
|
||||
export function mergeConditions(condition1: Condition, condition2: Condition): Condition {
|
||||
if (!condition1) return condition2;
|
||||
if (!condition2) return condition1;
|
||||
if (condition1.conditionType == 'and' && condition2.conditionType == 'and') {
|
||||
return {
|
||||
conditionType: 'and',
|
||||
conditions: [...condition1.conditions, ...condition2.conditions],
|
||||
};
|
||||
}
|
||||
if (condition1.conditionType == 'and') {
|
||||
return {
|
||||
conditionType: 'and',
|
||||
conditions: [...condition1.conditions, condition2],
|
||||
};
|
||||
}
|
||||
if (condition2.conditionType == 'and') {
|
||||
return {
|
||||
conditionType: 'and',
|
||||
conditions: [condition1, ...condition2.conditions],
|
||||
};
|
||||
}
|
||||
return {
|
||||
conditionType: 'and',
|
||||
conditions: [condition1, condition2],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
version-tag-prefix packages-tools-v
|
||||
version-git-message "packages-tools v%s"
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.7",
|
||||
"name": "dbgate-tools",
|
||||
"main": "lib/index.js",
|
||||
"typings": "lib/index.d.ts",
|
||||
@@ -19,7 +19,8 @@
|
||||
"prepare": "yarn build",
|
||||
"build": "tsc",
|
||||
"start": "tsc --watch",
|
||||
"test": "jest"
|
||||
"test": "jest",
|
||||
"prepublishOnly": "yarn build"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
|
||||
@@ -43,7 +43,7 @@ export function createBulkInsertStreamBase(driver, stream, pool, name, options):
|
||||
await driver.query(pool, `TRUNCATE TABLE ${fullNameQuoted}`);
|
||||
}
|
||||
|
||||
this.columnNames = _intersection(
|
||||
writable.columnNames = _intersection(
|
||||
structure.columns.map((x) => x.columnName),
|
||||
writable.structure.columns.map((x) => x.columnName)
|
||||
);
|
||||
@@ -56,14 +56,14 @@ export function createBulkInsertStreamBase(driver, stream, pool, name, options):
|
||||
const dmp = driver.createDumper();
|
||||
|
||||
dmp.putRaw(`INSERT INTO ${fullNameQuoted} (`);
|
||||
dmp.putCollection(',', this.columnNames, (col) => dmp.putRaw(driver.dialect.quoteIdentifier(col)));
|
||||
dmp.putCollection(',', writable.columnNames, (col) => dmp.putRaw(driver.dialect.quoteIdentifier(col)));
|
||||
dmp.putRaw(')\n VALUES\n');
|
||||
|
||||
let wasRow = false;
|
||||
for (const row of rows) {
|
||||
if (wasRow) dmp.putRaw(',\n');
|
||||
dmp.putRaw('(');
|
||||
dmp.putCollection(',', this.columnNames, (col) => dmp.putValue(row[col]));
|
||||
dmp.putCollection(',', writable.columnNames, (col) => dmp.putValue(row[col]));
|
||||
dmp.putRaw(')');
|
||||
wasRow = true;
|
||||
}
|
||||
|
||||
@@ -6,3 +6,5 @@ export * from './createBulkInsertStreamBase';
|
||||
export * from './DatabaseAnalyser';
|
||||
export * from './driverBase';
|
||||
export * from './SqlDumper';
|
||||
export * from './testPermission';
|
||||
export * from './splitPostgresQuery';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DatabaseInfo, DatabaseInfoObjects } from 'dbgate-types';
|
||||
import { ColumnInfo, DatabaseInfo, DatabaseInfoObjects, TableInfo } from 'dbgate-types';
|
||||
|
||||
export function fullNameFromString(name) {
|
||||
const m = name.match(/\[([^\]]+)\]\.\[([^\]]+)\]/);
|
||||
@@ -45,3 +45,7 @@ export function findObjectLike(
|
||||
// @ts-ignore
|
||||
return dbinfo[objectTypeField].find((x) => equalStringLike(x.pureName, pureName));
|
||||
}
|
||||
|
||||
export function findForeignKeyForColumn(table: TableInfo, column: ColumnInfo) {
|
||||
return (table.foreignKeys || []).find((fk) => fk.columns.find((col) => col.columnName == column.columnName));
|
||||
}
|
||||
|
||||
@@ -18,6 +18,15 @@ export function extractShellApiPlugins(functionName, props): string[] {
|
||||
return res;
|
||||
}
|
||||
|
||||
export function extractPackageName(name): string {
|
||||
if (!name) return null;
|
||||
const nsMatch = name.match(/^([^@]+)@([^@]+)/);
|
||||
if (nsMatch) {
|
||||
return nsMatch[2];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function extractShellApiFunctionName(functionName) {
|
||||
const nsMatch = functionName.match(/^([^@]+)@([^@]+)/);
|
||||
if (nsMatch) {
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
const SINGLE_QUOTE = "'";
|
||||
const DOUBLE_QUOTE = '"';
|
||||
// const BACKTICK = '`';
|
||||
const DOUBLE_DASH_COMMENT_START = '--';
|
||||
const HASH_COMMENT_START = '#';
|
||||
const C_STYLE_COMMENT_START = '/*';
|
||||
const SEMICOLON = ';';
|
||||
const LINE_FEED = '\n';
|
||||
const DELIMITER_KEYWORD = 'DELIMITER';
|
||||
|
||||
export interface SplitOptions {
|
||||
multipleStatements?: boolean;
|
||||
retainComments?: boolean;
|
||||
}
|
||||
|
||||
interface SqlStatement {
|
||||
value: string;
|
||||
supportMulti: boolean;
|
||||
}
|
||||
|
||||
interface SplitExecutionContext extends Required<SplitOptions> {
|
||||
unread: string;
|
||||
currentDelimiter: string;
|
||||
currentStatement: SqlStatement;
|
||||
output: SqlStatement[];
|
||||
}
|
||||
|
||||
interface FindExpResult {
|
||||
expIndex: number;
|
||||
exp: string | null;
|
||||
nextIndex: number;
|
||||
}
|
||||
|
||||
const regexEscapeSetRegex = /[-/\\^$*+?.()|[\]{}]/g;
|
||||
const singleQuoteStringEndRegex = /(?<!\\)'/;
|
||||
const doubleQuoteStringEndRegex = /(?<!\\)"/;
|
||||
// const backtickQuoteEndRegex = /(?<!`)`(?!`)/;
|
||||
const doubleDashCommentStartRegex = /--[ \f\n\r\t\v]/;
|
||||
const cStyleCommentStartRegex = /\/\*/;
|
||||
const cStyleCommentEndRegex = /(?<!\/)\*\//;
|
||||
const newLineRegex = /(?:[\r\n]+|$)/;
|
||||
const delimiterStartRegex = /(?:^|[\n\r]+)[ \f\t\v]*DELIMITER[ \t]+/i;
|
||||
// Best effort only, unable to find a syntax specification on delimiter
|
||||
const delimiterTokenRegex = /^(?:'(.+)'|"(.+)"|`(.+)`|([^\s]+))/;
|
||||
const semicolonKeyTokenRegex = buildKeyTokenRegex(SEMICOLON);
|
||||
const quoteEndRegexDict: Record<string, RegExp> = {
|
||||
[SINGLE_QUOTE]: singleQuoteStringEndRegex,
|
||||
[DOUBLE_QUOTE]: doubleQuoteStringEndRegex,
|
||||
// [BACKTICK]: backtickQuoteEndRegex,
|
||||
};
|
||||
|
||||
function escapeRegex(value: string): string {
|
||||
return value.replace(regexEscapeSetRegex, '\\$&');
|
||||
}
|
||||
|
||||
function buildKeyTokenRegex(delimiter: string): RegExp {
|
||||
return new RegExp(
|
||||
'(?:' +
|
||||
[
|
||||
escapeRegex(delimiter),
|
||||
SINGLE_QUOTE,
|
||||
DOUBLE_QUOTE,
|
||||
// BACKTICK,
|
||||
doubleDashCommentStartRegex.source,
|
||||
HASH_COMMENT_START,
|
||||
cStyleCommentStartRegex.source,
|
||||
delimiterStartRegex.source,
|
||||
].join('|') +
|
||||
')',
|
||||
'i'
|
||||
);
|
||||
}
|
||||
|
||||
function findExp(content: string, regex: RegExp): FindExpResult {
|
||||
const match = content.match(regex);
|
||||
let result: FindExpResult;
|
||||
if (match?.index !== undefined) {
|
||||
result = {
|
||||
expIndex: match.index,
|
||||
exp: match[0],
|
||||
nextIndex: match.index + match[0].length,
|
||||
};
|
||||
} else {
|
||||
result = {
|
||||
expIndex: -1,
|
||||
exp: null,
|
||||
nextIndex: content.length,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function findKeyToken(content: string, currentDelimiter: string): FindExpResult {
|
||||
let regex;
|
||||
if (currentDelimiter === SEMICOLON) {
|
||||
regex = semicolonKeyTokenRegex;
|
||||
} else {
|
||||
regex = buildKeyTokenRegex(currentDelimiter);
|
||||
}
|
||||
return findExp(content, regex);
|
||||
}
|
||||
|
||||
function findEndQuote(content: string, quote: string): FindExpResult {
|
||||
if (!(quote in quoteEndRegexDict)) {
|
||||
throw new TypeError(`Incorrect quote ${quote} supplied`);
|
||||
}
|
||||
return findExp(content, quoteEndRegexDict[quote]);
|
||||
}
|
||||
|
||||
function read(
|
||||
context: SplitExecutionContext,
|
||||
readToIndex: number,
|
||||
nextUnreadIndex?: number,
|
||||
checkSemicolon?: boolean
|
||||
): void {
|
||||
if (checkSemicolon === undefined) {
|
||||
checkSemicolon = true;
|
||||
}
|
||||
const readContent = context.unread.slice(0, readToIndex);
|
||||
if (checkSemicolon && readContent.includes(SEMICOLON)) {
|
||||
context.currentStatement.supportMulti = false;
|
||||
}
|
||||
context.currentStatement.value += readContent;
|
||||
if (nextUnreadIndex !== undefined && nextUnreadIndex > 0) {
|
||||
context.unread = context.unread.slice(nextUnreadIndex);
|
||||
} else {
|
||||
context.unread = context.unread.slice(readToIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function readTillNewLine(context: SplitExecutionContext, checkSemicolon?: boolean): void {
|
||||
const findResult = findExp(context.unread, newLineRegex);
|
||||
read(context, findResult.expIndex, findResult.expIndex, checkSemicolon);
|
||||
}
|
||||
|
||||
function discard(context: SplitExecutionContext, nextUnreadIndex: number): void {
|
||||
if (nextUnreadIndex > 0) {
|
||||
context.unread = context.unread.slice(nextUnreadIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function discardTillNewLine(context: SplitExecutionContext): void {
|
||||
const findResult = findExp(context.unread, newLineRegex);
|
||||
discard(context, findResult.expIndex);
|
||||
}
|
||||
|
||||
function publishStatementInMultiMode(splitOutput: SqlStatement[], currentStatement: SqlStatement): void {
|
||||
if (splitOutput.length === 0) {
|
||||
splitOutput.push({
|
||||
value: '',
|
||||
supportMulti: true,
|
||||
});
|
||||
}
|
||||
const lastSplitResult = splitOutput[splitOutput.length - 1];
|
||||
if (currentStatement.supportMulti) {
|
||||
if (lastSplitResult.supportMulti) {
|
||||
if (lastSplitResult.value !== '' && !lastSplitResult.value.endsWith(LINE_FEED)) {
|
||||
lastSplitResult.value += LINE_FEED;
|
||||
}
|
||||
lastSplitResult.value += currentStatement.value + SEMICOLON;
|
||||
} else {
|
||||
splitOutput.push({
|
||||
value: currentStatement.value + SEMICOLON,
|
||||
supportMulti: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
splitOutput.push({
|
||||
value: currentStatement.value,
|
||||
supportMulti: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function publishStatement(context: SplitExecutionContext): void {
|
||||
const trimmed = context.currentStatement.value.trim();
|
||||
if (trimmed !== '') {
|
||||
if (!context.multipleStatements) {
|
||||
context.output.push({
|
||||
value: trimmed,
|
||||
supportMulti: context.currentStatement.supportMulti,
|
||||
});
|
||||
} else {
|
||||
context.currentStatement.value = trimmed;
|
||||
publishStatementInMultiMode(context.output, context.currentStatement);
|
||||
}
|
||||
}
|
||||
context.currentStatement.value = '';
|
||||
context.currentStatement.supportMulti = true;
|
||||
}
|
||||
|
||||
function handleKeyTokenFindResult(context: SplitExecutionContext, findResult: FindExpResult): void {
|
||||
switch (findResult.exp?.trim()) {
|
||||
case context.currentDelimiter:
|
||||
read(context, findResult.expIndex, findResult.nextIndex);
|
||||
publishStatement(context);
|
||||
break;
|
||||
// case BACKTICK:
|
||||
case SINGLE_QUOTE:
|
||||
case DOUBLE_QUOTE: {
|
||||
read(context, findResult.nextIndex);
|
||||
const findQuoteResult = findEndQuote(context.unread, findResult.exp);
|
||||
read(context, findQuoteResult.nextIndex, undefined, false);
|
||||
break;
|
||||
}
|
||||
case DOUBLE_DASH_COMMENT_START: {
|
||||
if (context.retainComments) {
|
||||
read(context, findResult.nextIndex);
|
||||
readTillNewLine(context, false);
|
||||
} else {
|
||||
read(context, findResult.expIndex, findResult.expIndex + DOUBLE_DASH_COMMENT_START.length);
|
||||
discardTillNewLine(context);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HASH_COMMENT_START: {
|
||||
if (context.retainComments) {
|
||||
read(context, findResult.nextIndex);
|
||||
readTillNewLine(context, false);
|
||||
} else {
|
||||
read(context, findResult.expIndex, findResult.nextIndex);
|
||||
discardTillNewLine(context);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case C_STYLE_COMMENT_START: {
|
||||
if (['!', '+'].includes(context.unread[findResult.nextIndex]) || context.retainComments) {
|
||||
// Should not be skipped, see https://dev.mysql.com/doc/refman/5.7/en/comments.html
|
||||
read(context, findResult.nextIndex);
|
||||
const findCommentResult = findExp(context.unread, cStyleCommentEndRegex);
|
||||
read(context, findCommentResult.nextIndex);
|
||||
} else {
|
||||
read(context, findResult.expIndex, findResult.nextIndex);
|
||||
const findCommentResult = findExp(context.unread, cStyleCommentEndRegex);
|
||||
discard(context, findCommentResult.nextIndex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DELIMITER_KEYWORD: {
|
||||
read(context, findResult.expIndex, findResult.nextIndex);
|
||||
// MySQL client will return `DELIMITER cannot contain a backslash character` if backslash is used
|
||||
// Shall we reject backslash as well?
|
||||
const matched = context.unread.match(delimiterTokenRegex);
|
||||
if (matched?.index !== undefined) {
|
||||
context.currentDelimiter = matched[0].trim();
|
||||
discard(context, matched[0].length);
|
||||
}
|
||||
discardTillNewLine(context);
|
||||
break;
|
||||
}
|
||||
case undefined:
|
||||
case null:
|
||||
read(context, findResult.nextIndex);
|
||||
publishStatement(context);
|
||||
break;
|
||||
default:
|
||||
// This should never happen
|
||||
throw new Error(`Unknown token '${findResult.exp ?? '(null)'}'`);
|
||||
}
|
||||
}
|
||||
|
||||
export function splitPostgresQuery(sql: string, options?: SplitOptions): string[] {
|
||||
options = options ?? {};
|
||||
const context: SplitExecutionContext = {
|
||||
multipleStatements: options.multipleStatements ?? false,
|
||||
retainComments: options.retainComments ?? false,
|
||||
unread: sql,
|
||||
currentDelimiter: SEMICOLON,
|
||||
currentStatement: {
|
||||
value: '',
|
||||
supportMulti: true,
|
||||
},
|
||||
output: [],
|
||||
};
|
||||
let findResult: FindExpResult = {
|
||||
expIndex: -1,
|
||||
exp: null,
|
||||
nextIndex: 0,
|
||||
};
|
||||
let lastUnreadLength;
|
||||
do {
|
||||
lastUnreadLength = context.unread.length;
|
||||
findResult = findKeyToken(context.unread, context.currentDelimiter);
|
||||
handleKeyTokenFindResult(context, findResult);
|
||||
// Prevent infinite loop by returning incorrect result
|
||||
if (lastUnreadLength === context.unread.length) {
|
||||
read(context, context.unread.length);
|
||||
}
|
||||
} while (context.unread !== '');
|
||||
publishStatement(context);
|
||||
return context.output.map((v) => v.value);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import _escapeRegExp from 'lodash/escapeRegExp';
|
||||
import _isString from 'lodash/isString';
|
||||
|
||||
export function compilePermissions(permissions: string[] | string) {
|
||||
if (!permissions) return null;
|
||||
if (_isString(permissions)) permissions = permissions.split(',');
|
||||
return permissions.map((x) => new RegExp('^' + _escapeRegExp(x).replace(/\\\*/g, '.*') + '$'));
|
||||
}
|
||||
|
||||
export function testPermission(tested: string, permissions: RegExp[]) {
|
||||
if (!permissions) return true;
|
||||
for (const permission of permissions) {
|
||||
if (tested.match(permission)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
version-tag-prefix packages-types-v
|
||||
version-git-message "packages-types v%s"
|
||||
@@ -7,6 +7,7 @@ Typescript definitions for DbGate app
|
||||
- dumper.d.ts - SQL dumper - dump SQL commands independed on DB engine
|
||||
- engines.d.ts - definition of SQL engine driver
|
||||
- query.d.ts - query results definition
|
||||
- extensions.d.ts - plugin related definitions
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Vendored
+4
-3
@@ -1,4 +1,4 @@
|
||||
import { EngineDriver } from "./engines";
|
||||
import { EngineDriver } from './engines';
|
||||
|
||||
export interface FileFormatDefinition {
|
||||
storageType: string;
|
||||
@@ -7,9 +7,10 @@ export interface FileFormatDefinition {
|
||||
readerFunc?: string;
|
||||
writerFunc?: string;
|
||||
args?: any[];
|
||||
addFilesToSourceList?: (
|
||||
addFileToSourceList?: (
|
||||
file: {
|
||||
full: string;
|
||||
fileName: string;
|
||||
shortName: string;
|
||||
},
|
||||
newSources: string[],
|
||||
newValues: {
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
{
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"name": "dbgate-types",
|
||||
|
||||
"homepage": "https://dbgate.org/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dbshell/dbgate.git"
|
||||
},
|
||||
},
|
||||
"funding": "https://www.paypal.com/paypalme/JanProchazkaCz/30eur",
|
||||
"author": "Jan Prochazka",
|
||||
"license": "GPL",
|
||||
|
||||
"keywords": [
|
||||
"dbgate"
|
||||
],
|
||||
|
||||
"types": "index.d.ts",
|
||||
"main": "",
|
||||
"typeScriptVersion": "2.8"
|
||||
|
||||
@@ -10,21 +10,24 @@
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"ace-builds": "^1.4.8",
|
||||
"axios": "^0.19.0",
|
||||
"chart.js": "^2.9.4",
|
||||
"cross-env": "^6.0.3",
|
||||
"dbgate-datalib": "^1.0.0",
|
||||
"dbgate-sqltree": "^1.0.0",
|
||||
"dbgate-tools": "^1.0.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-react": "^7.17.0",
|
||||
"formik": "^2.1.0",
|
||||
"json-stable-stringify": "^1.0.1",
|
||||
"localforage": "^1.9.0",
|
||||
"markdown-to-jsx": "^7.1.0",
|
||||
"randomcolor": "^0.6.2",
|
||||
"react": "^16.12.0",
|
||||
"react-ace": "^8.0.0",
|
||||
"react-chartjs-2": "^2.11.1",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-dropzone": "^11.2.3",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-json-view": "^1.19.1",
|
||||
"react-markdown": "^5.0.3",
|
||||
"react-modal": "^3.11.1",
|
||||
"react-scripts": "3.3.0",
|
||||
"react-select": "^3.1.0",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 182 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 30 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 137 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"short_name": "DbGate",
|
||||
"name": "DbGate database tool",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
|
||||
+21
-21
@@ -5,7 +5,6 @@ import {
|
||||
CurrentWidgetProvider,
|
||||
CurrentDatabaseProvider,
|
||||
OpenedTabsProvider,
|
||||
SavedSqlFilesProvider,
|
||||
OpenedConnectionsProvider,
|
||||
LeftPanelWidthProvider,
|
||||
CurrentArchiveProvider,
|
||||
@@ -18,6 +17,7 @@ import UploadsProvider from './utility/UploadsProvider';
|
||||
import ThemeHelmet from './themes/ThemeHelmet';
|
||||
import PluginsProvider from './plugins/PluginsProvider';
|
||||
import { ExtensionsProvider } from './utility/useExtensions';
|
||||
import { MenuLayerProvider } from './modals/showMenu';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@@ -25,28 +25,28 @@ function App() {
|
||||
<CurrentDatabaseProvider>
|
||||
<SocketProvider>
|
||||
<OpenedTabsProvider>
|
||||
<SavedSqlFilesProvider>
|
||||
<OpenedConnectionsProvider>
|
||||
<LeftPanelWidthProvider>
|
||||
<ConnectionsPinger>
|
||||
<PluginsProvider>
|
||||
<ExtensionsProvider>
|
||||
<ModalLayerProvider>
|
||||
<CurrentArchiveProvider>
|
||||
<CurrentThemeProvider>
|
||||
<UploadsProvider>
|
||||
<OpenedConnectionsProvider>
|
||||
<LeftPanelWidthProvider>
|
||||
<ConnectionsPinger>
|
||||
<PluginsProvider>
|
||||
<ExtensionsProvider>
|
||||
<CurrentArchiveProvider>
|
||||
<CurrentThemeProvider>
|
||||
<UploadsProvider>
|
||||
<ModalLayerProvider>
|
||||
<MenuLayerProvider>
|
||||
<ThemeHelmet />
|
||||
<Screen />
|
||||
</UploadsProvider>
|
||||
</CurrentThemeProvider>
|
||||
</CurrentArchiveProvider>
|
||||
</ModalLayerProvider>
|
||||
</ExtensionsProvider>
|
||||
</PluginsProvider>
|
||||
</ConnectionsPinger>
|
||||
</LeftPanelWidthProvider>
|
||||
</OpenedConnectionsProvider>
|
||||
</SavedSqlFilesProvider>
|
||||
</MenuLayerProvider>
|
||||
</ModalLayerProvider>
|
||||
</UploadsProvider>
|
||||
</CurrentThemeProvider>
|
||||
</CurrentArchiveProvider>
|
||||
</ExtensionsProvider>
|
||||
</PluginsProvider>
|
||||
</ConnectionsPinger>
|
||||
</LeftPanelWidthProvider>
|
||||
</OpenedConnectionsProvider>
|
||||
</OpenedTabsProvider>
|
||||
</SocketProvider>
|
||||
</CurrentDatabaseProvider>
|
||||
|
||||
@@ -15,6 +15,8 @@ import { ModalLayer } from './modals/showModal';
|
||||
import DragAndDropFileTarget from './DragAndDropFileTarget';
|
||||
import { useUploadsZone } from './utility/UploadsProvider';
|
||||
import useTheme from './theme/useTheme';
|
||||
import { MenuLayer } from './modals/showMenu';
|
||||
import ErrorBoundary from './utility/ErrorBoundary';
|
||||
|
||||
const BodyDiv = styled.div`
|
||||
position: fixed;
|
||||
@@ -112,7 +114,9 @@ export default function Screen() {
|
||||
</IconBar>
|
||||
{!!currentWidget && (
|
||||
<LeftPanel theme={theme}>
|
||||
<WidgetContainer />
|
||||
<ErrorBoundary>
|
||||
<WidgetContainer />
|
||||
</ErrorBoundary>
|
||||
</LeftPanel>
|
||||
)}
|
||||
{!!currentWidget && (
|
||||
@@ -132,6 +136,7 @@ export default function Screen() {
|
||||
<StatusBar />
|
||||
</StausBarContainer>
|
||||
<ModalLayer />
|
||||
<MenuLayer />
|
||||
|
||||
<DragAndDropFileTarget inputProps={getInputProps()} isDragActive={isDragActive} />
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import _ from 'lodash';
|
||||
import styled from 'styled-components';
|
||||
import tabs from './tabs';
|
||||
import { useOpenedTabs } from './utility/globalState';
|
||||
import ErrorBoundary from './utility/ErrorBoundary';
|
||||
|
||||
const TabContainer = styled.div`
|
||||
position: absolute;
|
||||
@@ -11,7 +12,7 @@ const TabContainer = styled.div`
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
visibility: ${props =>
|
||||
visibility: ${(props) =>
|
||||
// @ts-ignore
|
||||
props.tabVisible ? 'visible' : 'hidden'};
|
||||
`;
|
||||
@@ -34,10 +35,10 @@ export default function TabContent({ toolbarPortalRef }) {
|
||||
|
||||
// cleanup closed tabs
|
||||
if (_.difference(_.keys(mountedTabs), _.map(files, 'tabid')).length > 0) {
|
||||
setMountedTabs(_.pickBy(mountedTabs, (v, k) => files.find(x => x.tabid == k)));
|
||||
setMountedTabs(_.pickBy(mountedTabs, (v, k) => files.find((x) => x.tabid == k)));
|
||||
}
|
||||
|
||||
const selectedTab = files.find(x => x.selected);
|
||||
const selectedTab = files.find((x) => x.selected);
|
||||
if (selectedTab) {
|
||||
const { tabid } = selectedTab;
|
||||
if (tabid && !mountedTabs[tabid])
|
||||
@@ -47,13 +48,15 @@ export default function TabContent({ toolbarPortalRef }) {
|
||||
});
|
||||
}
|
||||
|
||||
return _.keys(mountedTabs).map(tabid => {
|
||||
return _.keys(mountedTabs).map((tabid) => {
|
||||
const { TabComponent, props } = mountedTabs[tabid];
|
||||
const tabVisible = tabid == (selectedTab && selectedTab.tabid);
|
||||
return (
|
||||
// @ts-ignore
|
||||
<TabContainer key={tabid} tabVisible={tabVisible}>
|
||||
<TabComponent {...props} tabid={tabid} tabVisible={tabVisible} toolbarPortalRef={toolbarPortalRef} />
|
||||
<ErrorBoundary>
|
||||
<TabComponent {...props} tabid={tabid} tabVisible={tabVisible} toolbarPortalRef={toolbarPortalRef} />
|
||||
</ErrorBoundary>
|
||||
</TabContainer>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -4,11 +4,11 @@ import styled from 'styled-components';
|
||||
import { DropDownMenuItem, DropDownMenuDivider } from './modals/DropDownMenu';
|
||||
|
||||
import { useOpenedTabs, useSetOpenedTabs, useCurrentDatabase, useSetCurrentDatabase } from './utility/globalState';
|
||||
import { showMenu } from './modals/DropDownMenu';
|
||||
import { getConnectionInfo } from './utility/metadataLoaders';
|
||||
import { FontIcon } from './icons';
|
||||
import useTheme from './theme/useTheme';
|
||||
import usePropsCompare from './utility/usePropsCompare';
|
||||
import { useShowMenu } from './modals/showMenu';
|
||||
|
||||
// const files = [
|
||||
// { name: 'app.js' },
|
||||
@@ -126,6 +126,7 @@ function getDbIcon(key) {
|
||||
export default function TabsPanel() {
|
||||
// const formatDbKey = (conid, database) => `${database}-${conid}`;
|
||||
const theme = useTheme();
|
||||
const showMenu = useShowMenu();
|
||||
|
||||
const tabs = useOpenedTabs();
|
||||
const setOpenedTabs = useSetOpenedTabs();
|
||||
|
||||
@@ -4,9 +4,8 @@ import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FontIcon } from '../icons';
|
||||
import { showMenu } from '../modals/DropDownMenu';
|
||||
import { useShowMenu } from '../modals/showMenu';
|
||||
import useTheme from '../theme/useTheme';
|
||||
import { useSetOpenedTabs, useAppObjectParams } from '../utility/globalState';
|
||||
|
||||
const AppObjectDiv = styled.div`
|
||||
padding: 5px;
|
||||
@@ -18,11 +17,6 @@ const AppObjectDiv = styled.div`
|
||||
font-weight: ${(props) => (props.isBold ? 'bold' : 'normal')};
|
||||
`;
|
||||
|
||||
const AppObjectSpan = styled.span`
|
||||
white-space: nowrap;
|
||||
font-weight: ${(props) => (props.isBold ? 'bold' : 'normal')};
|
||||
`;
|
||||
|
||||
const IconWrap = styled.span`
|
||||
margin-right: 5px;
|
||||
`;
|
||||
@@ -32,49 +26,51 @@ const StatusIconWrap = styled.span`
|
||||
`;
|
||||
|
||||
const ExtInfoWrap = styled.span`
|
||||
font-weight: normal;
|
||||
margin-left: 5px;
|
||||
font-weight: normal;
|
||||
margin-left: 5px;
|
||||
color: ${(props) => props.theme.left_font3};
|
||||
`;
|
||||
|
||||
export function AppObjectCore({
|
||||
title,
|
||||
icon,
|
||||
Menu,
|
||||
data,
|
||||
makeAppObj,
|
||||
onClick,
|
||||
isBold,
|
||||
isBusy,
|
||||
component = 'div',
|
||||
prefix = null,
|
||||
statusIcon,
|
||||
extInfo,
|
||||
statusTitle,
|
||||
onClick = undefined,
|
||||
onClick2 = undefined,
|
||||
onClick3 = undefined,
|
||||
isBold = undefined,
|
||||
isBusy = undefined,
|
||||
prefix = undefined,
|
||||
statusIcon = undefined,
|
||||
extInfo = undefined,
|
||||
statusTitle = undefined,
|
||||
Menu = undefined,
|
||||
...other
|
||||
}) {
|
||||
const appObjectParams = useAppObjectParams();
|
||||
const theme = useTheme();
|
||||
const showMenu = useShowMenu();
|
||||
|
||||
const handleContextMenu = (event) => {
|
||||
if (!Menu) return;
|
||||
|
||||
event.preventDefault();
|
||||
showMenu(event.pageX, event.pageY, <Menu data={data} makeAppObj={makeAppObj} {...appObjectParams} />);
|
||||
showMenu(event.pageX, event.pageY, <Menu data={data} />);
|
||||
};
|
||||
|
||||
const Component = component == 'div' ? AppObjectDiv : AppObjectSpan;
|
||||
|
||||
let bold = false;
|
||||
if (_.isFunction(isBold)) bold = isBold(appObjectParams);
|
||||
else bold = !!isBold;
|
||||
|
||||
return (
|
||||
<Component
|
||||
<AppObjectDiv
|
||||
onContextMenu={handleContextMenu}
|
||||
onClick={onClick ? () => onClick(data) : undefined}
|
||||
isBold={bold}
|
||||
onClick={() => {
|
||||
if (onClick) onClick(data);
|
||||
if (onClick2) onClick2(data);
|
||||
if (onClick3) onClick3(data);
|
||||
}}
|
||||
theme={theme}
|
||||
isBold={isBold}
|
||||
draggable
|
||||
onDragStart={(e) => {
|
||||
e.dataTransfer.setData('app_object_drag_data', JSON.stringify(data));
|
||||
}}
|
||||
{...other}
|
||||
>
|
||||
{prefix}
|
||||
@@ -86,12 +82,6 @@ export function AppObjectCore({
|
||||
</StatusIconWrap>
|
||||
)}
|
||||
{extInfo && <ExtInfoWrap theme={theme}>{extInfo}</ExtInfoWrap>}
|
||||
</Component>
|
||||
</AppObjectDiv>
|
||||
);
|
||||
}
|
||||
|
||||
export function AppObjectControl({ data, makeAppObj, component = 'div' }) {
|
||||
const appObjectParams = useAppObjectParams();
|
||||
const appobj = makeAppObj(data, appObjectParams);
|
||||
return <AppObjectCore {...appobj} data={data} makeAppObj={makeAppObj} component={component} />;
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { AppObjectCore } from './AppObjects';
|
||||
import { useSetOpenedTabs, useAppObjectParams } from '../utility/globalState';
|
||||
import styled from 'styled-components';
|
||||
import { ExpandIcon } from '../icons';
|
||||
import useTheme from '../theme/useTheme';
|
||||
@@ -31,53 +29,45 @@ const GroupDiv = styled.div`
|
||||
font-weight: bold;
|
||||
`;
|
||||
|
||||
function AppObjectListItem({ makeAppObj, data, filter, appobj, onObjectClick, SubItems }) {
|
||||
function AppObjectListItem({
|
||||
AppObjectComponent,
|
||||
data,
|
||||
filter,
|
||||
onObjectClick,
|
||||
isExpandable,
|
||||
SubItems,
|
||||
getCommonProps,
|
||||
}) {
|
||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||
const [isHover, setIsHover] = React.useState(false);
|
||||
|
||||
const expandable = data && isExpandable && isExpandable(data);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!appobj.isExpandable) {
|
||||
// if (data._id == '6pOY2iFY8Gsq7mk6') console.log('COLLAPSE1');
|
||||
if (!expandable) {
|
||||
setIsExpanded(false);
|
||||
}
|
||||
}, [appobj && appobj.isExpandable]);
|
||||
}, [expandable]);
|
||||
|
||||
// const { matcher } = appobj;
|
||||
// if (matcher && !matcher(filter)) return null;
|
||||
let commonProps = {
|
||||
prefix: SubItems ? (
|
||||
<ExpandIconHolder2>
|
||||
{expandable ? <ExpandIcon isExpanded={isExpanded} /> : <ExpandIcon isBlank />}
|
||||
</ExpandIconHolder2>
|
||||
) : null,
|
||||
};
|
||||
|
||||
if (onObjectClick)
|
||||
appobj = {
|
||||
...appobj,
|
||||
onClick: onObjectClick,
|
||||
};
|
||||
if (SubItems) {
|
||||
const oldClick = appobj.onClick;
|
||||
appobj = {
|
||||
...appobj,
|
||||
onClick: () => {
|
||||
if (oldClick) oldClick();
|
||||
// if (data._id == '6pOY2iFY8Gsq7mk6') console.log('COLLAPSE2');
|
||||
setIsExpanded((v) => !v);
|
||||
},
|
||||
};
|
||||
commonProps.onClick2 = () => setIsExpanded((v) => !v);
|
||||
}
|
||||
if (onObjectClick) {
|
||||
commonProps.onClick3 = onObjectClick;
|
||||
}
|
||||
|
||||
let res = (
|
||||
<AppObjectCore
|
||||
data={data}
|
||||
makeAppObj={makeAppObj}
|
||||
{...appobj}
|
||||
onMouseEnter={() => setIsHover(true)}
|
||||
onMouseLeave={() => setIsHover(false)}
|
||||
prefix={
|
||||
SubItems ? (
|
||||
<ExpandIconHolder2>
|
||||
{appobj.isExpandable ? <ExpandIcon isExpanded={isExpanded} /> : <ExpandIcon isBlank />}
|
||||
</ExpandIconHolder2>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
);
|
||||
if (getCommonProps) {
|
||||
commonProps = { ...commonProps, ...getCommonProps(data) };
|
||||
}
|
||||
|
||||
let res = <AppObjectComponent data={data} commonProps={commonProps} />;
|
||||
if (SubItems && isExpanded) {
|
||||
res = (
|
||||
<>
|
||||
@@ -93,16 +83,10 @@ function AppObjectListItem({ makeAppObj, data, filter, appobj, onObjectClick, Su
|
||||
|
||||
function AppObjectGroup({ group, items }) {
|
||||
const [isExpanded, setIsExpanded] = React.useState(true);
|
||||
const [isHover, setIsHover] = React.useState(false);
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<>
|
||||
<GroupDiv
|
||||
onMouseEnter={() => setIsHover(true)}
|
||||
onMouseLeave={() => setIsHover(false)}
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
theme={theme}
|
||||
>
|
||||
<GroupDiv onClick={() => setIsExpanded(!isExpanded)} theme={theme}>
|
||||
<ExpandIconHolder>
|
||||
<ExpandIcon isExpanded={isExpanded} />
|
||||
</ExpandIconHolder>
|
||||
@@ -115,36 +99,36 @@ function AppObjectGroup({ group, items }) {
|
||||
|
||||
export function AppObjectList({
|
||||
list,
|
||||
makeAppObj,
|
||||
AppObjectComponent,
|
||||
SubItems = undefined,
|
||||
onObjectClick = undefined,
|
||||
filter = undefined,
|
||||
groupFunc = undefined,
|
||||
groupOrdered = undefined,
|
||||
isExpandable = undefined,
|
||||
getCommonProps = undefined,
|
||||
}) {
|
||||
const appObjectParams = useAppObjectParams();
|
||||
|
||||
const createComponent = (data, appobj) => (
|
||||
const createComponent = (data) => (
|
||||
<AppObjectListItem
|
||||
key={appobj.key}
|
||||
appobj={appobj}
|
||||
makeAppObj={makeAppObj}
|
||||
key={AppObjectComponent.extractKey(data)}
|
||||
AppObjectComponent={AppObjectComponent}
|
||||
data={data}
|
||||
filter={filter}
|
||||
onObjectClick={onObjectClick}
|
||||
SubItems={SubItems}
|
||||
isExpandable={isExpandable}
|
||||
getCommonProps={getCommonProps}
|
||||
/>
|
||||
);
|
||||
|
||||
if (groupFunc) {
|
||||
const listGrouped = _.compact(
|
||||
(list || []).map((data) => {
|
||||
const appobj = makeAppObj(data, appObjectParams);
|
||||
const { matcher } = appobj;
|
||||
const matcher = AppObjectComponent.createMatcher && AppObjectComponent.createMatcher(data);
|
||||
if (matcher && !matcher(filter)) return null;
|
||||
const component = createComponent(data, appobj);
|
||||
const group = groupFunc(appobj);
|
||||
return { group, appobj, component };
|
||||
const component = createComponent(data);
|
||||
const group = groupFunc(data);
|
||||
return { group, data, component };
|
||||
})
|
||||
);
|
||||
const groups = _.groupBy(listGrouped, 'group');
|
||||
@@ -154,9 +138,8 @@ export function AppObjectList({
|
||||
}
|
||||
|
||||
return (list || []).map((data) => {
|
||||
const appobj = makeAppObj(data, appObjectParams);
|
||||
const { matcher } = appobj;
|
||||
const matcher = AppObjectComponent.createMatcher && AppObjectComponent.createMatcher(data);
|
||||
if (matcher && !matcher(filter)) return null;
|
||||
return createComponent(data, appobj);
|
||||
return createComponent(data);
|
||||
});
|
||||
}
|
||||
|
||||
+21
-17
@@ -1,13 +1,12 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||
import { openNewTab } from '../utility/common';
|
||||
import { filterName } from 'dbgate-datalib';
|
||||
import axios from '../utility/axios';
|
||||
import { AppObjectCore } from './AppObjectCore';
|
||||
import useOpenNewTab from '../utility/useOpenNewTab';
|
||||
|
||||
function openArchive(setOpenedTabs, fileName, folderName) {
|
||||
openNewTab(setOpenedTabs, {
|
||||
function openArchive(openNewTab, fileName, folderName) {
|
||||
openNewTab({
|
||||
title: fileName,
|
||||
icon: 'img archive',
|
||||
tooltip: `${folderName}\n${fileName}`,
|
||||
@@ -19,23 +18,24 @@ function openArchive(setOpenedTabs, fileName, folderName) {
|
||||
});
|
||||
}
|
||||
|
||||
function Menu({ data, setOpenedTabs }) {
|
||||
function Menu({ data }) {
|
||||
const openNewTab = useOpenNewTab();
|
||||
const handleDelete = () => {
|
||||
axios.post('archive/delete-file', { file: data.fileName, folder: data.folderName });
|
||||
// setOpenedTabs((tabs) => tabs.filter((x) => x.tabid != data.tabid));
|
||||
};
|
||||
const handleOpenRead = () => {
|
||||
openArchive(setOpenedTabs, data.fileName, data.folderName);
|
||||
openArchive(openNewTab, data.fileName, data.folderName);
|
||||
};
|
||||
const handleOpenWrite = async () => {
|
||||
// const resp = await axios.post('archive/load-free-table', { file: data.fileName, folder: data.folderName });
|
||||
|
||||
openNewTab(setOpenedTabs, {
|
||||
openNewTab({
|
||||
title: data.fileName,
|
||||
icon: 'img archive',
|
||||
tabComponent: 'FreeTableTab',
|
||||
props: {
|
||||
initialData: {
|
||||
initialArgs: {
|
||||
functionName: 'archiveReader',
|
||||
props: {
|
||||
fileName: data.fileName,
|
||||
@@ -57,15 +57,19 @@ function Menu({ data, setOpenedTabs }) {
|
||||
);
|
||||
}
|
||||
|
||||
const archiveFileAppObject = () => ({ fileName, folderName }, { setOpenedTabs }) => {
|
||||
const key = fileName;
|
||||
const icon = 'img archive';
|
||||
function ArchiveFileAppObject({ data, commonProps }) {
|
||||
const { fileName, folderName } = data;
|
||||
const openNewTab = useOpenNewTab();
|
||||
const onClick = () => {
|
||||
openArchive(setOpenedTabs, fileName, folderName);
|
||||
openArchive(openNewTab, fileName, folderName);
|
||||
};
|
||||
const matcher = (filter) => filterName(filter, fileName);
|
||||
|
||||
return { title: fileName, key, icon, Menu, onClick, matcher };
|
||||
};
|
||||
return (
|
||||
<AppObjectCore {...commonProps} data={data} title={fileName} icon="img archive" onClick={onClick} Menu={Menu} />
|
||||
);
|
||||
}
|
||||
|
||||
export default archiveFileAppObject;
|
||||
ArchiveFileAppObject.extractKey = (data) => data.fileName;
|
||||
ArchiveFileAppObject.createMatcher = ({ fileName }) => (filter) => filterName(filter, fileName);
|
||||
|
||||
export default ArchiveFileAppObject;
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||
import axios from '../utility/axios';
|
||||
import { filterName } from 'dbgate-datalib';
|
||||
import { AppObjectCore } from './AppObjectCore';
|
||||
import { useCurrentArchive } from '../utility/globalState';
|
||||
|
||||
function Menu({ data }) {
|
||||
const handleDelete = () => {
|
||||
axios.post('archive/delete-folder', { folder: data.name });
|
||||
};
|
||||
return <>{data.name != 'default' && <DropDownMenuItem onClick={handleDelete}>Delete</DropDownMenuItem>}</>;
|
||||
}
|
||||
|
||||
function ArchiveFolderAppObject({ data, commonProps }) {
|
||||
const { name } = data;
|
||||
const currentArchive = useCurrentArchive();
|
||||
|
||||
return (
|
||||
<AppObjectCore
|
||||
{...commonProps}
|
||||
data={data}
|
||||
title={name}
|
||||
icon="img archive-folder"
|
||||
isBold={name == currentArchive}
|
||||
Menu={Menu}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
ArchiveFolderAppObject.extractKey = (data) => data.name;
|
||||
ArchiveFolderAppObject.createMatcher = (data) => (filter) => filterName(filter, data.name);
|
||||
|
||||
export default ArchiveFolderAppObject;
|
||||
+23
-7
@@ -2,8 +2,11 @@ import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||
import { useSetOpenedTabs } from '../utility/globalState';
|
||||
import { AppObjectCore } from './AppObjectCore';
|
||||
|
||||
function Menu({ data, setOpenedTabs }) {
|
||||
function Menu({ data }) {
|
||||
const setOpenedTabs = useSetOpenedTabs();
|
||||
const handleDelete = () => {
|
||||
setOpenedTabs((tabs) => tabs.filter((x) => x.tabid != data.tabid));
|
||||
};
|
||||
@@ -18,9 +21,9 @@ function Menu({ data, setOpenedTabs }) {
|
||||
);
|
||||
}
|
||||
|
||||
const closedTabAppObject = () => ({ tabid, props, selected, icon, title, closedTime, busy }, { setOpenedTabs }) => {
|
||||
const key = tabid;
|
||||
const isBold = !!selected;
|
||||
function ClosedTabAppObject({ data, commonProps }) {
|
||||
const { tabid, props, selected, icon, title, closedTime, busy } = data;
|
||||
const setOpenedTabs = useSetOpenedTabs();
|
||||
|
||||
const onClick = () => {
|
||||
setOpenedTabs((files) =>
|
||||
@@ -32,7 +35,20 @@ const closedTabAppObject = () => ({ tabid, props, selected, icon, title, closedT
|
||||
);
|
||||
};
|
||||
|
||||
return { title: `${title} ${moment(closedTime).fromNow()}`, key, icon, isBold, onClick, isBusy: busy, Menu };
|
||||
};
|
||||
return (
|
||||
<AppObjectCore
|
||||
{...commonProps}
|
||||
data={data}
|
||||
title={`${title} ${moment(closedTime).fromNow()}`}
|
||||
icon={icon}
|
||||
isBold={!!selected}
|
||||
onClick={onClick}
|
||||
isBusy={busy}
|
||||
Menu={Menu}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default closedTabAppObject;
|
||||
ClosedTabAppObject.extractKey = (data) => data.tabid;
|
||||
|
||||
export default ClosedTabAppObject;
|
||||
+50
-33
@@ -6,8 +6,18 @@ import axios from '../utility/axios';
|
||||
import { filterName } from 'dbgate-datalib';
|
||||
import ConfirmModal from '../modals/ConfirmModal';
|
||||
import CreateDatabaseModal from '../modals/CreateDatabaseModal';
|
||||
import { useCurrentDatabase, useOpenedConnections, useSetOpenedConnections } from '../utility/globalState';
|
||||
import { AppObjectCore } from './AppObjectCore';
|
||||
import useShowModal from '../modals/showModal';
|
||||
import { useConfig } from '../utility/metadataLoaders';
|
||||
import useExtensions from '../utility/useExtensions';
|
||||
|
||||
function Menu({ data }) {
|
||||
const openedConnections = useOpenedConnections();
|
||||
const setOpenedConnections = useSetOpenedConnections();
|
||||
const showModal = useShowModal();
|
||||
const config = useConfig();
|
||||
|
||||
function Menu({ data, setOpenedConnections, openedConnections, config, showModal }) {
|
||||
const handleEdit = () => {
|
||||
showModal((modalState) => <ConnectionModal modalState={modalState} connection={data} />);
|
||||
};
|
||||
@@ -54,49 +64,56 @@ function Menu({ data, setOpenedConnections, openedConnections, config, showModal
|
||||
);
|
||||
}
|
||||
|
||||
const connectionAppObject = (flags) => (
|
||||
{ _id, server, displayName, engine, status },
|
||||
{ openedConnections, setOpenedConnections }
|
||||
) => {
|
||||
const title = displayName || server;
|
||||
const key = _id;
|
||||
const isExpandable = openedConnections.includes(_id);
|
||||
const icon = 'img server';
|
||||
const matcher = (filter) => filterName(filter, displayName, server);
|
||||
const { boldCurrentDatabase } = flags || {};
|
||||
const isBold = boldCurrentDatabase
|
||||
? ({ currentDatabase }) => {
|
||||
return _.get(currentDatabase, 'connection._id') == _id;
|
||||
}
|
||||
: null;
|
||||
function ConnectionAppObject({ data, commonProps }) {
|
||||
const { _id, server, displayName, engine, status } = data;
|
||||
const openedConnections = useOpenedConnections();
|
||||
const setOpenedConnections = useSetOpenedConnections();
|
||||
const currentDatabase = useCurrentDatabase();
|
||||
const extensions = useExtensions();
|
||||
|
||||
const isBold = _.get(currentDatabase, 'connection._id') == _id;
|
||||
const onClick = () => setOpenedConnections((c) => [...c, _id]);
|
||||
|
||||
let statusIcon = null;
|
||||
let statusTitle = null;
|
||||
|
||||
let extInfo = null;
|
||||
if (extensions.drivers.find((x) => x.engine == engine)) {
|
||||
const match = (engine || '').match(/^([^@]*)@/);
|
||||
extInfo = match ? match[1] : engine;
|
||||
} else {
|
||||
extInfo = engine;
|
||||
statusIcon = 'img warn';
|
||||
statusTitle = `Engine driver ${engine} not found, review installed plugins and change engine in edit connection dialog`;
|
||||
}
|
||||
|
||||
if (openedConnections.includes(_id)) {
|
||||
if (!status) statusIcon = 'icon loading';
|
||||
else if (status.name == 'pending') statusIcon = 'icon loading';
|
||||
else if (status.name == 'ok') statusIcon = 'img green-ok';
|
||||
else if (status.name == 'ok') statusIcon = 'img ok';
|
||||
else statusIcon = 'img error';
|
||||
if (status && status.name == 'error') {
|
||||
statusTitle = status.message;
|
||||
}
|
||||
}
|
||||
const extInfo = engine;
|
||||
|
||||
return {
|
||||
title,
|
||||
key,
|
||||
icon,
|
||||
Menu,
|
||||
matcher,
|
||||
isBold,
|
||||
isExpandable,
|
||||
onClick,
|
||||
statusIcon,
|
||||
statusTitle,
|
||||
extInfo,
|
||||
};
|
||||
};
|
||||
return (
|
||||
<AppObjectCore
|
||||
{...commonProps}
|
||||
title={displayName || server}
|
||||
icon="img server"
|
||||
data={data}
|
||||
statusIcon={statusIcon}
|
||||
statusTitle={statusTitle}
|
||||
extInfo={extInfo}
|
||||
isBold={isBold}
|
||||
onClick={onClick}
|
||||
Menu={Menu}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connectionAppObject;
|
||||
ConnectionAppObject.extractKey = (data) => data._id;
|
||||
ConnectionAppObject.createMatcher = ({ displayName, server }) => (filter) => filterName(filter, displayName, server);
|
||||
|
||||
export default ConnectionAppObject;
|
||||
+33
-21
@@ -1,16 +1,25 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||
import { openNewTab } from '../utility/common';
|
||||
import ImportExportModal from '../modals/ImportExportModal';
|
||||
import { getDefaultFileFormat } from '../utility/fileformats';
|
||||
import { useCurrentDatabase } from '../utility/globalState';
|
||||
import { AppObjectCore } from './AppObjectCore';
|
||||
import useShowModal from '../modals/showModal';
|
||||
import useExtensions from '../utility/useExtensions';
|
||||
import useOpenNewTab from '../utility/useOpenNewTab';
|
||||
|
||||
function Menu({ data, setOpenedTabs, showModal, extensions }) {
|
||||
function Menu({ data }) {
|
||||
const { connection, name } = data;
|
||||
const openNewTab = useOpenNewTab();
|
||||
|
||||
const extensions = useExtensions();
|
||||
const showModal = useShowModal();
|
||||
|
||||
const tooltip = `${connection.displayName || connection.server}\n${name}`;
|
||||
|
||||
const handleNewQuery = () => {
|
||||
openNewTab(setOpenedTabs, {
|
||||
openNewTab({
|
||||
title: 'Query',
|
||||
icon: 'img sql-file',
|
||||
tooltip,
|
||||
@@ -29,8 +38,8 @@ function Menu({ data, setOpenedTabs, showModal, extensions }) {
|
||||
initialValues={{
|
||||
sourceStorageType: getDefaultFileFormat(extensions).storageType,
|
||||
targetStorageType: 'database',
|
||||
targetConnectionId: data.connection._id,
|
||||
targetDatabaseName: data.name,
|
||||
targetConnectionId: connection._id,
|
||||
targetDatabaseName: name,
|
||||
}}
|
||||
/>
|
||||
));
|
||||
@@ -43,8 +52,8 @@ function Menu({ data, setOpenedTabs, showModal, extensions }) {
|
||||
initialValues={{
|
||||
targetStorageType: getDefaultFileFormat(extensions).storageType,
|
||||
sourceStorageType: 'database',
|
||||
sourceConnectionId: data.connection._id,
|
||||
sourceDatabaseName: data.name,
|
||||
sourceConnectionId: connection._id,
|
||||
sourceDatabaseName: name,
|
||||
}}
|
||||
/>
|
||||
));
|
||||
@@ -59,20 +68,23 @@ function Menu({ data, setOpenedTabs, showModal, extensions }) {
|
||||
);
|
||||
}
|
||||
|
||||
const databaseAppObject = (flags) => ({ name, connection }) => {
|
||||
const { boldCurrentDatabase } = flags || {};
|
||||
const title = name;
|
||||
const key = name;
|
||||
const icon = 'img database';
|
||||
const isBold = boldCurrentDatabase
|
||||
? ({ currentDatabase }) => {
|
||||
return (
|
||||
_.get(currentDatabase, 'connection._id') == _.get(connection, '_id') && _.get(currentDatabase, 'name') == name
|
||||
);
|
||||
function DatabaseAppObject({ data, commonProps }) {
|
||||
const { name, connection } = data;
|
||||
const currentDatabase = useCurrentDatabase();
|
||||
return (
|
||||
<AppObjectCore
|
||||
{...commonProps}
|
||||
data={data}
|
||||
title={name}
|
||||
icon="img database"
|
||||
isBold={
|
||||
_.get(currentDatabase, 'connection._id') == _.get(connection, '_id') && _.get(currentDatabase, 'name') == name
|
||||
}
|
||||
: null;
|
||||
Menu={Menu}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return { title, key, icon, Menu, isBold };
|
||||
};
|
||||
DatabaseAppObject.extractKey = (props) => props.name;
|
||||
|
||||
export default databaseAppObject;
|
||||
export default DatabaseAppObject;
|
||||
@@ -0,0 +1,290 @@
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||
import { getConnectionInfo } from '../utility/metadataLoaders';
|
||||
import fullDisplayName from '../utility/fullDisplayName';
|
||||
import { filterName } from 'dbgate-datalib';
|
||||
import ImportExportModal from '../modals/ImportExportModal';
|
||||
import { useSetOpenedTabs } from '../utility/globalState';
|
||||
import { AppObjectCore } from './AppObjectCore';
|
||||
import useShowModal from '../modals/showModal';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import useExtensions from '../utility/useExtensions';
|
||||
import useOpenNewTab from '../utility/useOpenNewTab';
|
||||
import uuidv1 from 'uuid/v1';
|
||||
|
||||
const icons = {
|
||||
tables: 'img table',
|
||||
views: 'img view',
|
||||
procedures: 'img procedure',
|
||||
functions: 'img function',
|
||||
};
|
||||
|
||||
const menus = {
|
||||
tables: [
|
||||
{
|
||||
label: 'Open data',
|
||||
tab: 'TableDataTab',
|
||||
forceNewTab: true,
|
||||
},
|
||||
{
|
||||
label: 'Open structure',
|
||||
tab: 'TableStructureTab',
|
||||
},
|
||||
{
|
||||
label: 'Show CREATE TABLE script',
|
||||
sqlTemplate: 'CREATE TABLE',
|
||||
},
|
||||
{
|
||||
label: 'Export',
|
||||
isExport: true,
|
||||
},
|
||||
{
|
||||
label: 'Open in free table editor',
|
||||
isOpenFreeTable: true,
|
||||
},
|
||||
{
|
||||
label: 'Open active chart',
|
||||
isActiveChart: true,
|
||||
},
|
||||
{
|
||||
label: 'Query designer',
|
||||
isQueryDesigner: true,
|
||||
},
|
||||
],
|
||||
views: [
|
||||
{
|
||||
label: 'Open data',
|
||||
tab: 'ViewDataTab',
|
||||
forceNewTab: true,
|
||||
},
|
||||
{
|
||||
label: 'Show CREATE VIEW script',
|
||||
sqlTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'Show CREATE TABLE script',
|
||||
sqlTemplate: 'CREATE TABLE',
|
||||
},
|
||||
{
|
||||
label: 'Export',
|
||||
isExport: true,
|
||||
},
|
||||
{
|
||||
label: 'Open in free table editor',
|
||||
isOpenFreeTable: true,
|
||||
},
|
||||
{
|
||||
label: 'Open structure',
|
||||
tab: 'TableStructureTab',
|
||||
},
|
||||
{
|
||||
label: 'Open active chart',
|
||||
isActiveChart: true,
|
||||
},
|
||||
{
|
||||
label: 'Query designer',
|
||||
isQueryDesigner: true,
|
||||
},
|
||||
],
|
||||
procedures: [
|
||||
{
|
||||
label: 'Show CREATE PROCEDURE script',
|
||||
sqlTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'Show EXECUTE script',
|
||||
sqlTemplate: 'EXECUTE PROCEDURE',
|
||||
},
|
||||
],
|
||||
functions: [
|
||||
{
|
||||
label: 'Show CREATE FUNCTION script',
|
||||
sqlTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const defaultTabs = {
|
||||
tables: 'TableDataTab',
|
||||
views: 'ViewDataTab',
|
||||
};
|
||||
|
||||
export async function openDatabaseObjectDetail(
|
||||
openNewTab,
|
||||
tabComponent,
|
||||
sqlTemplate,
|
||||
{ schemaName, pureName, conid, database, objectTypeField },
|
||||
forceNewTab
|
||||
) {
|
||||
const connection = await getConnectionInfo({ conid });
|
||||
const tooltip = `${connection.displayName || connection.server}\n${database}\n${fullDisplayName({
|
||||
schemaName,
|
||||
pureName,
|
||||
})}`;
|
||||
|
||||
openNewTab(
|
||||
{
|
||||
title: pureName,
|
||||
tooltip,
|
||||
icon: sqlTemplate ? 'img sql-file' : icons[objectTypeField],
|
||||
tabComponent: sqlTemplate ? 'QueryTab' : tabComponent,
|
||||
props: {
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
initialArgs: sqlTemplate ? { sqlTemplate } : null,
|
||||
},
|
||||
},
|
||||
undefined,
|
||||
{ forceNewTab }
|
||||
);
|
||||
}
|
||||
|
||||
function Menu({ data }) {
|
||||
const showModal = useShowModal();
|
||||
const openNewTab = useOpenNewTab();
|
||||
const extensions = useExtensions();
|
||||
|
||||
const getDriver = async () => {
|
||||
const conn = await getConnectionInfo(data);
|
||||
if (!conn) return;
|
||||
const driver = findEngineDriver(conn, extensions);
|
||||
return driver;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{menus[data.objectTypeField].map((menu) => (
|
||||
<DropDownMenuItem
|
||||
key={menu.label}
|
||||
onClick={async () => {
|
||||
if (menu.isExport) {
|
||||
showModal((modalState) => (
|
||||
<ImportExportModal
|
||||
modalState={modalState}
|
||||
initialValues={{
|
||||
sourceStorageType: 'database',
|
||||
sourceConnectionId: data.conid,
|
||||
sourceDatabaseName: data.database,
|
||||
sourceSchemaName: data.schemaName,
|
||||
sourceList: [data.pureName],
|
||||
}}
|
||||
/>
|
||||
));
|
||||
} else if (menu.isOpenFreeTable) {
|
||||
const coninfo = await getConnectionInfo(data);
|
||||
openNewTab({
|
||||
title: data.pureName,
|
||||
icon: 'img free-table',
|
||||
tabComponent: 'FreeTableTab',
|
||||
props: {
|
||||
initialArgs: {
|
||||
functionName: 'tableReader',
|
||||
props: {
|
||||
connection: {
|
||||
...coninfo,
|
||||
database: data.database,
|
||||
},
|
||||
schemaName: data.schemaName,
|
||||
pureName: data.pureName,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else if (menu.isActiveChart) {
|
||||
const driver = await getDriver();
|
||||
const dmp = driver.createDumper();
|
||||
dmp.put('^select * from %f', data);
|
||||
openNewTab(
|
||||
{
|
||||
title: data.pureName,
|
||||
icon: 'img chart',
|
||||
tabComponent: 'ChartTab',
|
||||
props: {
|
||||
conid: data.conid,
|
||||
database: data.database,
|
||||
},
|
||||
},
|
||||
{
|
||||
editor: {
|
||||
config: { chartType: 'bar' },
|
||||
sql: dmp.s,
|
||||
},
|
||||
}
|
||||
);
|
||||
} else if (menu.isQueryDesigner) {
|
||||
openNewTab(
|
||||
{
|
||||
title: data.pureName,
|
||||
icon: 'img query-design',
|
||||
tabComponent: 'QueryDesignTab',
|
||||
props: {
|
||||
conid: data.conid,
|
||||
database: data.database,
|
||||
},
|
||||
},
|
||||
{
|
||||
editor: {
|
||||
tables: [
|
||||
{
|
||||
...data,
|
||||
designerId: uuidv1(),
|
||||
left: 50,
|
||||
top: 50,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
} else {
|
||||
openDatabaseObjectDetail(openNewTab, menu.tab, menu.sqlTemplate, data, menu.forceNewTab);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{menu.label}
|
||||
</DropDownMenuItem>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function DatabaseObjectAppObject({ data, commonProps }) {
|
||||
const { conid, database, pureName, schemaName, objectTypeField } = data;
|
||||
const openNewTab = useOpenNewTab();
|
||||
const onClick = ({ schemaName, pureName }) => {
|
||||
openDatabaseObjectDetail(
|
||||
openNewTab,
|
||||
defaultTabs[objectTypeField],
|
||||
defaultTabs[objectTypeField] ? null : 'CREATE OBJECT',
|
||||
{
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
},
|
||||
false
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<AppObjectCore
|
||||
{...commonProps}
|
||||
data={data}
|
||||
title={schemaName ? `${schemaName}.${pureName}` : pureName}
|
||||
icon={icons[objectTypeField]}
|
||||
onClick={onClick}
|
||||
Menu={Menu}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
DatabaseObjectAppObject.extractKey = ({ schemaName, pureName }) =>
|
||||
schemaName ? `${schemaName}.${pureName}` : pureName;
|
||||
|
||||
DatabaseObjectAppObject.createMatcher = ({ pureName }) => (filter) => filterName(filter, pureName);
|
||||
|
||||
export default DatabaseObjectAppObject;
|
||||
@@ -0,0 +1,104 @@
|
||||
import React from 'react';
|
||||
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||
import FavoriteModal from '../modals/FavoriteModal';
|
||||
import useShowModal from '../modals/showModal';
|
||||
import axios from '../utility/axios';
|
||||
import { copyTextToClipboard } from '../utility/clipboard';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import useOpenNewTab from '../utility/useOpenNewTab';
|
||||
import { SavedFileAppObjectBase } from './SavedFileAppObject';
|
||||
|
||||
export function useOpenFavorite() {
|
||||
const openNewTab = useOpenNewTab();
|
||||
|
||||
const openFavorite = React.useCallback(
|
||||
async (favorite) => {
|
||||
const { icon, tabComponent, title, props, tabdata } = favorite;
|
||||
let tabdataNew = tabdata;
|
||||
if (props.savedFile) {
|
||||
const resp = await axios.post('files/load', {
|
||||
folder: props.savedFolder,
|
||||
file: props.savedFile,
|
||||
format: props.savedFormat,
|
||||
});
|
||||
tabdataNew = {
|
||||
...tabdata,
|
||||
editor: resp.data,
|
||||
};
|
||||
}
|
||||
openNewTab(
|
||||
{
|
||||
title,
|
||||
icon: icon || 'img favorite',
|
||||
props,
|
||||
tabComponent,
|
||||
},
|
||||
tabdataNew
|
||||
);
|
||||
},
|
||||
[openNewTab]
|
||||
);
|
||||
|
||||
return openFavorite;
|
||||
}
|
||||
|
||||
export function FavoriteFileAppObject({ data, commonProps }) {
|
||||
const { icon, tabComponent, title, props, tabdata, urlPath } = data;
|
||||
const openNewTab = useOpenNewTab();
|
||||
const showModal = useShowModal();
|
||||
const openFavorite = useOpenFavorite();
|
||||
const electron = getElectron();
|
||||
|
||||
const editFavorite = () => {
|
||||
showModal((modalState) => <FavoriteModal modalState={modalState} editingData={data} />);
|
||||
};
|
||||
|
||||
const editFavoriteJson = async () => {
|
||||
const resp = await axios.post('files/load', {
|
||||
folder: 'favorites',
|
||||
file: data.file,
|
||||
format: 'text',
|
||||
});
|
||||
|
||||
openNewTab(
|
||||
{
|
||||
icon: 'icon favorite',
|
||||
title,
|
||||
tabComponent: 'FavoriteEditorTab',
|
||||
props: {
|
||||
savedFile: data.file,
|
||||
savedFormat: 'text',
|
||||
savedFolder: 'favorites',
|
||||
},
|
||||
},
|
||||
{ editor: JSON.stringify(JSON.parse(resp.data), null, 2) }
|
||||
);
|
||||
};
|
||||
|
||||
const copyLink = () => {
|
||||
copyTextToClipboard(`${document.location.origin}#favorite=${urlPath}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<SavedFileAppObjectBase
|
||||
data={data}
|
||||
commonProps={commonProps}
|
||||
format="json"
|
||||
icon={icon || 'img favorite'}
|
||||
title={title}
|
||||
disableRename
|
||||
onLoad={async (data) => {
|
||||
openFavorite(data);
|
||||
}}
|
||||
menuExt={
|
||||
<>
|
||||
<DropDownMenuItem onClick={editFavorite}>Edit</DropDownMenuItem>
|
||||
<DropDownMenuItem onClick={editFavoriteJson}>Edit JSON definition</DropDownMenuItem>
|
||||
{!electron && urlPath && <DropDownMenuItem onClick={copyLink}>Copy link</DropDownMenuItem>}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
FavoriteFileAppObject.extractKey = (data) => data.file;
|
||||
@@ -1,13 +1,15 @@
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { filterName } from 'dbgate-datalib';
|
||||
import { AppObjectCore } from './AppObjectCore';
|
||||
|
||||
const macroAppObject = () => ({ name, type, title, group }, { setOpenedTabs }) => {
|
||||
const key = name;
|
||||
const icon = 'img macro';
|
||||
const matcher = (filter) => filterName(filter, name, title);
|
||||
const groupTitle = group;
|
||||
function MacroAppObject({ data, commonProps }) {
|
||||
const { name, type, title, group } = data;
|
||||
|
||||
return { title, key, icon, groupTitle, matcher };
|
||||
};
|
||||
return <AppObjectCore {...commonProps} data={data} title={title} icon={'img macro'} />;
|
||||
}
|
||||
|
||||
export default macroAppObject;
|
||||
MacroAppObject.extractKey = (data) => data.name;
|
||||
MacroAppObject.createMatcher = ({ name, title }) => (filter) => filterName(filter, name, title);
|
||||
|
||||
export default MacroAppObject;
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
import React from 'react';
|
||||
import axios from '../utility/axios';
|
||||
import _ from 'lodash';
|
||||
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||
import { AppObjectCore } from './AppObjectCore';
|
||||
import useNewQuery from '../query/useNewQuery';
|
||||
import { useCurrentDatabase } from '../utility/globalState';
|
||||
import ScriptWriter from '../impexp/ScriptWriter';
|
||||
import { extractPackageName } from 'dbgate-tools';
|
||||
import useShowModal from '../modals/showModal';
|
||||
import InputTextModal from '../modals/InputTextModal';
|
||||
import useHasPermission from '../utility/useHasPermission';
|
||||
import useOpenNewTab from '../utility/useOpenNewTab';
|
||||
import ConfirmModal from '../modals/ConfirmModal';
|
||||
|
||||
function Menu({ data, menuExt = null, title = undefined, disableRename = false }) {
|
||||
const hasPermission = useHasPermission();
|
||||
const showModal = useShowModal();
|
||||
const handleDelete = () => {
|
||||
showModal((modalState) => (
|
||||
<ConfirmModal
|
||||
modalState={modalState}
|
||||
message={`Really delete file ${title || data.file}?`}
|
||||
onConfirm={() => {
|
||||
axios.post('files/delete', data);
|
||||
}}
|
||||
/>
|
||||
));
|
||||
};
|
||||
const handleRename = () => {
|
||||
showModal((modalState) => (
|
||||
<InputTextModal
|
||||
modalState={modalState}
|
||||
value={data.file}
|
||||
label="New file name"
|
||||
header="Rename file"
|
||||
onConfirm={(newFile) => {
|
||||
axios.post('files/rename', { ...data, newFile });
|
||||
}}
|
||||
/>
|
||||
));
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{hasPermission(`files/${data.folder}/write`) && (
|
||||
<DropDownMenuItem onClick={handleDelete}>Delete</DropDownMenuItem>
|
||||
)}
|
||||
{hasPermission(`files/${data.folder}/write`) && !disableRename && (
|
||||
<DropDownMenuItem onClick={handleRename}>Rename</DropDownMenuItem>
|
||||
)}
|
||||
{menuExt}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function SavedFileAppObjectBase({
|
||||
data,
|
||||
commonProps,
|
||||
format,
|
||||
icon,
|
||||
onLoad,
|
||||
title = undefined,
|
||||
menuExt = null,
|
||||
disableRename = false,
|
||||
}) {
|
||||
const { file, folder } = data;
|
||||
|
||||
const onClick = async () => {
|
||||
const resp = await axios.post('files/load', { folder, file, format });
|
||||
onLoad(resp.data);
|
||||
};
|
||||
|
||||
return (
|
||||
<AppObjectCore
|
||||
{...commonProps}
|
||||
data={data}
|
||||
title={title || file}
|
||||
icon={icon}
|
||||
onClick={onClick}
|
||||
Menu={(props) => <Menu {...props} menuExt={menuExt} title={title} disableRename={disableRename} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SavedSqlFileAppObject({ data, commonProps }) {
|
||||
const { file, folder } = data;
|
||||
const newQuery = useNewQuery();
|
||||
const currentDatabase = useCurrentDatabase();
|
||||
const openNewTab = useOpenNewTab();
|
||||
|
||||
const connection = _.get(currentDatabase, 'connection');
|
||||
const database = _.get(currentDatabase, 'name');
|
||||
|
||||
const handleGenerateExecute = () => {
|
||||
const script = new ScriptWriter();
|
||||
const conn = {
|
||||
..._.omit(connection, ['displayName', '_id']),
|
||||
database,
|
||||
};
|
||||
script.put(`const sql = await dbgateApi.loadFile('${folder}/${file}');`);
|
||||
script.put(`await dbgateApi.executeQuery({ sql, connection: ${JSON.stringify(conn)} });`);
|
||||
// @ts-ignore
|
||||
script.requirePackage(extractPackageName(conn.engine));
|
||||
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Shell',
|
||||
icon: 'img shell',
|
||||
tabComponent: 'ShellTab',
|
||||
},
|
||||
{ editor: script.getScript() }
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<SavedFileAppObjectBase
|
||||
data={data}
|
||||
commonProps={commonProps}
|
||||
format="text"
|
||||
icon="img sql-file"
|
||||
menuExt={
|
||||
connection && database ? (
|
||||
<DropDownMenuItem onClick={handleGenerateExecute}>Generate shell execute</DropDownMenuItem>
|
||||
) : null
|
||||
}
|
||||
onLoad={(data) => {
|
||||
newQuery({
|
||||
title: file,
|
||||
initialData: data,
|
||||
// @ts-ignore
|
||||
savedFile: file,
|
||||
savedFolder: 'sql',
|
||||
savedFormat: 'text',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SavedShellFileAppObject({ data, commonProps }) {
|
||||
const { file, folder } = data;
|
||||
const openNewTab = useOpenNewTab();
|
||||
|
||||
return (
|
||||
<SavedFileAppObjectBase
|
||||
data={data}
|
||||
commonProps={commonProps}
|
||||
format="text"
|
||||
icon="img shell"
|
||||
onLoad={(data) => {
|
||||
openNewTab(
|
||||
{
|
||||
title: file,
|
||||
icon: 'img shell',
|
||||
tabComponent: 'ShellTab',
|
||||
props: {
|
||||
savedFile: file,
|
||||
savedFolder: 'shell',
|
||||
savedFormat: 'text',
|
||||
},
|
||||
},
|
||||
{ editor: data }
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SavedChartFileAppObject({ data, commonProps }) {
|
||||
const { file, folder } = data;
|
||||
const openNewTab = useOpenNewTab();
|
||||
|
||||
const currentDatabase = useCurrentDatabase();
|
||||
|
||||
const connection = _.get(currentDatabase, 'connection') || {};
|
||||
const database = _.get(currentDatabase, 'name');
|
||||
|
||||
const tooltip = `${connection.displayName || connection.server}\n${database}`;
|
||||
|
||||
return (
|
||||
<SavedFileAppObjectBase
|
||||
data={data}
|
||||
commonProps={commonProps}
|
||||
format="json"
|
||||
icon="img chart"
|
||||
onLoad={(data) => {
|
||||
openNewTab(
|
||||
{
|
||||
title: file,
|
||||
icon: 'img chart',
|
||||
tooltip,
|
||||
props: {
|
||||
conid: connection._id,
|
||||
database,
|
||||
savedFile: file,
|
||||
savedFolder: 'charts',
|
||||
savedFormat: 'json',
|
||||
},
|
||||
tabComponent: 'ChartTab',
|
||||
},
|
||||
{ editor: data }
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SavedQueryFileAppObject({ data, commonProps }) {
|
||||
const { file, folder } = data;
|
||||
const openNewTab = useOpenNewTab();
|
||||
|
||||
const currentDatabase = useCurrentDatabase();
|
||||
|
||||
const connection = _.get(currentDatabase, 'connection') || {};
|
||||
const database = _.get(currentDatabase, 'name');
|
||||
|
||||
const tooltip = `${connection.displayName || connection.server}\n${database}`;
|
||||
|
||||
return (
|
||||
<SavedFileAppObjectBase
|
||||
data={data}
|
||||
commonProps={commonProps}
|
||||
format="json"
|
||||
icon="img query-design"
|
||||
onLoad={(data) => {
|
||||
openNewTab(
|
||||
{
|
||||
title: file,
|
||||
icon: 'img query-design',
|
||||
tooltip,
|
||||
props: {
|
||||
conid: connection._id,
|
||||
database,
|
||||
savedFile: file,
|
||||
savedFolder: 'query',
|
||||
savedFormat: 'json',
|
||||
},
|
||||
tabComponent: 'QueryDesignTab',
|
||||
},
|
||||
{ editor: data }
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SavedMarkdownFileAppObject({ data, commonProps }) {
|
||||
const { file, folder } = data;
|
||||
const openNewTab = useOpenNewTab();
|
||||
|
||||
const showPage = () => {
|
||||
openNewTab({
|
||||
title: file,
|
||||
icon: 'img markdown',
|
||||
tabComponent: 'MarkdownViewTab',
|
||||
props: {
|
||||
savedFile: file,
|
||||
savedFolder: 'markdown',
|
||||
savedFormat: 'text',
|
||||
},
|
||||
});
|
||||
};
|
||||
return (
|
||||
<SavedFileAppObjectBase
|
||||
data={data}
|
||||
commonProps={commonProps}
|
||||
format="text"
|
||||
icon="img markdown"
|
||||
onLoad={(data) => {
|
||||
openNewTab(
|
||||
{
|
||||
title: file,
|
||||
icon: 'img markdown',
|
||||
tabComponent: 'MarkdownEditorTab',
|
||||
props: {
|
||||
savedFile: file,
|
||||
savedFolder: 'markdown',
|
||||
savedFormat: 'text',
|
||||
},
|
||||
},
|
||||
{ editor: data }
|
||||
);
|
||||
}}
|
||||
menuExt={<DropDownMenuItem onClick={showPage}>Show page</DropDownMenuItem>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SavedFileAppObject({ data, commonProps }) {
|
||||
const { folder } = data;
|
||||
const folderTypes = {
|
||||
sql: SavedSqlFileAppObject,
|
||||
shell: SavedShellFileAppObject,
|
||||
charts: SavedChartFileAppObject,
|
||||
markdown: SavedMarkdownFileAppObject,
|
||||
query: SavedQueryFileAppObject,
|
||||
};
|
||||
const AppObject = folderTypes[folder];
|
||||
if (AppObject) {
|
||||
return <AppObject data={data} commonProps={commonProps} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
[
|
||||
SavedSqlFileAppObject,
|
||||
SavedShellFileAppObject,
|
||||
SavedChartFileAppObject,
|
||||
SavedMarkdownFileAppObject,
|
||||
SavedFileAppObject,
|
||||
].forEach((fn) => {
|
||||
// @ts-ignore
|
||||
fn.extractKey = (data) => data.file;
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||
import axios from '../utility/axios';
|
||||
import { filterName } from 'dbgate-datalib';
|
||||
|
||||
function Menu({ data, setOpenedTabs }) {
|
||||
const handleDelete = () => {
|
||||
axios.post('archive/delete-folder', { folder: data.name });
|
||||
};
|
||||
return <>{data.name != 'default' && <DropDownMenuItem onClick={handleDelete}>Delete</DropDownMenuItem>}</>;
|
||||
}
|
||||
|
||||
const archiveFolderAppObject = () => ({ name }, { setOpenedTabs, currentArchive }) => {
|
||||
const key = name;
|
||||
const icon = 'img archive-folder';
|
||||
const isBold = name == currentArchive;
|
||||
const matcher = (filter) => filterName(filter, name);
|
||||
|
||||
return { title: name, key, icon, isBold, Menu, matcher };
|
||||
};
|
||||
|
||||
export default archiveFolderAppObject;
|
||||
@@ -1,15 +0,0 @@
|
||||
/** @param columnProps {import('dbgate-types').ColumnInfo} */
|
||||
function getColumnIcon(columnProps) {
|
||||
if (columnProps.autoIncrement) return 'img autoincrement';
|
||||
return 'img column';
|
||||
}
|
||||
|
||||
/** @param columnProps {import('dbgate-types').ColumnInfo} */
|
||||
export default function columnAppObject(columnProps, { setOpenedTabs }) {
|
||||
const title = columnProps.columnName;
|
||||
const key = title;
|
||||
const icon = getColumnIcon(columnProps);
|
||||
const isBold = columnProps.notNull;
|
||||
|
||||
return { title, key, icon, isBold };
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
/** @param props {import('dbgate-types').ConstraintInfo} */
|
||||
function getConstraintIcon(props) {
|
||||
if (props.constraintType == 'primaryKey') return 'img primary-key';
|
||||
if (props.constraintType == 'foreignKey') return 'img foreign-key';
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @param props {import('dbgate-types').ConstraintInfo} */
|
||||
export default function constraintAppObject(props, { setOpenedTabs }) {
|
||||
const title = props.constraintName;
|
||||
const key = title;
|
||||
const icon = getConstraintIcon(props);
|
||||
|
||||
return { title, key, icon };
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||
import { openNewTab } from '../utility/common';
|
||||
import { getConnectionInfo } from '../utility/metadataLoaders';
|
||||
import fullDisplayName from '../utility/fullDisplayName';
|
||||
import { filterName } from 'dbgate-datalib';
|
||||
import ImportExportModal from '../modals/ImportExportModal';
|
||||
|
||||
const icons = {
|
||||
tables: 'img table',
|
||||
views: 'img view',
|
||||
procedures: 'img procedure',
|
||||
functions: 'img function',
|
||||
};
|
||||
|
||||
const menus = {
|
||||
tables: [
|
||||
{
|
||||
label: 'Open data',
|
||||
tab: 'TableDataTab',
|
||||
},
|
||||
{
|
||||
label: 'Open structure',
|
||||
tab: 'TableStructureTab',
|
||||
},
|
||||
{
|
||||
label: 'Show CREATE TABLE script',
|
||||
sqlTemplate: 'CREATE TABLE',
|
||||
},
|
||||
{
|
||||
label: 'Export',
|
||||
isExport: true,
|
||||
},
|
||||
{
|
||||
label: 'Open in free table editor',
|
||||
isOpenFreeTable: true,
|
||||
},
|
||||
],
|
||||
views: [
|
||||
{
|
||||
label: 'Open data',
|
||||
tab: 'ViewDataTab',
|
||||
},
|
||||
{
|
||||
label: 'Show CREATE VIEW script',
|
||||
sqlTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'Show CREATE TABLE script',
|
||||
sqlTemplate: 'CREATE TABLE',
|
||||
},
|
||||
{
|
||||
label: 'Export',
|
||||
isExport: true,
|
||||
},
|
||||
{
|
||||
label: 'Open in free table editor',
|
||||
isOpenFreeTable: true,
|
||||
},
|
||||
{
|
||||
label: 'Open structure',
|
||||
tab: 'TableStructureTab',
|
||||
},
|
||||
],
|
||||
procedures: [
|
||||
{
|
||||
label: 'Show CREATE PROCEDURE script',
|
||||
sqlTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
{
|
||||
label: 'Show EXECUTE script',
|
||||
sqlTemplate: 'EXECUTE PROCEDURE',
|
||||
},
|
||||
],
|
||||
functions: [
|
||||
{
|
||||
label: 'Show CREATE FUNCTION script',
|
||||
sqlTemplate: 'CREATE OBJECT',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const defaultTabs = {
|
||||
tables: 'TableDataTab',
|
||||
views: 'ViewDataTab',
|
||||
};
|
||||
|
||||
export async function openDatabaseObjectDetail(
|
||||
setOpenedTabs,
|
||||
tabComponent,
|
||||
sqlTemplate,
|
||||
{ schemaName, pureName, conid, database, objectTypeField }
|
||||
) {
|
||||
const connection = await getConnectionInfo({ conid });
|
||||
const tooltip = `${connection.displayName || connection.server}\n${database}\n${fullDisplayName({
|
||||
schemaName,
|
||||
pureName,
|
||||
})}`;
|
||||
|
||||
openNewTab(setOpenedTabs, {
|
||||
title: pureName,
|
||||
tooltip,
|
||||
icon: sqlTemplate ? 'img sql-file' : icons[objectTypeField],
|
||||
tabComponent: sqlTemplate ? 'QueryTab' : tabComponent,
|
||||
props: {
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
initialArgs: sqlTemplate ? { sqlTemplate } : null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function Menu({ data, makeAppObj, setOpenedTabs, showModal }) {
|
||||
return (
|
||||
<>
|
||||
{menus[data.objectTypeField].map((menu) => (
|
||||
<DropDownMenuItem
|
||||
key={menu.label}
|
||||
onClick={async () => {
|
||||
if (menu.isExport) {
|
||||
showModal((modalState) => (
|
||||
<ImportExportModal
|
||||
modalState={modalState}
|
||||
initialValues={{
|
||||
sourceStorageType: 'database',
|
||||
sourceConnectionId: data.conid,
|
||||
sourceDatabaseName: data.database,
|
||||
sourceSchemaName: data.schemaName,
|
||||
sourceList: [data.pureName],
|
||||
}}
|
||||
/>
|
||||
));
|
||||
} else if (menu.isOpenFreeTable) {
|
||||
const coninfo = await getConnectionInfo(data);
|
||||
openNewTab(setOpenedTabs, {
|
||||
title: data.pureName,
|
||||
icon: 'img free-table',
|
||||
tabComponent: 'FreeTableTab',
|
||||
props: {
|
||||
initialData: {
|
||||
functionName: 'tableReader',
|
||||
props: {
|
||||
connection: {
|
||||
...coninfo,
|
||||
database: data.database,
|
||||
},
|
||||
schemaName: data.schemaName,
|
||||
pureName: data.pureName,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
openDatabaseObjectDetail(setOpenedTabs, menu.tab, menu.sqlTemplate, data);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{menu.label}
|
||||
</DropDownMenuItem>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const databaseObjectAppObject = () => (
|
||||
{ conid, database, pureName, schemaName, objectTypeField },
|
||||
{ setOpenedTabs }
|
||||
) => {
|
||||
const title = schemaName ? `${schemaName}.${pureName}` : pureName;
|
||||
const key = title;
|
||||
const icon = icons[objectTypeField];
|
||||
// const Icon = (props) => getIconImage(icons[objectTypeField], props);
|
||||
const onClick = ({ schemaName, pureName }) => {
|
||||
openDatabaseObjectDetail(
|
||||
setOpenedTabs,
|
||||
defaultTabs[objectTypeField],
|
||||
defaultTabs[objectTypeField] ? null : 'CREATE OBJECT',
|
||||
{
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
}
|
||||
);
|
||||
};
|
||||
const matcher = (filter) => filterName(filter, pureName);
|
||||
const groupTitle = _.startCase(objectTypeField);
|
||||
|
||||
return { title, key, icon, Menu, onClick, matcher, groupTitle };
|
||||
};
|
||||
|
||||
export default databaseObjectAppObject;
|
||||
@@ -1,41 +0,0 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||
|
||||
function Menu({ data, setSavedSqlFiles }) {
|
||||
const handleDelete = () => {
|
||||
setSavedSqlFiles((files) => files.filter((x) => x.storageKey != data.storageKey));
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<DropDownMenuItem onClick={handleDelete}>Delete</DropDownMenuItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const savedSqlFileAppObject = () => ({ name, storageKey }, { setOpenedTabs, newQuery, openedTabs }) => {
|
||||
const key = storageKey;
|
||||
const title = name;
|
||||
const icon = 'img sql-file';
|
||||
|
||||
const onClick = () => {
|
||||
const existing = openedTabs.find((x) => x.props && x.props.storageKey == storageKey);
|
||||
if (existing) {
|
||||
setOpenedTabs(
|
||||
openedTabs.map((x) => ({
|
||||
...x,
|
||||
selected: x == existing,
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
newQuery({
|
||||
title,
|
||||
storageKey,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return { title, key, icon, onClick, Menu };
|
||||
};
|
||||
|
||||
export default savedSqlFileAppObject;
|
||||
@@ -0,0 +1,154 @@
|
||||
import React from 'react';
|
||||
import Chart from 'react-chartjs-2';
|
||||
import _ from 'lodash';
|
||||
import styled from 'styled-components';
|
||||
import useTheme from '../theme/useTheme';
|
||||
import useDimensions from '../utility/useDimensions';
|
||||
import { HorizontalSplitter } from '../widgets/Splitter';
|
||||
import WidgetColumnBar, { WidgetColumnBarItem } from '../widgets/WidgetColumnBar';
|
||||
import { FormCheckboxField, FormSelectField, FormTextField } from '../utility/forms';
|
||||
import DataChart from './DataChart';
|
||||
import { FormProviderCore } from '../utility/FormProvider';
|
||||
import { loadChartData, loadChartStructure } from './chartDataLoader';
|
||||
import useExtensions from '../utility/useExtensions';
|
||||
import { getConnectionInfo } from '../utility/metadataLoaders';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import { FormFieldTemplateTiny } from '../utility/formStyle';
|
||||
import { ManagerInnerContainer } from '../datagrid/ManagerStyles';
|
||||
import { presetPrimaryColors } from '@ant-design/colors';
|
||||
import ErrorInfo from '../widgets/ErrorInfo';
|
||||
|
||||
const LeftContainer = styled.div`
|
||||
background-color: ${(props) => props.theme.manager_background};
|
||||
display: flex;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export default function ChartEditor({ data, config, setConfig, sql, conid, database }) {
|
||||
const [managerSize, setManagerSize] = React.useState(0);
|
||||
const theme = useTheme();
|
||||
const extensions = useExtensions();
|
||||
const [error, setError] = React.useState(null);
|
||||
|
||||
const [availableColumnNames, setAvailableColumnNames] = React.useState([]);
|
||||
const [loadedData, setLoadedData] = React.useState(null);
|
||||
|
||||
const getDriver = async () => {
|
||||
const conn = await getConnectionInfo({ conid });
|
||||
if (!conn) return;
|
||||
const driver = findEngineDriver(conn, extensions);
|
||||
return driver;
|
||||
};
|
||||
|
||||
const handleLoadColumns = async () => {
|
||||
const driver = await getDriver();
|
||||
if (!driver) return;
|
||||
try {
|
||||
const columns = await loadChartStructure(driver, conid, database, sql);
|
||||
setAvailableColumnNames(columns);
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadData = async () => {
|
||||
const driver = await getDriver();
|
||||
if (!driver) return;
|
||||
const loaded = await loadChartData(driver, conid, database, sql, config);
|
||||
if (!loaded) return;
|
||||
const { columns, rows } = loaded;
|
||||
setLoadedData({
|
||||
structure: columns,
|
||||
rows,
|
||||
});
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (sql && conid && database) {
|
||||
handleLoadColumns();
|
||||
}
|
||||
}, [sql, conid, database, extensions]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (data) {
|
||||
setAvailableColumnNames(data ? data.structure.columns.map((x) => x.columnName) : []);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (config.labelColumn && sql && conid && database) {
|
||||
handleLoadData();
|
||||
}
|
||||
}, [config, sql, conid, database, availableColumnNames]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<ErrorInfo message={error} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FormProviderCore values={config} setValues={setConfig} template={FormFieldTemplateTiny}>
|
||||
<HorizontalSplitter initialValue="300px" size={managerSize} setSize={setManagerSize}>
|
||||
<LeftContainer theme={theme}>
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Style" name="style" height="40%">
|
||||
<ManagerInnerContainer style={{ maxWidth: managerSize }}>
|
||||
<FormSelectField label="Chart type" name="chartType">
|
||||
<option value="bar">Bar</option>
|
||||
<option value="line">Line</option>
|
||||
{/* <option value="radar">Radar</option> */}
|
||||
<option value="pie">Pie</option>
|
||||
<option value="polarArea">Polar area</option>
|
||||
{/* <option value="bubble">Bubble</option>
|
||||
<option value="scatter">Scatter</option> */}
|
||||
</FormSelectField>
|
||||
<FormTextField label="Color set" name="colorSeed" />
|
||||
<FormSelectField label="Truncate from" name="truncateFrom">
|
||||
<option value="begin">Begin</option>
|
||||
<option value="end">End (most recent data for datetime)</option>
|
||||
</FormSelectField>
|
||||
<FormTextField label="Truncate limit" name="truncateLimit" />
|
||||
<FormCheckboxField label="Show relative values" name="showRelativeValues" />
|
||||
</ManagerInnerContainer>
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Data" name="data">
|
||||
<ManagerInnerContainer style={{ maxWidth: managerSize }}>
|
||||
{availableColumnNames.length > 0 && (
|
||||
<FormSelectField label="Label column" name="labelColumn">
|
||||
<option value=""></option>
|
||||
{availableColumnNames.map((col) => (
|
||||
<option value={col} key={col}>
|
||||
{col}
|
||||
</option>
|
||||
))}
|
||||
</FormSelectField>
|
||||
)}
|
||||
{availableColumnNames.map((col) => (
|
||||
<React.Fragment key={col}>
|
||||
<FormCheckboxField label={col} name={`dataColumn_${col}`} />
|
||||
{config[`dataColumn_${col}`] && (
|
||||
<FormSelectField label="Color" name={`dataColumnColor_${col}`}>
|
||||
<option value="">Random</option>
|
||||
|
||||
{_.keys(presetPrimaryColors).map((color) => (
|
||||
<option value={color} key={color}>
|
||||
{_.startCase(color)}
|
||||
</option>
|
||||
))}
|
||||
</FormSelectField>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</ManagerInnerContainer>
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
</LeftContainer>
|
||||
|
||||
<DataChart data={data || loadedData} />
|
||||
</HorizontalSplitter>
|
||||
</FormProviderCore>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import useHasPermission from '../utility/useHasPermission';
|
||||
import ToolbarButton from '../widgets/ToolbarButton';
|
||||
|
||||
export default function ChartToolbar({ save, modelState, dispatchModel }) {
|
||||
const hasPermission = useHasPermission();
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasPermission('files/charts/write') && (
|
||||
<ToolbarButton onClick={save} icon="icon save">
|
||||
Save
|
||||
</ToolbarButton>
|
||||
)}
|
||||
<ToolbarButton disabled={!modelState.canUndo} onClick={() => dispatchModel({ type: 'undo' })} icon="icon undo">
|
||||
Undo
|
||||
</ToolbarButton>
|
||||
<ToolbarButton disabled={!modelState.canRedo} onClick={() => dispatchModel({ type: 'redo' })} icon="icon redo">
|
||||
Redo
|
||||
</ToolbarButton>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import Chart from 'react-chartjs-2';
|
||||
import randomcolor from 'randomcolor';
|
||||
import styled from 'styled-components';
|
||||
import useDimensions from '../utility/useDimensions';
|
||||
import { useForm } from '../utility/FormProvider';
|
||||
import useTheme from '../theme/useTheme';
|
||||
import moment from 'moment';
|
||||
|
||||
const ChartWrapper = styled.div`
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
function getTimeAxis(labels) {
|
||||
const res = [];
|
||||
for (const label of labels) {
|
||||
const parsed = moment(label);
|
||||
if (!parsed.isValid()) return null;
|
||||
const iso = parsed.toISOString();
|
||||
if (iso < '1850-01-01T00:00:00' || iso > '2150-01-01T00:00:00') return null;
|
||||
res.push(parsed);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function getLabels(labelValues, timeAxis, chartType) {
|
||||
if (!timeAxis) return labelValues;
|
||||
if (chartType === 'line') return timeAxis.map((x) => x.toDate());
|
||||
return timeAxis.map((x) => x.format('D. M. YYYY'));
|
||||
}
|
||||
|
||||
function getOptions(timeAxis, chartType) {
|
||||
if (timeAxis && chartType === 'line') {
|
||||
return {
|
||||
scales: {
|
||||
xAxes: [
|
||||
{
|
||||
type: 'time',
|
||||
distribution: 'linear',
|
||||
|
||||
time: {
|
||||
tooltipFormat: 'D. M. YYYY HH:mm',
|
||||
displayFormats: {
|
||||
millisecond: 'HH:mm:ss.SSS',
|
||||
second: 'HH:mm:ss',
|
||||
minute: 'HH:mm',
|
||||
hour: 'D.M hA',
|
||||
day: 'D. M.',
|
||||
week: 'D. M. YYYY',
|
||||
month: 'MM-YYYY',
|
||||
quarter: '[Q]Q - YYYY',
|
||||
year: 'YYYY',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function createChartData(freeData, labelColumn, dataColumns, colorSeed, chartType, dataColumnColors, theme) {
|
||||
if (!freeData || !labelColumn || !dataColumns || !freeData.rows || dataColumns.length == 0) return [{}, {}];
|
||||
const colors = randomcolor({
|
||||
count: _.max([freeData.rows.length, dataColumns.length, 1]),
|
||||
seed: colorSeed,
|
||||
});
|
||||
let backgroundColor = null;
|
||||
let borderColor = null;
|
||||
const labelValues = freeData.rows.map((x) => x[labelColumn]);
|
||||
const timeAxis = getTimeAxis(labelValues);
|
||||
const labels = getLabels(labelValues, timeAxis, chartType);
|
||||
const res = {
|
||||
labels,
|
||||
datasets: dataColumns.map((dataColumn, columnIndex) => {
|
||||
if (chartType == 'line' || chartType == 'bar') {
|
||||
const color = dataColumnColors[dataColumn];
|
||||
if (color) {
|
||||
backgroundColor = theme.main_palettes[color][4] + '80';
|
||||
borderColor = theme.main_palettes[color][7];
|
||||
} else {
|
||||
backgroundColor = colors[columnIndex] + '80';
|
||||
borderColor = colors[columnIndex];
|
||||
}
|
||||
} else {
|
||||
backgroundColor = colors;
|
||||
}
|
||||
|
||||
return {
|
||||
label: dataColumn,
|
||||
data: freeData.rows.map((row) => row[dataColumn]),
|
||||
backgroundColor,
|
||||
borderColor,
|
||||
borderWidth: 1,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
const options = getOptions(timeAxis, chartType);
|
||||
return [res, options];
|
||||
}
|
||||
|
||||
export function extractDataColumns(values) {
|
||||
const dataColumns = [];
|
||||
for (const key in values) {
|
||||
if (key.startsWith('dataColumn_') && values[key]) {
|
||||
dataColumns.push(key.substring('dataColumn_'.length));
|
||||
}
|
||||
}
|
||||
return dataColumns;
|
||||
}
|
||||
export function extractDataColumnColors(values, dataColumns) {
|
||||
const res = {};
|
||||
for (const column of dataColumns) {
|
||||
const color = values[`dataColumnColor_${column}`];
|
||||
if (color) res[column] = color;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export default function DataChart({ data }) {
|
||||
const [containerRef, { height: containerHeight, width: containerWidth }] = useDimensions();
|
||||
const { values } = useForm();
|
||||
const theme = useTheme();
|
||||
|
||||
const { labelColumn } = values;
|
||||
const dataColumns = extractDataColumns(values);
|
||||
const dataColumnColors = extractDataColumnColors(values, dataColumns);
|
||||
const [chartData, options] = createChartData(
|
||||
data,
|
||||
labelColumn,
|
||||
dataColumns,
|
||||
values.colorSeed || '5',
|
||||
values.chartType,
|
||||
dataColumnColors,
|
||||
theme
|
||||
);
|
||||
|
||||
return (
|
||||
<ChartWrapper ref={containerRef}>
|
||||
<Chart
|
||||
key={`${values.chartType}|${containerWidth}|${containerHeight}`}
|
||||
width={containerWidth}
|
||||
height={containerHeight}
|
||||
data={chartData}
|
||||
type={values.chartType}
|
||||
options={{
|
||||
...options,
|
||||
// elements: {
|
||||
// point: {
|
||||
// radius: 0,
|
||||
// },
|
||||
// },
|
||||
// tooltips: {
|
||||
// mode: 'index',
|
||||
// intersect: false,
|
||||
// },
|
||||
}}
|
||||
/>
|
||||
</ChartWrapper>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { dumpSqlSelect, Select } from 'dbgate-sqltree';
|
||||
import { EngineDriver } from 'dbgate-types';
|
||||
import axios from '../utility/axios';
|
||||
import _ from 'lodash';
|
||||
import { extractDataColumns } from './DataChart';
|
||||
|
||||
export async function loadChartStructure(driver: EngineDriver, conid, database, sql) {
|
||||
const select: Select = {
|
||||
commandType: 'select',
|
||||
selectAll: true,
|
||||
topRecords: 1,
|
||||
from: {
|
||||
subQueryString: sql,
|
||||
alias: 'subq',
|
||||
},
|
||||
};
|
||||
|
||||
const dmp = driver.createDumper();
|
||||
dumpSqlSelect(dmp, select);
|
||||
const resp = await axios.post('database-connections/query-data', { conid, database, sql: dmp.s });
|
||||
if (resp.data.errorMessage) throw new Error(resp.data.errorMessage);
|
||||
return resp.data.columns.map((x) => x.columnName);
|
||||
}
|
||||
|
||||
export async function loadChartData(driver: EngineDriver, conid, database, sql, config) {
|
||||
const dataColumns = extractDataColumns(config);
|
||||
const { labelColumn, truncateFrom, truncateLimit, showRelativeValues } = config;
|
||||
if (!labelColumn || !dataColumns || dataColumns.length == 0) return null;
|
||||
|
||||
const select: Select = {
|
||||
commandType: 'select',
|
||||
|
||||
columns: [
|
||||
{
|
||||
exprType: 'column',
|
||||
source: { alias: 'subq' },
|
||||
columnName: labelColumn,
|
||||
alias: labelColumn,
|
||||
},
|
||||
// @ts-ignore
|
||||
...dataColumns.map((columnName) => ({
|
||||
exprType: 'call',
|
||||
func: 'SUM',
|
||||
args: [
|
||||
{
|
||||
exprType: 'column',
|
||||
columnName,
|
||||
source: { alias: 'subq' },
|
||||
},
|
||||
],
|
||||
alias: columnName,
|
||||
})),
|
||||
],
|
||||
topRecords: truncateLimit || 100,
|
||||
from: {
|
||||
subQueryString: sql,
|
||||
alias: 'subq',
|
||||
},
|
||||
groupBy: [
|
||||
{
|
||||
exprType: 'column',
|
||||
source: { alias: 'subq' },
|
||||
columnName: labelColumn,
|
||||
},
|
||||
],
|
||||
orderBy: [
|
||||
{
|
||||
exprType: 'column',
|
||||
source: { alias: 'subq' },
|
||||
columnName: labelColumn,
|
||||
direction: truncateFrom == 'end' ? 'DESC' : 'ASC',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const dmp = driver.createDumper();
|
||||
dumpSqlSelect(dmp, select);
|
||||
const resp = await axios.post('database-connections/query-data', { conid, database, sql: dmp.s });
|
||||
let { rows, columns } = resp.data;
|
||||
if (truncateFrom == 'end' && rows) {
|
||||
rows = _.reverse([...rows]);
|
||||
}
|
||||
if (showRelativeValues) {
|
||||
const maxValues = dataColumns.map((col) => _.max(rows.map((row) => row[col])));
|
||||
for (const [col, max] of _.zip(dataColumns, maxValues)) {
|
||||
if (!max) continue;
|
||||
if (!_.isNumber(max)) continue;
|
||||
if (!(max > 0)) continue;
|
||||
rows = rows.map((row) => ({
|
||||
...row,
|
||||
[col]: (row[col] / max) * 100,
|
||||
}));
|
||||
// columns = columns.map((x) => {
|
||||
// if (x.columnName == col) {
|
||||
// return { columnName: `${col} %` };
|
||||
// }
|
||||
// return x;
|
||||
// });
|
||||
}
|
||||
}
|
||||
return {
|
||||
columns,
|
||||
rows,
|
||||
};
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import DropDownButton from '../widgets/DropDownButton';
|
||||
import { DropDownMenuItem, DropDownMenuDivider } from '../modals/DropDownMenu';
|
||||
import { useSplitterDrag } from '../widgets/Splitter';
|
||||
import { isTypeDateTime } from 'dbgate-tools';
|
||||
import { openDatabaseObjectDetail } from '../appobj/databaseObjectAppObject';
|
||||
import { openDatabaseObjectDetail } from '../appobj/DatabaseObjectAppObject';
|
||||
import { useSetOpenedTabs } from '../utility/globalState';
|
||||
import { FontIcon } from '../icons';
|
||||
import useTheme from '../theme/useTheme';
|
||||
|
||||
@@ -5,13 +5,13 @@ import styled from 'styled-components';
|
||||
import { FontIcon } from '../icons';
|
||||
|
||||
const Label = styled.span`
|
||||
font-weight: ${props => (props.notNull ? 'bold' : 'normal')};
|
||||
font-weight: ${(props) => (props.notNull ? 'bold' : 'normal')};
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
/** @param column {import('dbgate-datalib').DisplayColumn|import('dbgate-types').ColumnInfo} */
|
||||
export default function ColumnLabel(column) {
|
||||
let icon = null;
|
||||
let icon = column.forceIcon ? 'img column' : null;
|
||||
if (column.autoIncrement) icon = 'img autoincrement';
|
||||
if (column.foreignKey) icon = 'img foreign-key';
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { DropDownMenuItem, DropDownMenuDivider, showMenu } from '../modals/DropDownMenu';
|
||||
import { DropDownMenuItem, DropDownMenuDivider } from '../modals/DropDownMenu';
|
||||
import styled from 'styled-components';
|
||||
import keycodes from '../utility/keycodes';
|
||||
import { parseFilter, createMultiLineFilter } from 'dbgate-filterparser';
|
||||
@@ -10,6 +10,7 @@ import FilterMultipleValuesModal from '../modals/FilterMultipleValuesModal';
|
||||
import SetFilterModal from '../modals/SetFilterModal';
|
||||
import { FontIcon } from '../icons';
|
||||
import useTheme from '../theme/useTheme';
|
||||
import { useShowMenu } from '../modals/showMenu';
|
||||
// import { $ } from '../../Utility/jquery';
|
||||
// import autobind from 'autobind-decorator';
|
||||
// import * as React from 'react';
|
||||
@@ -178,10 +179,11 @@ export default function DataFilterControl({
|
||||
filterType,
|
||||
filter,
|
||||
setFilter,
|
||||
focusIndex,
|
||||
onFocusGrid,
|
||||
focusIndex = 0,
|
||||
onFocusGrid = undefined,
|
||||
}) {
|
||||
const showModal = useShowModal();
|
||||
const showMenu = useShowMenu();
|
||||
const theme = useTheme();
|
||||
const [filterState, setFilterState] = React.useState('empty');
|
||||
const setFilterText = (filter) => {
|
||||
@@ -227,7 +229,7 @@ export default function DataFilterControl({
|
||||
setFilterText('');
|
||||
}
|
||||
if (ev.keyCode == keycodes.downArrow) {
|
||||
onFocusGrid();
|
||||
if (onFocusGrid) onFocusGrid();
|
||||
// ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ export default function DataGridContextMenu({
|
||||
filterSelectedValue,
|
||||
openQuery,
|
||||
openFreeTable,
|
||||
openChartSelection,
|
||||
openActiveChart,
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
@@ -53,6 +55,8 @@ export default function DataGridContextMenu({
|
||||
)}
|
||||
{openQuery && <DropDownMenuItem onClick={openQuery}>Open query</DropDownMenuItem>}
|
||||
<DropDownMenuItem onClick={openFreeTable}>Open selection in free table editor</DropDownMenuItem>
|
||||
<DropDownMenuItem onClick={openChartSelection}>Open chart from selection</DropDownMenuItem>
|
||||
{openActiveChart && <DropDownMenuItem onClick={openActiveChart}>Open active chart</DropDownMenuItem>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,14 +22,15 @@ import DataGridToolbar from './DataGridToolbar';
|
||||
// import usePropsCompare from '../utility/usePropsCompare';
|
||||
import ColumnHeaderControl from './ColumnHeaderControl';
|
||||
import InlineButton from '../widgets/InlineButton';
|
||||
import { showMenu } from '../modals/DropDownMenu';
|
||||
import DataGridContextMenu from './DataGridContextMenu';
|
||||
import LoadingInfo from '../widgets/LoadingInfo';
|
||||
import ErrorInfo from '../widgets/ErrorInfo';
|
||||
import { openNewTab } from '../utility/common';
|
||||
import { useSetOpenedTabs } from '../utility/globalState';
|
||||
import { FontIcon } from '../icons';
|
||||
import useTheme from '../theme/useTheme';
|
||||
import { useShowMenu } from '../modals/showMenu';
|
||||
import useOpenNewTab from '../utility/useOpenNewTab';
|
||||
import axios from '../utility/axios';
|
||||
|
||||
const GridContainer = styled.div`
|
||||
position: absolute;
|
||||
@@ -106,6 +107,7 @@ export default function DataGridCore(props) {
|
||||
isLoadedAll,
|
||||
loadedTime,
|
||||
exportGrid,
|
||||
openActiveChart,
|
||||
allRowCount,
|
||||
openQuery,
|
||||
onSave,
|
||||
@@ -117,7 +119,7 @@ export default function DataGridCore(props) {
|
||||
} = props;
|
||||
// console.log('RENDER GRID', display.baseTable.pureName);
|
||||
const columns = React.useMemo(() => display.allColumns, [display]);
|
||||
const setOpenedTabs = useSetOpenedTabs();
|
||||
const openNewTab = useOpenNewTab();
|
||||
|
||||
// usePropsCompare(props);
|
||||
|
||||
@@ -138,6 +140,7 @@ export default function DataGridCore(props) {
|
||||
const [autofillDragStartCell, setAutofillDragStartCell] = React.useState(nullCell);
|
||||
const [autofillSelectedCells, setAutofillSelectedCells] = React.useState(emptyCellArray);
|
||||
const [focusFilterInputs, setFocusFilterInputs] = React.useState({});
|
||||
const showMenu = useShowMenu();
|
||||
|
||||
const autofillMarkerCell = React.useMemo(
|
||||
() =>
|
||||
@@ -257,7 +260,7 @@ export default function DataGridCore(props) {
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
if (display.groupColumns) {
|
||||
if (display.groupColumns && display.baseTable) {
|
||||
props.onReferenceClick({
|
||||
schemaName: display.baseTable.schemaName,
|
||||
pureName: display.baseTable.pureName,
|
||||
@@ -268,7 +271,7 @@ export default function DataGridCore(props) {
|
||||
})),
|
||||
});
|
||||
}
|
||||
}, [stableStringify(display && display.groupColumns)]);
|
||||
}, [display.baseTable, stableStringify(display && display.groupColumns)]);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -320,22 +323,44 @@ export default function DataGridCore(props) {
|
||||
setFirstVisibleColumnScrollIndex(value);
|
||||
};
|
||||
|
||||
const handleOpenFreeTable = () => {
|
||||
const getSelectedFreeData = () => {
|
||||
const columns = getSelectedColumns();
|
||||
const rows = getSelectedRowData().map((row) => _.pickBy(row, (v, col) => columns.find((x) => x.columnName == col)));
|
||||
openNewTab(setOpenedTabs, {
|
||||
title: 'selection',
|
||||
icon: 'img free-table',
|
||||
tabComponent: 'FreeTableTab',
|
||||
props: {
|
||||
initialData: {
|
||||
structure: {
|
||||
columns,
|
||||
},
|
||||
rows,
|
||||
},
|
||||
return {
|
||||
structure: {
|
||||
columns,
|
||||
},
|
||||
});
|
||||
rows,
|
||||
};
|
||||
};
|
||||
|
||||
const handleOpenFreeTable = () => {
|
||||
openNewTab(
|
||||
{
|
||||
title: 'selection',
|
||||
icon: 'img free-table',
|
||||
tabComponent: 'FreeTableTab',
|
||||
props: {},
|
||||
},
|
||||
{ editor: getSelectedFreeData() }
|
||||
);
|
||||
};
|
||||
|
||||
const handleOpenChart = () => {
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Chart',
|
||||
icon: 'img chart',
|
||||
tabComponent: 'ChartTab',
|
||||
props: {},
|
||||
},
|
||||
{
|
||||
editor: {
|
||||
data: getSelectedFreeData(),
|
||||
config: { chartType: 'bar' },
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const handleContextMenu = (event) => {
|
||||
@@ -354,6 +379,8 @@ export default function DataGridCore(props) {
|
||||
filterSelectedValue={display.filterable ? filterSelectedValue : null}
|
||||
openQuery={openQuery}
|
||||
openFreeTable={handleOpenFreeTable}
|
||||
openChartSelection={handleOpenChart}
|
||||
openActiveChart={openActiveChart}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1046,7 +1073,15 @@ export default function DataGridCore(props) {
|
||||
props.toolbarPortalRef.current &&
|
||||
tabVisible &&
|
||||
ReactDOM.createPortal(
|
||||
<DataGridToolbar reload={() => display.reload()} save={handleSave} grider={grider} />,
|
||||
<DataGridToolbar
|
||||
reload={() => display.reload()}
|
||||
save={handleSave}
|
||||
grider={grider}
|
||||
reconnect={async () => {
|
||||
await axios.post('database-connections/refresh', { conid, database });
|
||||
display.reload();
|
||||
}}
|
||||
/>,
|
||||
props.toolbarPortalRef.current
|
||||
)}
|
||||
{isLoading && <LoadingInfo wrapper message="Loading data" />}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import React from 'react';
|
||||
import ToolbarButton from '../widgets/ToolbarButton';
|
||||
|
||||
export default function DataGridToolbar({ reload, grider, save }) {
|
||||
export default function DataGridToolbar({ reload, reconnect, grider, save }) {
|
||||
return (
|
||||
<>
|
||||
<ToolbarButton onClick={reload} icon="icon reload">
|
||||
Refresh
|
||||
</ToolbarButton>
|
||||
<ToolbarButton onClick={reconnect} icon="icon connection">
|
||||
Reconnect
|
||||
</ToolbarButton>
|
||||
<ToolbarButton disabled={!grider.canUndo} onClick={() => grider.undo()} icon="icon undo">
|
||||
Undo
|
||||
</ToolbarButton>
|
||||
|
||||
@@ -6,13 +6,13 @@ import useSocket from '../utility/SocketProvider';
|
||||
import useShowModal from '../modals/showModal';
|
||||
import ImportExportModal from '../modals/ImportExportModal';
|
||||
import { changeSetToSql, createChangeSet, getChangeSetInsertedRows } from 'dbgate-datalib';
|
||||
import { openNewTab } from '../utility/common';
|
||||
import LoadingDataGridCore from './LoadingDataGridCore';
|
||||
import ChangeSetGrider from './ChangeSetGrider';
|
||||
import { scriptToSql } from 'dbgate-sqltree';
|
||||
import useModalState from '../modals/useModalState';
|
||||
import ConfirmSqlModal from '../modals/ConfirmSqlModal';
|
||||
import ErrorMessageModal from '../modals/ErrorMessageModal';
|
||||
import useOpenNewTab from '../utility/useOpenNewTab';
|
||||
|
||||
/** @param props {import('./types').DataGridProps} */
|
||||
async function loadDataPage(props, offset, limit) {
|
||||
@@ -62,7 +62,7 @@ async function loadRowCount(props) {
|
||||
export default function SqlDataGridCore(props) {
|
||||
const { conid, database, display, changeSetState, dispatchChangeSet } = props;
|
||||
const showModal = useShowModal();
|
||||
const setOpenedTabs = useSetOpenedTabs();
|
||||
const openNewTab = useOpenNewTab();
|
||||
|
||||
const confirmSqlModalState = useModalState();
|
||||
const [confirmSql, setConfirmSql] = React.useState('');
|
||||
@@ -80,8 +80,29 @@ export default function SqlDataGridCore(props) {
|
||||
initialValues.sourceList = display.baseTable ? [display.baseTable.pureName] : [];
|
||||
showModal((modalState) => <ImportExportModal modalState={modalState} initialValues={initialValues} />);
|
||||
}
|
||||
function openActiveChart() {
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Chart',
|
||||
icon: 'img chart',
|
||||
tabComponent: 'ChartTab',
|
||||
props: {
|
||||
conid,
|
||||
database,
|
||||
},
|
||||
},
|
||||
{
|
||||
editor: {
|
||||
config: { chartType: 'bar' },
|
||||
sql: display.getExportQuery((select) => {
|
||||
select.orderBy = null;
|
||||
}),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
function openQuery() {
|
||||
openNewTab(setOpenedTabs, {
|
||||
openNewTab({
|
||||
title: 'Query',
|
||||
icon: 'img sql-file',
|
||||
tabComponent: 'QueryTab',
|
||||
@@ -131,6 +152,7 @@ export default function SqlDataGridCore(props) {
|
||||
<LoadingDataGridCore
|
||||
{...props}
|
||||
exportGrid={exportGrid}
|
||||
openActiveChart={openActiveChart}
|
||||
openQuery={openQuery}
|
||||
loadDataPage={loadDataPage}
|
||||
dataPageAvailable={dataPageAvailable}
|
||||
|
||||
@@ -122,8 +122,11 @@ export default function TableDataGrid({
|
||||
(selectedRows, loadedTime) => {
|
||||
setMyLoadedTime(loadedTime);
|
||||
if (!reference) return;
|
||||
|
||||
const filtersBase = display && display.isGrouped ? config.filters : childConfig.filters;
|
||||
|
||||
const filters = {
|
||||
...childConfig.filters,
|
||||
...filtersBase,
|
||||
..._.fromPairs(
|
||||
reference.columns.map((col) => [
|
||||
col.refName,
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import DesignerTable from './DesignerTable';
|
||||
import uuidv1 from 'uuid/v1';
|
||||
import _ from 'lodash';
|
||||
import useTheme from '../theme/useTheme';
|
||||
import DesignerReference from './DesignerReference';
|
||||
import cleanupDesignColumns from './cleanupDesignColumns';
|
||||
import { isConnectedByReference } from './designerTools';
|
||||
import { getTableInfo } from '../utility/metadataLoaders';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
flex: 1;
|
||||
background-color: ${(props) => props.theme.designer_background};
|
||||
overflow: scroll;
|
||||
`;
|
||||
|
||||
const Canvas = styled.div`
|
||||
width: 3000px;
|
||||
height: 3000px;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const EmptyInfo = styled.div`
|
||||
margin: 50px;
|
||||
font-size: 20px;
|
||||
`;
|
||||
|
||||
function fixPositions(tables) {
|
||||
const minLeft = _.min(tables.map((x) => x.left));
|
||||
const minTop = _.min(tables.map((x) => x.top));
|
||||
if (minLeft < 0 || minTop < 0) {
|
||||
const dLeft = minLeft < 0 ? -minLeft : 0;
|
||||
const dTop = minTop < 0 ? -minTop : 0;
|
||||
return tables.map((tbl) => ({
|
||||
...tbl,
|
||||
left: tbl.left + dLeft,
|
||||
top: tbl.top + dTop,
|
||||
}));
|
||||
}
|
||||
return tables;
|
||||
}
|
||||
|
||||
export default function Designer({ value, onChange, conid, database }) {
|
||||
const { tables, references } = value || {};
|
||||
const theme = useTheme();
|
||||
|
||||
const [sourceDragColumn, setSourceDragColumn] = React.useState(null);
|
||||
const [targetDragColumn, setTargetDragColumn] = React.useState(null);
|
||||
const domTablesRef = React.useRef({});
|
||||
const wrapperRef = React.useRef();
|
||||
const [changeToken, setChangeToken] = React.useState(0);
|
||||
|
||||
const handleDrop = (e) => {
|
||||
var data = e.dataTransfer.getData('app_object_drag_data');
|
||||
e.preventDefault();
|
||||
if (!data) return;
|
||||
const rect = e.target.getBoundingClientRect();
|
||||
var json = JSON.parse(data);
|
||||
const { objectTypeField } = json;
|
||||
if (objectTypeField != 'tables' && objectTypeField != 'views') return;
|
||||
json.designerId = uuidv1();
|
||||
json.left = e.clientX - rect.left;
|
||||
json.top = e.clientY - rect.top;
|
||||
|
||||
onChange((current) => {
|
||||
const foreignKeys = _.compact([
|
||||
...(json.foreignKeys || []).map((fk) => {
|
||||
const tables = ((current || {}).tables || []).filter(
|
||||
(tbl) => fk.refTableName == tbl.pureName && fk.refSchemaName == tbl.schemaName
|
||||
);
|
||||
if (tables.length == 1)
|
||||
return {
|
||||
...fk,
|
||||
sourceId: json.designerId,
|
||||
targetId: tables[0].designerId,
|
||||
};
|
||||
return null;
|
||||
}),
|
||||
..._.flatten(
|
||||
((current || {}).tables || []).map((tbl) =>
|
||||
(tbl.foreignKeys || []).map((fk) => {
|
||||
if (fk.refTableName == json.pureName && fk.refSchemaName == json.schemaName) {
|
||||
return {
|
||||
...fk,
|
||||
sourceId: tbl.designerId,
|
||||
targetId: json.designerId,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
})
|
||||
)
|
||||
),
|
||||
]);
|
||||
|
||||
return {
|
||||
...current,
|
||||
tables: [...((current || {}).tables || []), json],
|
||||
references:
|
||||
foreignKeys.length == 1
|
||||
? [
|
||||
...((current || {}).references || []),
|
||||
{
|
||||
designerId: uuidv1(),
|
||||
sourceId: foreignKeys[0].sourceId,
|
||||
targetId: foreignKeys[0].targetId,
|
||||
joinType: 'INNER JOIN',
|
||||
columns: foreignKeys[0].columns.map((col) => ({
|
||||
source: col.columnName,
|
||||
target: col.refColumnName,
|
||||
})),
|
||||
},
|
||||
]
|
||||
: (current || {}).references,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const changeTable = React.useCallback(
|
||||
(table) => {
|
||||
onChange((current) => ({
|
||||
...current,
|
||||
tables: fixPositions((current.tables || []).map((x) => (x.designerId == table.designerId ? table : x))),
|
||||
}));
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const bringToFront = React.useCallback(
|
||||
(table) => {
|
||||
onChange(
|
||||
(current) => ({
|
||||
...current,
|
||||
tables: [...(current.tables || []).filter((x) => x.designerId != table.designerId), table],
|
||||
}),
|
||||
true
|
||||
);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const removeTable = React.useCallback(
|
||||
(table) => {
|
||||
onChange((current) => ({
|
||||
...current,
|
||||
tables: (current.tables || []).filter((x) => x.designerId != table.designerId),
|
||||
references: (current.references || []).filter(
|
||||
(x) => x.sourceId != table.designerId && x.targetId != table.designerId
|
||||
),
|
||||
columns: (current.columns || []).filter((x) => x.designerId != table.designerId),
|
||||
}));
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const changeReference = React.useCallback(
|
||||
(ref) => {
|
||||
onChange((current) => ({
|
||||
...current,
|
||||
references: (current.references || []).map((x) => (x.designerId == ref.designerId ? ref : x)),
|
||||
}));
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const removeReference = React.useCallback(
|
||||
(ref) => {
|
||||
onChange((current) => ({
|
||||
...current,
|
||||
references: (current.references || []).filter((x) => x.designerId != ref.designerId),
|
||||
}));
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const handleCreateReference = (source, target) => {
|
||||
onChange((current) => {
|
||||
const existingReference = (current.references || []).find(
|
||||
(x) =>
|
||||
(x.sourceId == source.designerId && x.targetId == target.designerId) ||
|
||||
(x.sourceId == target.designerId && x.targetId == source.designerId)
|
||||
);
|
||||
|
||||
return {
|
||||
...current,
|
||||
references: existingReference
|
||||
? current.references.map((ref) =>
|
||||
ref == existingReference
|
||||
? {
|
||||
...existingReference,
|
||||
columns: [
|
||||
...existingReference.columns,
|
||||
existingReference.sourceId == source.designerId
|
||||
? {
|
||||
source: source.columnName,
|
||||
target: target.columnName,
|
||||
}
|
||||
: {
|
||||
source: target.columnName,
|
||||
target: source.columnName,
|
||||
},
|
||||
],
|
||||
}
|
||||
: ref
|
||||
)
|
||||
: [
|
||||
...(current.references || []),
|
||||
{
|
||||
designerId: uuidv1(),
|
||||
sourceId: source.designerId,
|
||||
targetId: target.designerId,
|
||||
joinType: isConnectedByReference(current, source, target, null) ? 'CROSS JOIN' : 'INNER JOIN',
|
||||
columns: [
|
||||
{
|
||||
source: source.columnName,
|
||||
target: target.columnName,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddReferenceByColumn = async (designerId, foreignKey) => {
|
||||
const toTable = await getTableInfo({
|
||||
conid,
|
||||
database,
|
||||
pureName: foreignKey.refTableName,
|
||||
schemaName: foreignKey.refSchemaName,
|
||||
});
|
||||
const newTableDesignerId = uuidv1();
|
||||
onChange((current) => {
|
||||
const fromTable = (current.tables || []).find((x) => x.designerId == designerId);
|
||||
if (!fromTable) return;
|
||||
return {
|
||||
...current,
|
||||
tables: [
|
||||
...(current.tables || []),
|
||||
{
|
||||
...toTable,
|
||||
left: fromTable.left + 300,
|
||||
top: fromTable.top + 50,
|
||||
designerId: newTableDesignerId,
|
||||
},
|
||||
],
|
||||
references: [
|
||||
...(current.references || []),
|
||||
{
|
||||
designerId: uuidv1(),
|
||||
sourceId: fromTable.designerId,
|
||||
targetId: newTableDesignerId,
|
||||
joinType: 'INNER JOIN',
|
||||
columns: foreignKey.columns.map((col) => ({
|
||||
source: col.columnName,
|
||||
target: col.refColumnName,
|
||||
})),
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectColumn = React.useCallback(
|
||||
(column) => {
|
||||
onChange(
|
||||
(current) => ({
|
||||
...current,
|
||||
columns: (current.columns || []).find(
|
||||
(x) => x.designerId == column.designerId && x.columnName == column.columnName
|
||||
)
|
||||
? current.columns
|
||||
: [...cleanupDesignColumns(current.columns), _.pick(column, ['designerId', 'columnName'])],
|
||||
}),
|
||||
true
|
||||
);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const handleChangeColumn = React.useCallback(
|
||||
(column, changeFunc) => {
|
||||
onChange((current) => {
|
||||
const currentColumns = (current || {}).columns || [];
|
||||
const existing = currentColumns.find(
|
||||
(x) => x.designerId == column.designerId && x.columnName == column.columnName
|
||||
);
|
||||
if (existing) {
|
||||
return {
|
||||
...current,
|
||||
columns: currentColumns.map((x) => (x == existing ? changeFunc(existing) : x)),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...current,
|
||||
columns: [
|
||||
...cleanupDesignColumns(currentColumns),
|
||||
changeFunc(_.pick(column, ['designerId', 'columnName'])),
|
||||
],
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
// React.useEffect(() => {
|
||||
// setTimeout(() => setChangeToken((x) => x + 1), 100);
|
||||
// }, [value]);
|
||||
|
||||
return (
|
||||
<Wrapper theme={theme}>
|
||||
{(tables || []).length == 0 && <EmptyInfo>Drag & drop tables or views from left panel here</EmptyInfo>}
|
||||
<Canvas onDragOver={(e) => e.preventDefault()} onDrop={handleDrop} ref={wrapperRef}>
|
||||
{(references || []).map((ref) => (
|
||||
<DesignerReference
|
||||
key={ref.designerId}
|
||||
changeToken={changeToken}
|
||||
domTablesRef={domTablesRef}
|
||||
reference={ref}
|
||||
onChangeReference={changeReference}
|
||||
onRemoveReference={removeReference}
|
||||
designer={value}
|
||||
/>
|
||||
))}
|
||||
{(tables || []).map((table) => (
|
||||
<DesignerTable
|
||||
key={table.designerId}
|
||||
sourceDragColumn={sourceDragColumn}
|
||||
setSourceDragColumn={setSourceDragColumn}
|
||||
targetDragColumn={targetDragColumn}
|
||||
setTargetDragColumn={setTargetDragColumn}
|
||||
onCreateReference={handleCreateReference}
|
||||
onSelectColumn={handleSelectColumn}
|
||||
onChangeColumn={handleChangeColumn}
|
||||
onAddReferenceByColumn={handleAddReferenceByColumn}
|
||||
table={table}
|
||||
onChangeTable={changeTable}
|
||||
onBringToFront={bringToFront}
|
||||
onRemoveTable={removeTable}
|
||||
setChangeToken={setChangeToken}
|
||||
wrapperRef={wrapperRef}
|
||||
onChangeDomTable={(table) => {
|
||||
domTablesRef.current[table.designerId] = table;
|
||||
}}
|
||||
designer={value}
|
||||
/>
|
||||
))}
|
||||
</Canvas>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import _ from 'lodash';
|
||||
import { dumpSqlSelect, Select, JoinType, Condition, Relation, mergeConditions, Source } from 'dbgate-sqltree';
|
||||
import { EngineDriver } from 'dbgate-types';
|
||||
import { DesignerInfo, DesignerTableInfo, DesignerReferenceInfo, DesignerJoinType } from './types';
|
||||
import { findPrimaryTable, findConnectingReference, referenceIsJoin, referenceIsExists } from './designerTools';
|
||||
|
||||
export class DesignerComponent {
|
||||
subComponents: DesignerComponent[] = [];
|
||||
parentComponent: DesignerComponent;
|
||||
parentReference: DesignerReferenceInfo;
|
||||
|
||||
tables: DesignerTableInfo[] = [];
|
||||
nonPrimaryReferences: DesignerReferenceInfo[] = [];
|
||||
|
||||
get primaryTable() {
|
||||
return this.tables[0];
|
||||
}
|
||||
get nonPrimaryTables() {
|
||||
return this.tables.slice(1);
|
||||
}
|
||||
get nonPrimaryTablesAndReferences() {
|
||||
return _.zip(this.nonPrimaryTables, this.nonPrimaryReferences);
|
||||
}
|
||||
get myAndParentTables() {
|
||||
return [...this.parentTables, ...this.tables];
|
||||
}
|
||||
get parentTables() {
|
||||
return this.parentComponent ? this.parentComponent.myAndParentTables : [];
|
||||
}
|
||||
get thisAndSubComponentsTables() {
|
||||
return [...this.tables, ..._.flatten(this.subComponents.map((x) => x.thisAndSubComponentsTables))];
|
||||
}
|
||||
}
|
||||
|
||||
export class DesignerComponentCreator {
|
||||
toAdd: DesignerTableInfo[];
|
||||
components: DesignerComponent[] = [];
|
||||
|
||||
constructor(public designer: DesignerInfo) {
|
||||
this.toAdd = [...designer.tables];
|
||||
while (this.toAdd.length > 0) {
|
||||
const component = this.parseComponent(null);
|
||||
this.components.push(component);
|
||||
}
|
||||
}
|
||||
|
||||
parseComponent(root) {
|
||||
if (root == null) {
|
||||
root = findPrimaryTable(this.toAdd);
|
||||
}
|
||||
if (!root) return null;
|
||||
_.remove(this.toAdd, (x) => x == root);
|
||||
const res = new DesignerComponent();
|
||||
res.tables.push(root);
|
||||
|
||||
for (;;) {
|
||||
let found = false;
|
||||
for (const test of this.toAdd) {
|
||||
const ref = findConnectingReference(this.designer, res.tables, [test], referenceIsJoin);
|
||||
if (ref) {
|
||||
res.tables.push(test);
|
||||
res.nonPrimaryReferences.push(ref);
|
||||
_.remove(this.toAdd, (x) => x == test);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) break;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
let found = false;
|
||||
for (const test of this.toAdd) {
|
||||
const ref = findConnectingReference(this.designer, res.tables, [test], referenceIsExists);
|
||||
if (ref) {
|
||||
const subComponent = this.parseComponent(test);
|
||||
res.subComponents.push(subComponent);
|
||||
subComponent.parentComponent = res;
|
||||
subComponent.parentReference = ref;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
dumpSqlSelect,
|
||||
Select,
|
||||
JoinType,
|
||||
Condition,
|
||||
Relation,
|
||||
mergeConditions,
|
||||
Source,
|
||||
ResultField,
|
||||
} from 'dbgate-sqltree';
|
||||
import { EngineDriver } from 'dbgate-types';
|
||||
import { DesignerInfo, DesignerTableInfo, DesignerReferenceInfo, DesignerJoinType } from './types';
|
||||
import { DesignerComponent } from './DesignerComponentCreator';
|
||||
import {
|
||||
getReferenceConditions,
|
||||
referenceIsCrossJoin,
|
||||
referenceIsConnecting,
|
||||
mergeSelectsFromDesigner,
|
||||
findQuerySource,
|
||||
findDesignerFilterType,
|
||||
} from './designerTools';
|
||||
import { parseFilter } from 'dbgate-filterparser';
|
||||
|
||||
export class DesignerQueryDumper {
|
||||
constructor(public designer: DesignerInfo, public components: DesignerComponent[]) {}
|
||||
|
||||
get topLevelTables(): DesignerTableInfo[] {
|
||||
return _.flatten(this.components.map((x) => x.tables));
|
||||
}
|
||||
|
||||
dumpComponent(component: DesignerComponent) {
|
||||
const select: Select = {
|
||||
commandType: 'select',
|
||||
from: {
|
||||
name: component.primaryTable,
|
||||
alias: component.primaryTable.alias,
|
||||
relations: [],
|
||||
},
|
||||
};
|
||||
|
||||
for (const [table, ref] of component.nonPrimaryTablesAndReferences) {
|
||||
select.from.relations.push({
|
||||
name: table,
|
||||
alias: table.alias,
|
||||
joinType: ref.joinType as JoinType,
|
||||
conditions: getReferenceConditions(ref, this.designer),
|
||||
});
|
||||
}
|
||||
|
||||
for (const subComponent of component.subComponents) {
|
||||
const subQuery = this.dumpComponent(subComponent);
|
||||
subQuery.selectAll = true;
|
||||
select.where = mergeConditions(select.where, {
|
||||
conditionType: subComponent.parentReference.joinType == 'WHERE NOT EXISTS' ? 'notExists' : 'exists',
|
||||
subQuery,
|
||||
});
|
||||
}
|
||||
|
||||
if (component.parentReference) {
|
||||
select.where = mergeConditions(select.where, {
|
||||
conditionType: 'and',
|
||||
conditions: getReferenceConditions(component.parentReference, this.designer),
|
||||
});
|
||||
|
||||
// cross join conditions in subcomponents
|
||||
for (const ref of this.designer.references || []) {
|
||||
if (referenceIsCrossJoin(ref) && referenceIsConnecting(ref, component.tables, component.myAndParentTables)) {
|
||||
select.where = mergeConditions(select.where, {
|
||||
conditionType: 'and',
|
||||
conditions: getReferenceConditions(ref, this.designer),
|
||||
});
|
||||
}
|
||||
}
|
||||
this.addConditions(select, component.tables);
|
||||
}
|
||||
|
||||
return select;
|
||||
}
|
||||
|
||||
addConditions(select: Select, tables: DesignerTableInfo[]) {
|
||||
for (const column of this.designer.columns || []) {
|
||||
if (!column.filter) continue;
|
||||
const table = (this.designer.tables || []).find((x) => x.designerId == column.designerId);
|
||||
if (!table) continue;
|
||||
if (!tables.find((x) => x.designerId == table.designerId)) continue;
|
||||
|
||||
const condition = parseFilter(column.filter, findDesignerFilterType(column, this.designer));
|
||||
if (condition) {
|
||||
select.where = mergeConditions(
|
||||
select.where,
|
||||
_.cloneDeepWith(condition, (expr) => {
|
||||
if (expr.exprType == 'placeholder')
|
||||
return {
|
||||
exprType: 'column',
|
||||
columnName: column.columnName,
|
||||
source: findQuerySource(this.designer, column.designerId),
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addGroupConditions(select: Select, tables: DesignerTableInfo[], selectIsGrouped: boolean) {
|
||||
for (const column of this.designer.columns || []) {
|
||||
if (!column.groupFilter) continue;
|
||||
const table = (this.designer.tables || []).find((x) => x.designerId == column.designerId);
|
||||
if (!table) continue;
|
||||
if (!tables.find((x) => x.designerId == table.designerId)) continue;
|
||||
|
||||
const condition = parseFilter(column.groupFilter, findDesignerFilterType(column, this.designer));
|
||||
if (condition) {
|
||||
select.having = mergeConditions(
|
||||
select.having,
|
||||
_.cloneDeepWith(condition, (expr) => {
|
||||
if (expr.exprType == 'placeholder') {
|
||||
return this.getColumnOutputExpression(column, selectIsGrouped);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getColumnOutputExpression(col, selectIsGrouped): ResultField {
|
||||
const source = findQuerySource(this.designer, col.designerId);
|
||||
const { columnName } = col;
|
||||
let { alias } = col;
|
||||
if (selectIsGrouped && !col.isGrouped) {
|
||||
// use aggregate
|
||||
const aggregate = col.aggregate == null || col.aggregate == '---' ? 'MAX' : col.aggregate;
|
||||
if (!alias) alias = `${aggregate}(${columnName})`;
|
||||
|
||||
return {
|
||||
exprType: 'call',
|
||||
func: aggregate == 'COUNT DISTINCT' ? 'COUNT' : aggregate,
|
||||
argsPrefix: aggregate == 'COUNT DISTINCT' ? 'DISTINCT' : null,
|
||||
alias,
|
||||
args: [
|
||||
{
|
||||
exprType: 'column',
|
||||
columnName,
|
||||
source,
|
||||
},
|
||||
],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
exprType: 'column',
|
||||
columnName,
|
||||
alias,
|
||||
source,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
run() {
|
||||
let res: Select = null;
|
||||
for (const component of this.components) {
|
||||
const select = this.dumpComponent(component);
|
||||
if (res == null) res = select;
|
||||
else res = mergeSelectsFromDesigner(res, select);
|
||||
}
|
||||
|
||||
// top level cross join conditions
|
||||
const topLevelTables = this.topLevelTables;
|
||||
for (const ref of this.designer.references || []) {
|
||||
if (referenceIsCrossJoin(ref) && referenceIsConnecting(ref, topLevelTables, topLevelTables)) {
|
||||
res.where = mergeConditions(res.where, {
|
||||
conditionType: 'and',
|
||||
conditions: getReferenceConditions(ref, this.designer),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const topLevelColumns = (this.designer.columns || []).filter((col) =>
|
||||
topLevelTables.find((tbl) => tbl.designerId == col.designerId)
|
||||
);
|
||||
const selectIsGrouped = !!topLevelColumns.find((x) => x.isGrouped || (x.aggregate && x.aggregate != '---'));
|
||||
const outputColumns = topLevelColumns.filter((x) => x.isOutput);
|
||||
if (outputColumns.length == 0) {
|
||||
res.selectAll = true;
|
||||
} else {
|
||||
res.columns = outputColumns.map((col) => this.getColumnOutputExpression(col, selectIsGrouped));
|
||||
}
|
||||
|
||||
const groupedColumns = topLevelColumns.filter((x) => x.isGrouped);
|
||||
if (groupedColumns.length > 0) {
|
||||
res.groupBy = groupedColumns.map((col) => ({
|
||||
exprType: 'column',
|
||||
columnName: col.columnName,
|
||||
source: findQuerySource(this.designer, col.designerId),
|
||||
}));
|
||||
}
|
||||
|
||||
const orderColumns = _.sortBy(
|
||||
topLevelColumns.filter((x) => x.sortOrder),
|
||||
(x) => Math.abs(x.sortOrder)
|
||||
);
|
||||
if (orderColumns.length > 0) {
|
||||
res.orderBy = orderColumns.map((col) => ({
|
||||
exprType: 'column',
|
||||
direction: col.sortOrder < 0 ? 'DESC' : 'ASC',
|
||||
columnName: col.columnName,
|
||||
source: findQuerySource(this.designer, col.designerId),
|
||||
}));
|
||||
}
|
||||
|
||||
this.addConditions(res, topLevelTables);
|
||||
this.addGroupConditions(res, topLevelTables, selectIsGrouped);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import DomTableRef from './DomTableRef';
|
||||
import _ from 'lodash';
|
||||
import useTheme from '../theme/useTheme';
|
||||
import { useShowMenu } from '../modals/showMenu';
|
||||
import { DropDownMenuDivider, DropDownMenuItem } from '../modals/DropDownMenu';
|
||||
import { isConnectedByReference } from './designerTools';
|
||||
|
||||
const StyledSvg = styled.svg`
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const ReferenceWrapper = styled.div`
|
||||
position: absolute;
|
||||
border: 1px solid ${(props) => props.theme.designer_line};
|
||||
background-color: ${(props) => props.theme.designer_background};
|
||||
z-index: 900;
|
||||
border-radius: 10px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
`;
|
||||
|
||||
const ReferenceText = styled.span`
|
||||
position: relative;
|
||||
float: left;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 900;
|
||||
white-space: nowrap;
|
||||
background-color: ${(props) => props.theme.designer_background};
|
||||
`;
|
||||
|
||||
function ReferenceContextMenu({ remove, setJoinType, isConnected }) {
|
||||
return (
|
||||
<>
|
||||
<DropDownMenuItem onClick={remove}>Remove</DropDownMenuItem>
|
||||
{!isConnected && (
|
||||
<>
|
||||
<DropDownMenuDivider />
|
||||
<DropDownMenuItem onClick={() => setJoinType('INNER JOIN')}>Set INNER JOIN</DropDownMenuItem>
|
||||
<DropDownMenuItem onClick={() => setJoinType('LEFT JOIN')}>Set LEFT JOIN</DropDownMenuItem>
|
||||
<DropDownMenuItem onClick={() => setJoinType('RIGHT JOIN')}>Set RIGHT JOIN</DropDownMenuItem>
|
||||
<DropDownMenuItem onClick={() => setJoinType('FULL OUTER JOIN')}>Set FULL OUTER JOIN</DropDownMenuItem>
|
||||
<DropDownMenuItem onClick={() => setJoinType('CROSS JOIN')}>Set CROSS JOIN</DropDownMenuItem>
|
||||
<DropDownMenuItem onClick={() => setJoinType('WHERE EXISTS')}>Set WHERE EXISTS</DropDownMenuItem>
|
||||
<DropDownMenuItem onClick={() => setJoinType('WHERE NOT EXISTS')}>Set WHERE NOT EXISTS</DropDownMenuItem>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DesignerReference({
|
||||
domTablesRef,
|
||||
reference,
|
||||
changeToken,
|
||||
onRemoveReference,
|
||||
onChangeReference,
|
||||
designer,
|
||||
}) {
|
||||
const { designerId, sourceId, targetId, columns, joinType } = reference;
|
||||
const theme = useTheme();
|
||||
const showMenu = useShowMenu();
|
||||
const domTables = domTablesRef.current;
|
||||
/** @type {DomTableRef} */
|
||||
const sourceTable = domTables[sourceId];
|
||||
/** @type {DomTableRef} */
|
||||
const targetTable = domTables[targetId];
|
||||
if (!sourceTable || !targetTable) return null;
|
||||
const sourceRect = sourceTable.getRect();
|
||||
const targetRect = targetTable.getRect();
|
||||
if (!sourceRect || !targetRect) return null;
|
||||
|
||||
const buswi = 10;
|
||||
const extwi = 25;
|
||||
|
||||
const possibilities = [];
|
||||
possibilities.push({ xsrc: sourceRect.left - buswi, dirsrc: -1, xdst: targetRect.left - buswi, dirdst: -1 });
|
||||
possibilities.push({ xsrc: sourceRect.left - buswi, dirsrc: -1, xdst: targetRect.right + buswi, dirdst: 1 });
|
||||
possibilities.push({ xsrc: sourceRect.right + buswi, dirsrc: 1, xdst: targetRect.left - buswi, dirdst: -1 });
|
||||
possibilities.push({ xsrc: sourceRect.right + buswi, dirsrc: 1, xdst: targetRect.right + buswi, dirdst: 1 });
|
||||
|
||||
let minpos = _.minBy(possibilities, (p) => Math.abs(p.xsrc - p.xdst));
|
||||
|
||||
let srcY = _.mean(columns.map((x) => sourceTable.getColumnY(x.source)));
|
||||
let dstY = _.mean(columns.map((x) => targetTable.getColumnY(x.target)));
|
||||
|
||||
if (columns.length == 0) {
|
||||
srcY = sourceTable.getColumnY('');
|
||||
dstY = targetTable.getColumnY('');
|
||||
}
|
||||
|
||||
const src = { x: minpos.xsrc, y: srcY };
|
||||
const dst = { x: minpos.xdst, y: dstY };
|
||||
|
||||
const lineStyle = { fill: 'none', stroke: theme.designer_line, strokeWidth: 2 };
|
||||
|
||||
const handleContextMenu = (event) => {
|
||||
event.preventDefault();
|
||||
showMenu(
|
||||
event.pageX,
|
||||
event.pageY,
|
||||
<ReferenceContextMenu
|
||||
remove={() => onRemoveReference({ designerId })}
|
||||
isConnected={isConnectedByReference(designer, { designerId: sourceId }, { designerId: targetId }, reference)}
|
||||
setJoinType={(joinType) => {
|
||||
onChangeReference({
|
||||
...reference,
|
||||
joinType,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledSvg>
|
||||
<polyline
|
||||
points={`
|
||||
${src.x},${src.y}
|
||||
${src.x + extwi * minpos.dirsrc},${src.y}
|
||||
${dst.x + extwi * minpos.dirdst},${dst.y}
|
||||
${dst.x},${dst.y}
|
||||
`}
|
||||
style={lineStyle}
|
||||
/>
|
||||
{columns.map((col, colIndex) => {
|
||||
let y1 = sourceTable.getColumnY(col.source);
|
||||
let y2 = targetTable.getColumnY(col.target);
|
||||
return (
|
||||
<React.Fragment key={colIndex}>
|
||||
<polyline
|
||||
points={`
|
||||
${src.x},${src.y}
|
||||
${src.x},${y1}
|
||||
${src.x - buswi * minpos.dirsrc},${y1}
|
||||
`}
|
||||
style={lineStyle}
|
||||
/>
|
||||
<polyline
|
||||
points={`
|
||||
${dst.x},${dst.y}
|
||||
${dst.x},${y2}
|
||||
${dst.x - buswi * minpos.dirdst},${y2}
|
||||
`}
|
||||
style={lineStyle}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</StyledSvg>
|
||||
<ReferenceWrapper
|
||||
theme={theme}
|
||||
style={{
|
||||
left: (src.x + extwi * minpos.dirsrc + dst.x + extwi * minpos.dirdst) / 2 - 16,
|
||||
top: (src.y + dst.y) / 2 - 16,
|
||||
}}
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
<ReferenceText theme={theme}>
|
||||
{_.snakeCase(joinType || 'CROSS JOIN')
|
||||
.replace('_', '\xa0')
|
||||
.replace('_', '\xa0')}
|
||||
</ReferenceText>
|
||||
</ReferenceWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { findForeignKeyForColumn } from 'dbgate-tools';
|
||||
import ColumnLabel from '../datagrid/ColumnLabel';
|
||||
import { FontIcon } from '../icons';
|
||||
import useTheme from '../theme/useTheme';
|
||||
import DomTableRef from './DomTableRef';
|
||||
import _ from 'lodash';
|
||||
import { CheckboxField } from '../utility/inputs';
|
||||
import { useShowMenu } from '../modals/showMenu';
|
||||
import { DropDownMenuDivider, DropDownMenuItem } from '../modals/DropDownMenu';
|
||||
import useShowModal from '../modals/showModal';
|
||||
import InputTextModal from '../modals/InputTextModal';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
position: absolute;
|
||||
// background-color: white;
|
||||
background-color: ${(props) => props.theme.designtable_background};
|
||||
border: 1px solid ${(props) => props.theme.border};
|
||||
`;
|
||||
|
||||
const Header = styled.div`
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 2px;
|
||||
background: ${(props) =>
|
||||
// @ts-ignore
|
||||
props.objectTypeField == 'views'
|
||||
? props.theme.designtable_background_magenta[2]
|
||||
: props.theme.designtable_background_blue[2]};
|
||||
border-bottom: 1px solid ${(props) => props.theme.border};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const ColumnsWrapper = styled.div`
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
width: calc(100% - 10px);
|
||||
padding: 5px;
|
||||
`;
|
||||
|
||||
const HeaderLabel = styled.div``;
|
||||
|
||||
const CloseWrapper = styled.div`
|
||||
${(props) =>
|
||||
`
|
||||
background-color: ${props.theme.toolbar_background} ;
|
||||
|
||||
&:hover {
|
||||
background-color: ${props.theme.toolbar_background2} ;
|
||||
}
|
||||
|
||||
&:active:hover {
|
||||
background-color: ${props.theme.toolbar_background3};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
// &:hover {
|
||||
// background-color: ${(props) => props.theme.designtable_background_gold[1]};
|
||||
// }
|
||||
|
||||
const ColumnLine = styled.div`
|
||||
${(props) =>
|
||||
// @ts-ignore
|
||||
!props.isDragSource &&
|
||||
// @ts-ignore
|
||||
!props.isDragTarget &&
|
||||
`
|
||||
&:hover {
|
||||
background-color: ${props.theme.designtable_background_gold[1]};
|
||||
}
|
||||
`}
|
||||
|
||||
${(props) =>
|
||||
// @ts-ignore
|
||||
props.isDragSource &&
|
||||
`
|
||||
background-color: ${props.theme.designtable_background_cyan[2]};
|
||||
`}
|
||||
|
||||
${(props) =>
|
||||
// @ts-ignore
|
||||
props.isDragTarget &&
|
||||
`
|
||||
background-color: ${props.theme.designtable_background_cyan[2]};
|
||||
`}
|
||||
`;
|
||||
|
||||
function TableContextMenu({ remove, setTableAlias, removeTableAlias }) {
|
||||
return (
|
||||
<>
|
||||
<DropDownMenuItem onClick={remove}>Remove</DropDownMenuItem>
|
||||
<DropDownMenuDivider />
|
||||
<DropDownMenuItem onClick={setTableAlias}>Set table alias</DropDownMenuItem>
|
||||
{!!removeTableAlias && <DropDownMenuItem onClick={removeTableAlias}>Remove table alias</DropDownMenuItem>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ColumnContextMenu({ setSortOrder, addReference }) {
|
||||
return (
|
||||
<>
|
||||
<DropDownMenuItem onClick={() => setSortOrder(1)}>Sort ascending</DropDownMenuItem>
|
||||
<DropDownMenuItem onClick={() => setSortOrder(-1)}>Sort descending</DropDownMenuItem>
|
||||
<DropDownMenuItem onClick={() => setSortOrder(0)}>Unsort</DropDownMenuItem>
|
||||
{!!addReference && <DropDownMenuItem onClick={addReference}>Add reference</DropDownMenuItem>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ColumnDesignerIcons({ column, designerId, designer }) {
|
||||
const designerColumn = (designer.columns || []).find(
|
||||
(x) => x.designerId == designerId && x.columnName == column.columnName
|
||||
);
|
||||
if (!designerColumn) return null;
|
||||
return (
|
||||
<>
|
||||
{!!designerColumn.filter && <FontIcon icon="img filter" />}
|
||||
{designerColumn.sortOrder > 0 && <FontIcon icon="img sort-asc" />}
|
||||
{designerColumn.sortOrder < 0 && <FontIcon icon="img sort-desc" />}
|
||||
{!!designerColumn.isGrouped && <FontIcon icon="img group" />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DesignerTable({
|
||||
table,
|
||||
onChangeTable,
|
||||
onBringToFront,
|
||||
onRemoveTable,
|
||||
onCreateReference,
|
||||
onAddReferenceByColumn,
|
||||
onSelectColumn,
|
||||
onChangeColumn,
|
||||
sourceDragColumn,
|
||||
setSourceDragColumn,
|
||||
targetDragColumn,
|
||||
setTargetDragColumn,
|
||||
onChangeDomTable,
|
||||
wrapperRef,
|
||||
setChangeToken,
|
||||
designer,
|
||||
}) {
|
||||
const { pureName, columns, left, top, designerId, alias, objectTypeField } = table;
|
||||
const [movingPosition, setMovingPosition] = React.useState(null);
|
||||
const movingPositionRef = React.useRef(null);
|
||||
const theme = useTheme();
|
||||
const domObjectsRef = React.useRef({});
|
||||
const showMenu = useShowMenu();
|
||||
const showModal = useShowModal();
|
||||
|
||||
const moveStartXRef = React.useRef(null);
|
||||
const moveStartYRef = React.useRef(null);
|
||||
|
||||
const handleMove = React.useCallback((e) => {
|
||||
let diffX = e.clientX - moveStartXRef.current;
|
||||
let diffY = e.clientY - moveStartYRef.current;
|
||||
moveStartXRef.current = e.clientX;
|
||||
moveStartYRef.current = e.clientY;
|
||||
|
||||
movingPositionRef.current = {
|
||||
left: (movingPositionRef.current.left || 0) + diffX,
|
||||
top: (movingPositionRef.current.top || 0) + diffY,
|
||||
};
|
||||
setMovingPosition(movingPositionRef.current);
|
||||
// setChangeToken((x) => x + 1);
|
||||
changeTokenDebounced.current();
|
||||
// onChangeTable(
|
||||
// {
|
||||
// ...props,
|
||||
// left: (left || 0) + diffX,
|
||||
// top: (top || 0) + diffY,
|
||||
// },
|
||||
// index
|
||||
// );
|
||||
}, []);
|
||||
|
||||
const changeTokenDebounced = React.useRef(
|
||||
// @ts-ignore
|
||||
_.debounce(() => setChangeToken((x) => x + 1), 100)
|
||||
);
|
||||
|
||||
const handleMoveEnd = React.useCallback(
|
||||
(e) => {
|
||||
if (movingPositionRef.current) {
|
||||
onChangeTable({
|
||||
...table,
|
||||
left: movingPositionRef.current.left,
|
||||
top: movingPositionRef.current.top,
|
||||
});
|
||||
}
|
||||
|
||||
movingPositionRef.current = null;
|
||||
setMovingPosition(null);
|
||||
changeTokenDebounced.current();
|
||||
// setChangeToken((x) => x + 1);
|
||||
|
||||
// this.props.model.fixPositions();
|
||||
|
||||
// this.props.designer.changedModel(true);
|
||||
},
|
||||
[onChangeTable, table]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (movingPosition) {
|
||||
document.addEventListener('mousemove', handleMove, true);
|
||||
document.addEventListener('mouseup', handleMoveEnd, true);
|
||||
return () => {
|
||||
document.removeEventListener('mousemove', handleMove, true);
|
||||
document.removeEventListener('mouseup', handleMoveEnd, true);
|
||||
};
|
||||
}
|
||||
}, [movingPosition == null, handleMove, handleMoveEnd]);
|
||||
|
||||
const headerMouseDown = React.useCallback(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
moveStartXRef.current = e.clientX;
|
||||
moveStartYRef.current = e.clientY;
|
||||
movingPositionRef.current = { left, top };
|
||||
setMovingPosition(movingPositionRef.current);
|
||||
// setIsMoving(true);
|
||||
},
|
||||
[handleMove, handleMoveEnd]
|
||||
);
|
||||
|
||||
const dispatchDomColumn = (columnName, dom) => {
|
||||
domObjectsRef.current[columnName] = dom;
|
||||
onChangeDomTable(new DomTableRef(table, domObjectsRef.current, wrapperRef.current));
|
||||
changeTokenDebounced.current();
|
||||
};
|
||||
|
||||
const handleSetTableAlias = () => {
|
||||
showModal((modalState) => (
|
||||
<InputTextModal
|
||||
modalState={modalState}
|
||||
value={alias || ''}
|
||||
label="New alias"
|
||||
header="Set table alias"
|
||||
onConfirm={(newAlias) => {
|
||||
onChangeTable({
|
||||
...table,
|
||||
alias: newAlias,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
const handleHeaderContextMenu = (event) => {
|
||||
event.preventDefault();
|
||||
showMenu(
|
||||
event.pageX,
|
||||
event.pageY,
|
||||
<TableContextMenu
|
||||
remove={() => onRemoveTable({ designerId })}
|
||||
setTableAlias={handleSetTableAlias}
|
||||
removeTableAlias={
|
||||
alias
|
||||
? () =>
|
||||
onChangeTable({
|
||||
...table,
|
||||
alias: null,
|
||||
})
|
||||
: null
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const handleColumnContextMenu = (column) => (event) => {
|
||||
event.preventDefault();
|
||||
const foreignKey = findForeignKeyForColumn(table, column);
|
||||
showMenu(
|
||||
event.pageX,
|
||||
event.pageY,
|
||||
<ColumnContextMenu
|
||||
setSortOrder={(sortOrder) => {
|
||||
onChangeColumn(
|
||||
{
|
||||
...column,
|
||||
designerId,
|
||||
},
|
||||
(col) => ({ ...col, sortOrder })
|
||||
);
|
||||
}}
|
||||
addReference={
|
||||
foreignKey
|
||||
? () => {
|
||||
onAddReferenceByColumn(designerId, foreignKey);
|
||||
}
|
||||
: null
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Wrapper
|
||||
theme={theme}
|
||||
style={{
|
||||
left: movingPosition ? movingPosition.left : left,
|
||||
top: movingPosition ? movingPosition.top : top,
|
||||
}}
|
||||
onMouseDown={() => onBringToFront(table)}
|
||||
ref={(dom) => dispatchDomColumn('', dom)}
|
||||
>
|
||||
<Header
|
||||
onMouseDown={headerMouseDown}
|
||||
theme={theme}
|
||||
onContextMenu={handleHeaderContextMenu}
|
||||
// @ts-ignore
|
||||
objectTypeField={objectTypeField}
|
||||
>
|
||||
<HeaderLabel>{alias || pureName}</HeaderLabel>
|
||||
<CloseWrapper onClick={() => onRemoveTable(table)} theme={theme}>
|
||||
<FontIcon icon="icon close" />
|
||||
</CloseWrapper>
|
||||
</Header>
|
||||
<ColumnsWrapper>
|
||||
{(columns || []).map((column) => (
|
||||
<ColumnLine
|
||||
onContextMenu={handleColumnContextMenu(column)}
|
||||
key={column.columnName}
|
||||
theme={theme}
|
||||
draggable
|
||||
ref={(dom) => dispatchDomColumn(column.columnName, dom)}
|
||||
// @ts-ignore
|
||||
isDragSource={
|
||||
sourceDragColumn &&
|
||||
sourceDragColumn.designerId == designerId &&
|
||||
sourceDragColumn.columnName == column.columnName
|
||||
}
|
||||
// @ts-ignore
|
||||
isDragTarget={
|
||||
targetDragColumn &&
|
||||
targetDragColumn.designerId == designerId &&
|
||||
targetDragColumn.columnName == column.columnName
|
||||
}
|
||||
onDragStart={(e) => {
|
||||
const dragData = {
|
||||
...column,
|
||||
designerId,
|
||||
};
|
||||
setSourceDragColumn(dragData);
|
||||
e.dataTransfer.setData('designer_column_drag_data', JSON.stringify(dragData));
|
||||
}}
|
||||
onDragEnd={(e) => {
|
||||
setTargetDragColumn(null);
|
||||
setSourceDragColumn(null);
|
||||
}}
|
||||
onDragOver={(e) => {
|
||||
if (sourceDragColumn) {
|
||||
e.preventDefault();
|
||||
setTargetDragColumn({
|
||||
...column,
|
||||
designerId,
|
||||
});
|
||||
}
|
||||
}}
|
||||
onDrop={(e) => {
|
||||
var data = e.dataTransfer.getData('designer_column_drag_data');
|
||||
e.preventDefault();
|
||||
if (!data) return;
|
||||
onCreateReference(sourceDragColumn, targetDragColumn);
|
||||
setTargetDragColumn(null);
|
||||
setSourceDragColumn(null);
|
||||
}}
|
||||
onMouseDown={(e) =>
|
||||
onSelectColumn({
|
||||
...column,
|
||||
designerId,
|
||||
})
|
||||
}
|
||||
>
|
||||
<CheckboxField
|
||||
checked={
|
||||
!!(designer.columns || []).find(
|
||||
(x) => x.designerId == designerId && x.columnName == column.columnName && x.isOutput
|
||||
)
|
||||
}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
onChangeColumn(
|
||||
{
|
||||
...column,
|
||||
designerId,
|
||||
},
|
||||
(col) => ({ ...col, isOutput: true })
|
||||
);
|
||||
} else {
|
||||
onChangeColumn(
|
||||
{
|
||||
...column,
|
||||
designerId,
|
||||
},
|
||||
(col) => ({ ...col, isOutput: false })
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ColumnLabel {...column} foreignKey={findForeignKeyForColumn(table, column)} forceIcon />
|
||||
<ColumnDesignerIcons column={column} designerId={designerId} designer={designer} />
|
||||
</ColumnLine>
|
||||
))}
|
||||
</ColumnsWrapper>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { DesignerTableInfo } from "./types";
|
||||
|
||||
export default class DomTableRef {
|
||||
domTable: Element;
|
||||
domWrapper: Element;
|
||||
table: DesignerTableInfo;
|
||||
designerId: string;
|
||||
domRefs: { [column: string]: Element };
|
||||
|
||||
constructor(table: DesignerTableInfo, domRefs, domWrapper: Element) {
|
||||
this.domTable = domRefs[''];
|
||||
this.domWrapper = domWrapper;
|
||||
this.table = table;
|
||||
this.designerId = table.designerId;
|
||||
this.domRefs = domRefs;
|
||||
}
|
||||
|
||||
getRect() {
|
||||
if (!this.domWrapper) return null;
|
||||
if (!this.domTable) return null;
|
||||
|
||||
const wrap = this.domWrapper.getBoundingClientRect();
|
||||
const rect = this.domTable.getBoundingClientRect();
|
||||
return {
|
||||
left: rect.left - wrap.left,
|
||||
top: rect.top - wrap.top,
|
||||
right: rect.right - wrap.left,
|
||||
bottom: rect.bottom - wrap.top,
|
||||
};
|
||||
}
|
||||
|
||||
getColumnY(columnName: string) {
|
||||
let col = this.domRefs[columnName];
|
||||
if (!col) return null;
|
||||
const rect = col.getBoundingClientRect();
|
||||
const wrap = this.domWrapper.getBoundingClientRect();
|
||||
return (rect.top + rect.bottom) / 2 - wrap.top;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import React from 'react';
|
||||
import DataFilterControl from '../datagrid/DataFilterControl';
|
||||
import { CheckboxField, SelectField, TextField } from '../utility/inputs';
|
||||
import TableControl, { TableColumn } from '../utility/TableControl';
|
||||
import InlineButton from '../widgets/InlineButton';
|
||||
import { findDesignerFilterType } from './designerTools';
|
||||
|
||||
function getTableDisplayName(column, tables) {
|
||||
const table = (tables || []).find((x) => x.designerId == column.designerId);
|
||||
if (table) return table.alias || table.pureName;
|
||||
return '';
|
||||
}
|
||||
|
||||
export default function QueryDesignColumns({ value, onChange }) {
|
||||
const { columns, tables } = value || {};
|
||||
|
||||
const changeColumn = React.useCallback(
|
||||
(col) => {
|
||||
onChange((current) => ({
|
||||
...current,
|
||||
columns: (current.columns || []).map((x) =>
|
||||
x.designerId == col.designerId && x.columnName == col.columnName ? col : x
|
||||
),
|
||||
}));
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const removeColumn = React.useCallback(
|
||||
(col) => {
|
||||
onChange((current) => ({
|
||||
...current,
|
||||
columns: (current.columns || []).filter(
|
||||
(x) => x.designerId != col.designerId || x.columnName != col.columnName
|
||||
),
|
||||
}));
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const hasGroupedColumn = !!(columns || []).find((x) => x.isGrouped);
|
||||
|
||||
return (
|
||||
<TableControl rows={columns || []}>
|
||||
<TableColumn fieldName="columnName" header="Column/Expression" />
|
||||
<TableColumn fieldName="tableDisplayName" header="Table" formatter={(row) => getTableDisplayName(row, tables)} />
|
||||
<TableColumn
|
||||
fieldName="isOutput"
|
||||
header="Output"
|
||||
formatter={(row) => (
|
||||
<CheckboxField
|
||||
checked={row.isOutput}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) changeColumn({ ...row, isOutput: true });
|
||||
else changeColumn({ ...row, isOutput: false });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<TableColumn
|
||||
fieldName="alias"
|
||||
header="Alias"
|
||||
formatter={(row) => (
|
||||
<TextField
|
||||
value={row.alias}
|
||||
onChange={(e) => {
|
||||
changeColumn({ ...row, alias: e.target.value });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<TableColumn
|
||||
fieldName="isGrouped"
|
||||
header="Group by"
|
||||
formatter={(row) => (
|
||||
<CheckboxField
|
||||
checked={row.isGrouped}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) changeColumn({ ...row, isGrouped: true });
|
||||
else changeColumn({ ...row, isGrouped: false });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<TableColumn
|
||||
fieldName="aggregate"
|
||||
header="Aggregate"
|
||||
formatter={(row) =>
|
||||
!row.isGrouped && (
|
||||
<SelectField
|
||||
value={row.aggregate}
|
||||
onChange={(e) => {
|
||||
changeColumn({ ...row, aggregate: e.target.value });
|
||||
}}
|
||||
>
|
||||
<option value="---">---</option>
|
||||
<option value="MIN">MIN</option>
|
||||
<option value="MAX">MAX</option>
|
||||
<option value="COUNT">COUNT</option>
|
||||
<option value="COUNT DISTINCT">COUNT DISTINCT</option>
|
||||
<option value="SUM">SUM</option>
|
||||
<option value="AVG">AVG</option>
|
||||
</SelectField>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<TableColumn
|
||||
fieldName="sortOrder"
|
||||
header="Sort order"
|
||||
formatter={(row) => (
|
||||
<SelectField
|
||||
value={row.sortOrder}
|
||||
onChange={(e) => {
|
||||
changeColumn({ ...row, sortOrder: parseInt(e.target.value) });
|
||||
}}
|
||||
>
|
||||
<option value="0">---</option>
|
||||
<option value="1">1st, ascending</option>
|
||||
<option value="-1">1st, descending</option>
|
||||
<option value="2">2nd, ascending</option>
|
||||
<option value="-2">2nd, descending</option>
|
||||
<option value="3">3rd, ascending</option>
|
||||
<option value="-3">3rd, descending</option>,
|
||||
</SelectField>
|
||||
)}
|
||||
/>
|
||||
<TableColumn
|
||||
fieldName="filter"
|
||||
header="Filter"
|
||||
formatter={(row) => (
|
||||
<DataFilterControl
|
||||
filterType={findDesignerFilterType(row, value)}
|
||||
filter={row.filter}
|
||||
setFilter={(filter) => {
|
||||
changeColumn({ ...row, filter });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{hasGroupedColumn && (
|
||||
<TableColumn
|
||||
fieldName="groupFilter"
|
||||
header="Group filter"
|
||||
formatter={(row) => (
|
||||
<DataFilterControl
|
||||
filterType={findDesignerFilterType(row, value)}
|
||||
filter={row.groupFilter}
|
||||
setFilter={(groupFilter) => {
|
||||
changeColumn({ ...row, groupFilter });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<TableColumn
|
||||
fieldName="actions"
|
||||
header=""
|
||||
formatter={(row) => (
|
||||
<>
|
||||
<InlineButton onClick={() => removeColumn(row)}>Remove</InlineButton>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</TableControl>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user