Compare commits
112 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c582902902 | |||
| e9cd1906bc | |||
| 2706297142 | |||
| 75465bf415 | |||
| 42a79b2557 | |||
| 838bc34a4f | |||
| 63cdb4e507 | |||
| ff3c39ccad | |||
| 49597b4b01 | |||
| a3b7490849 | |||
| 45a1c58dc5 | |||
| 61b9fd9210 | |||
| 7c8156fbb9 | |||
| 7a0635234a | |||
| 7e5364d400 | |||
| cfa08286de | |||
| 9132bfb656 | |||
| a9352f2a93 | |||
| 47729d8cc3 | |||
| e537b43563 | |||
| 5f14da3844 | |||
| e179b0f20b | |||
| 35532b718a | |||
| 42c71c1204 | |||
| 591945dc93 | |||
| ecfaa7198b | |||
| 27e714111b | |||
| c086eaa510 | |||
| a7444a1475 | |||
| 399298d3bb | |||
| 196c0b8a3e | |||
| 5d6d827044 | |||
| 2440d6b75f | |||
| 623456b0a7 | |||
| 9bfb37ab94 | |||
| 630d909b73 | |||
| 33552e30b7 | |||
| a64504ba02 | |||
| 04b195f4c6 | |||
| 17fd9035ee | |||
| f867cc5a1e | |||
| 97aa563fe7 | |||
| fb2e261a08 | |||
| aad4df419c | |||
| de60f1b335 | |||
| c0c06a2099 | |||
| bcfb54b7c7 | |||
| 8b56ebfb39 | |||
| 1128fe6c8f | |||
| db5f5a9153 | |||
| 25fb3b71ca | |||
| a6822dd293 | |||
| 112513a569 | |||
| fc448ed578 | |||
| f777530b1c | |||
| 7fcebedcdd | |||
| cf39fd59f9 | |||
| 6beecd157f | |||
| 57b26a2729 | |||
| 6ee8ca5f86 | |||
| 1adf1da0eb | |||
| d537a75d83 | |||
| 2e847eee9b | |||
| 07cb4defe6 | |||
| 74a597164e | |||
| f7f4a0ed3f | |||
| dc45b1e75f | |||
| 5e68ce3218 | |||
| faf6339b41 | |||
| 33cd3b0647 | |||
| 4c5da50a04 | |||
| 2c805b3357 | |||
| f345c80144 | |||
| 4346147bfc | |||
| b0405855aa | |||
| 53ee6eacb2 | |||
| f39b3dd347 | |||
| 385f8ff5fd | |||
| fad8e91c7e | |||
| 74b0216714 | |||
| af3529e5e7 | |||
| d3936ae3ec | |||
| 0afee6e3fe | |||
| f1920549a8 | |||
| b5661afdcf | |||
| 38a80ec695 | |||
| f697ba03f8 | |||
| feaaa35590 | |||
| 74c04cf113 | |||
| 83e15ede5c | |||
| 6a942a5058 | |||
| 8864c3489d | |||
| a4cb65b7b1 | |||
| c3fe20b6f9 | |||
| 8db941dc06 | |||
| 05329951f9 | |||
| dd964273cd | |||
| c3c9ad1aed | |||
| cd8fe5d691 | |||
| 15d99f98f8 | |||
| be6e0f3bc8 | |||
| 3867b7f5ba | |||
| 1b347c7e0b | |||
| 10664b16fe | |||
| e10e8ca161 | |||
| c3e05e22ad | |||
| 97753e2b11 | |||
| 315c0670d0 | |||
| e5fb3414fe | |||
| 227d81a01a | |||
| bacb9510d7 | |||
| 48209509ae |
@@ -8,6 +8,17 @@ Builds:
|
||||
- linux - application for linux
|
||||
- win - application for Windows
|
||||
|
||||
### 5.3.4
|
||||
- FIXED: On blank system does not start (window does not appear) #862
|
||||
- FIXED: Missing Execute, Export bar #861
|
||||
|
||||
### 5.3.3
|
||||
- FIXED: The application Window is not visible when openning after changing monitor configuration. #856
|
||||
- FIXED: Multi column filter is broken for Postgresql #855
|
||||
- ADDED: Do not display internal timescaledb objects in postgres databases #839
|
||||
- FIXED: When in splitview mode and Clicking "Refresh" button on the right side, will refresh the left side, and not the right side #810
|
||||
- FIXED: Cannot filter by uuid field in psql #538
|
||||
|
||||
### 5.3.1
|
||||
- FIXED: Column sorting on query tab not working #819
|
||||
- FIXED: Postgres Connection stays in "Loading database structure" until reloading the page #826
|
||||
|
||||
+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>.
|
||||
@@ -0,0 +1,25 @@
|
||||
This project is licensed under the GPLv3 License. See the LICENSE file for full text of the GPLv3 license.
|
||||
|
||||
The original project was licensed under the MIT License, and the following notice applies to the original code:
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Jan Prochazka
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+3
-1
@@ -7,6 +7,7 @@
|
||||
"dependencies": {
|
||||
"electron-log": "^4.4.1",
|
||||
"electron-updater": "^4.6.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lodash.clonedeepwith": "^4.5.0",
|
||||
"patch-package": "^6.4.7"
|
||||
},
|
||||
@@ -121,6 +122,7 @@
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"better-sqlite3": "9.6.0",
|
||||
"msnodesqlv8": "^4.2.1"
|
||||
"msnodesqlv8": "^4.2.1",
|
||||
"oracledb": "^6.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
+49
-5
@@ -16,7 +16,7 @@ const BrowserWindow = electron.BrowserWindow;
|
||||
const path = require('path');
|
||||
const url = require('url');
|
||||
const mainMenuDefinition = require('./mainMenuDefinition');
|
||||
const { settings } = require('cluster');
|
||||
const { isProApp, checkLicense } = require('./proTools');
|
||||
let disableAutoUpgrade = false;
|
||||
|
||||
// require('@electron/remote/main').initialize();
|
||||
@@ -276,10 +276,34 @@ function fillMissingSettings(value) {
|
||||
return res;
|
||||
}
|
||||
|
||||
function ensureBoundsVisible(bounds) {
|
||||
const area = electron.screen.getDisplayMatching(bounds).workArea;
|
||||
|
||||
let { x, y, width, height } = bounds;
|
||||
|
||||
const isWithinDisplay =
|
||||
x >= area.x && x + width <= area.x + area.width && y >= area.y && y + height <= area.y + area.height;
|
||||
|
||||
if (!isWithinDisplay) {
|
||||
width = Math.min(width, area.width);
|
||||
height = Math.min(height, area.height);
|
||||
|
||||
if (width < 400) width = 400;
|
||||
if (height < 300) height = 300;
|
||||
|
||||
x = area.x; // + Math.round(area.width - width / 2);
|
||||
y = area.y; // + Math.round(area.height - height / 2);
|
||||
}
|
||||
|
||||
return { x, y, width, height };
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
const datadir = path.join(os.homedir(), '.dbgate');
|
||||
|
||||
let settingsJson = {};
|
||||
let licenseKey = null;
|
||||
try {
|
||||
const datadir = path.join(os.homedir(), '.dbgate');
|
||||
settingsJson = fillMissingSettings(
|
||||
JSON.parse(fs.readFileSync(path.join(datadir, 'settings.json'), { encoding: 'utf-8' }))
|
||||
);
|
||||
@@ -287,9 +311,22 @@ function createWindow() {
|
||||
console.log('Error loading settings.json:', err.message);
|
||||
settingsJson = fillMissingSettings({});
|
||||
}
|
||||
if (isProApp()) {
|
||||
try {
|
||||
licenseKey = fs.readFileSync(path.join(datadir, 'license.key'), { encoding: 'utf-8' });
|
||||
} catch (err) {
|
||||
console.log('Error loading license.key:', err.message);
|
||||
licenseKey = null;
|
||||
}
|
||||
}
|
||||
|
||||
const bounds = initialConfig['winBounds'];
|
||||
useNativeMenu = settingsJson['app.useNativeMenu'];
|
||||
const licenseOk = !isProApp() || checkLicense(licenseKey) == 'premium';
|
||||
|
||||
let bounds = initialConfig['winBounds'];
|
||||
if (bounds) {
|
||||
bounds = ensureBoundsVisible(bounds);
|
||||
}
|
||||
useNativeMenu = settingsJson['app.useNativeMenu'] || !licenseOk;
|
||||
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
@@ -340,11 +377,13 @@ function createWindow() {
|
||||
console.log('Error saving config-root:', err.message);
|
||||
}
|
||||
});
|
||||
|
||||
// mainWindow.webContents.toggleDevTools();
|
||||
|
||||
mainWindow.loadURL(startUrl);
|
||||
if (os.platform() == 'linux') {
|
||||
mainWindow.setIcon(path.resolve(__dirname, '../icon.png'));
|
||||
}
|
||||
// mainWindow.webContents.toggleDevTools();
|
||||
|
||||
mainWindow.on('maximize', () => {
|
||||
mainWindow.webContents.send('setIsMaximized', true);
|
||||
@@ -353,6 +392,11 @@ function createWindow() {
|
||||
mainWindow.on('unmaximize', () => {
|
||||
mainWindow.webContents.send('setIsMaximized', false);
|
||||
});
|
||||
|
||||
// app.on('browser-window-focus', () => {
|
||||
// const bounds = ensureBoundsVisible(mainWindow.getBounds());
|
||||
// mainWindow.setBounds(bounds);
|
||||
// });
|
||||
}
|
||||
|
||||
if (!apiLoaded) {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
function isProApp() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function checkLicense(license) {
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isProApp,
|
||||
checkLicense,
|
||||
};
|
||||
@@ -476,6 +476,11 @@ buffer-crc32@~0.2.3:
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
|
||||
|
||||
buffer-equal@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe"
|
||||
@@ -927,6 +932,13 @@ duplexer3@^0.1.4:
|
||||
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e"
|
||||
integrity sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
|
||||
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
ejs@^3.1.7:
|
||||
version "3.1.10"
|
||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b"
|
||||
@@ -1663,6 +1675,39 @@ jsonfile@^6.0.1:
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsonwebtoken@^9.0.2:
|
||||
version "9.0.2"
|
||||
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3"
|
||||
integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==
|
||||
dependencies:
|
||||
jws "^3.2.2"
|
||||
lodash.includes "^4.3.0"
|
||||
lodash.isboolean "^3.0.3"
|
||||
lodash.isinteger "^4.0.4"
|
||||
lodash.isnumber "^3.0.3"
|
||||
lodash.isplainobject "^4.0.6"
|
||||
lodash.isstring "^4.0.1"
|
||||
lodash.once "^4.0.0"
|
||||
ms "^2.1.1"
|
||||
semver "^7.5.4"
|
||||
|
||||
jwa@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
|
||||
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
|
||||
dependencies:
|
||||
buffer-equal-constant-time "1.0.1"
|
||||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jws@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
|
||||
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
|
||||
dependencies:
|
||||
jwa "^1.4.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
keyv@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
|
||||
@@ -1718,11 +1763,46 @@ lodash.escaperegexp@^4.1.2:
|
||||
resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
|
||||
integrity sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==
|
||||
|
||||
lodash.includes@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
|
||||
integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
|
||||
|
||||
lodash.isboolean@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
|
||||
integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
|
||||
|
||||
lodash.isequal@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
|
||||
|
||||
lodash.isinteger@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
|
||||
integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
|
||||
|
||||
lodash.isnumber@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
|
||||
integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==
|
||||
|
||||
lodash.isplainobject@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||
integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
|
||||
|
||||
lodash.isstring@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
|
||||
|
||||
lodash.once@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
|
||||
|
||||
lodash@^4.17.15:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
@@ -1860,6 +1940,11 @@ ms@2.1.2:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
ms@^2.1.1:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
|
||||
msnodesqlv8@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/msnodesqlv8/-/msnodesqlv8-4.2.1.tgz#59f2930e7f3b9b201d7288425a6ffa923ea1a573"
|
||||
@@ -1944,6 +2029,11 @@ open@^7.4.2:
|
||||
is-docker "^2.0.0"
|
||||
is-wsl "^2.1.1"
|
||||
|
||||
oracledb@^6.6.0:
|
||||
version "6.6.0"
|
||||
resolved "https://registry.yarnpkg.com/oracledb/-/oracledb-6.6.0.tgz#bb40adbe81a84a1e544c48af9f120c61f030e936"
|
||||
integrity sha512-T3dx+o3j+tVN53wQyr4yGTmoPHLy+a2V8yb1T2PmWrsj3ZlSt2Yu1BgV2yTDqnmBZYpRi/I3yJXRCOHHD7PiyA==
|
||||
|
||||
os-tmpdir@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||
@@ -2317,6 +2407,11 @@ semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.1.tgz#60bfe090bf907a25aa8119a72b9f90ef7ca281b2"
|
||||
integrity sha512-f/vbBsu+fOiYt+lmwZV0rVwJScl46HppnOA1ZvIuBWKOTlllpyJ3bfVax76/OrhCH38dyxoDIA8K7uB963IYgA==
|
||||
|
||||
semver@^7.5.4:
|
||||
version "7.6.3"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
|
||||
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
|
||||
|
||||
serialize-error@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18"
|
||||
|
||||
@@ -3,9 +3,10 @@ const fs = require('fs');
|
||||
let fillContent = '';
|
||||
|
||||
if (process.platform == 'win32') {
|
||||
fillContent += `content.msnodesqlv8 = () => require('msnodesqlv8');`;
|
||||
fillContent += `content.msnodesqlv8 = () => require('msnodesqlv8');\n`;
|
||||
}
|
||||
fillContent += `content['better-sqlite3'] = () => require('better-sqlite3');`;
|
||||
fillContent += `content['better-sqlite3'] = () => require('better-sqlite3');\n`;
|
||||
fillContent += `content['oracledb'] = () => require('oracledb');\n`;
|
||||
|
||||
const getContent = empty => `
|
||||
// this file is generated automatically by script fillNativeModules.js, do not edit it manually
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "5.3.1",
|
||||
"version": "5.3.5-beta.3",
|
||||
"name": "dbgate-all",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
@@ -51,8 +51,8 @@
|
||||
"resetPackagedPlugins": "node resetPackagedPlugins",
|
||||
"prettier": "prettier --write packages/api/src && prettier --write packages/datalib/src && prettier --write packages/filterparser/src && prettier --write packages/sqltree/src && prettier --write packages/tools/src && prettier --write packages/types && prettier --write packages/web/src && prettier --write app/src",
|
||||
"copy:docker:build": "copyfiles packages/api/dist/* docker -f && copyfiles packages/web/public/* docker -u 2 && copyfiles \"packages/web/public/**/*\" docker -u 2 && copyfiles \"plugins/dist/**/*\" docker/plugins -u 2",
|
||||
"install:sqlite:docker": "cd docker && yarn init --yes && yarn add better-sqlite3 && cd ..",
|
||||
"prepare:docker": "yarn plugins:copydist && yarn build:web:docker && yarn build:api && yarn copy:docker:build && yarn install:sqlite:docker",
|
||||
"install:drivers:docker": "cd docker && yarn init --yes && yarn add better-sqlite3 && yarn add oracledb && cd ..",
|
||||
"prepare:docker": "yarn plugins:copydist && yarn build:web:docker && yarn build:api && yarn copy:docker:build && yarn install:drivers:docker",
|
||||
"start": "concurrently --kill-others-on-fail \"yarn start:api\" \"yarn start:web\"",
|
||||
"lib": "concurrently --kill-others-on-fail \"yarn start:sqltree\" \"yarn start:filterparser\" \"yarn start:datalib\" \"yarn start:tools\" \"yarn build:plugins:frontend:watch\"",
|
||||
"ts:api": "yarn workspace dbgate-api ts",
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"better-sqlite3": "9.6.0",
|
||||
"msnodesqlv8": "^4.2.1"
|
||||
"msnodesqlv8": "^4.2.1",
|
||||
"oracledb": "^6.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
const crypto = require('crypto');
|
||||
|
||||
const tokenSecret = crypto.randomUUID();
|
||||
|
||||
function getTokenLifetime() {
|
||||
return process.env.TOKEN_LIFETIME || '1d';
|
||||
}
|
||||
|
||||
function getTokenSecret() {
|
||||
return tokenSecret;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getTokenLifetime,
|
||||
getTokenSecret,
|
||||
};
|
||||
@@ -0,0 +1,322 @@
|
||||
const { getTokenSecret, getTokenLifetime } = require('./authCommon');
|
||||
const _ = require('lodash');
|
||||
const axios = require('axios');
|
||||
const { getLogger, getPredefinedPermissions } = require('dbgate-tools');
|
||||
|
||||
const AD = require('activedirectory2').promiseWrapper;
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const logger = getLogger('authProvider');
|
||||
|
||||
class AuthProviderBase {
|
||||
amoid = 'none';
|
||||
|
||||
async login(login, password, options = undefined) {
|
||||
return {
|
||||
accessToken: jwt.sign(
|
||||
{
|
||||
amoid: this.amoid,
|
||||
},
|
||||
getTokenSecret(),
|
||||
{ expiresIn: getTokenLifetime() }
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
oauthToken(params) {
|
||||
return {};
|
||||
}
|
||||
|
||||
getCurrentLogin(req) {
|
||||
const login = req?.user?.login ?? req?.auth?.user ?? null;
|
||||
return login;
|
||||
}
|
||||
|
||||
isUserLoggedIn(req) {
|
||||
return !!req?.user || !!req?.auth;
|
||||
}
|
||||
|
||||
getCurrentPermissions(req) {
|
||||
const login = this.getCurrentLogin(req);
|
||||
const permissions = process.env[`LOGIN_PERMISSIONS_${login}`];
|
||||
return permissions || process.env.PERMISSIONS;
|
||||
}
|
||||
|
||||
getLoginPageConnections() {
|
||||
return null;
|
||||
}
|
||||
|
||||
getSingleConnectionId(req) {
|
||||
return null;
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
amoid: this.amoid,
|
||||
workflowType: 'anonymous',
|
||||
name: 'Anonymous',
|
||||
};
|
||||
}
|
||||
|
||||
async redirect({ state }) {
|
||||
return {
|
||||
status: 'error',
|
||||
};
|
||||
}
|
||||
|
||||
async getLogoutUrl() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class OAuthProvider extends AuthProviderBase {
|
||||
amoid = 'oauth';
|
||||
|
||||
async oauthToken(params) {
|
||||
const { redirectUri, code } = params;
|
||||
|
||||
const scopeParam = process.env.OAUTH_SCOPE ? `&scope=${process.env.OAUTH_SCOPE}` : '';
|
||||
const resp = await axios.default.post(
|
||||
`${process.env.OAUTH_TOKEN}`,
|
||||
`grant_type=authorization_code&code=${encodeURIComponent(code)}&redirect_uri=${encodeURIComponent(
|
||||
redirectUri
|
||||
)}&client_id=${process.env.OAUTH_CLIENT_ID}&client_secret=${process.env.OAUTH_CLIENT_SECRET}${scopeParam}`
|
||||
);
|
||||
|
||||
const { access_token, refresh_token } = resp.data;
|
||||
|
||||
const payload = jwt.decode(access_token);
|
||||
|
||||
logger.info({ payload }, 'User payload returned from OAUTH');
|
||||
|
||||
const login =
|
||||
process.env.OAUTH_LOGIN_FIELD && payload && payload[process.env.OAUTH_LOGIN_FIELD]
|
||||
? payload[process.env.OAUTH_LOGIN_FIELD]
|
||||
: 'oauth';
|
||||
|
||||
if (
|
||||
process.env.OAUTH_ALLOWED_LOGINS &&
|
||||
!process.env.OAUTH_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim())
|
||||
) {
|
||||
return { error: `Username ${login} not allowed to log in` };
|
||||
}
|
||||
|
||||
const groups =
|
||||
process.env.OAUTH_GROUP_FIELD && payload && payload[process.env.OAUTH_GROUP_FIELD]
|
||||
? payload[process.env.OAUTH_GROUP_FIELD]
|
||||
: [];
|
||||
|
||||
const allowedGroups = process.env.OAUTH_ALLOWED_GROUPS
|
||||
? process.env.OAUTH_ALLOWED_GROUPS.split(',').map(group => group.toLowerCase().trim())
|
||||
: [];
|
||||
|
||||
if (process.env.OAUTH_ALLOWED_GROUPS && !groups.some(group => allowedGroups.includes(group.toLowerCase().trim()))) {
|
||||
return { error: `Username ${login} does not belong to an allowed group` };
|
||||
}
|
||||
|
||||
if (access_token) {
|
||||
return {
|
||||
accessToken: jwt.sign({ login }, getTokenSecret(), { expiresIn: getTokenLifetime() }),
|
||||
};
|
||||
}
|
||||
|
||||
return { error: 'Token not found' };
|
||||
}
|
||||
|
||||
async getLogoutUrl() {
|
||||
return process.env.OAUTH_LOGOUT;
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
...super.toJson(),
|
||||
workflowType: 'redirect',
|
||||
name: 'OAuth 2.0',
|
||||
};
|
||||
}
|
||||
|
||||
redirect({ state, redirectUri }) {
|
||||
const scopeParam = process.env.OAUTH_SCOPE ? `&scope=${process.env.OAUTH_SCOPE}` : '';
|
||||
return {
|
||||
status: 'ok',
|
||||
uri: `${process.env.OAUTH_AUTH}?client_id=${
|
||||
process.env.OAUTH_CLIENT_ID
|
||||
}&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&state=${encodeURIComponent(
|
||||
state
|
||||
)}${scopeParam}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class ADProvider extends AuthProviderBase {
|
||||
amoid = 'ad';
|
||||
|
||||
async login(login, password, options = undefined) {
|
||||
const adConfig = {
|
||||
url: process.env.AD_URL,
|
||||
baseDN: process.env.AD_BASEDN,
|
||||
username: process.env.AD_USERNAME,
|
||||
password: process.env.AD_PASSWORD,
|
||||
};
|
||||
const ad = new AD(adConfig);
|
||||
try {
|
||||
const res = await ad.authenticate(login, password);
|
||||
if (!res) {
|
||||
return { error: 'Login failed' };
|
||||
}
|
||||
if (
|
||||
process.env.AD_ALLOWED_LOGINS &&
|
||||
!process.env.AD_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim())
|
||||
) {
|
||||
return { error: `Username ${login} not allowed to log in` };
|
||||
}
|
||||
return {
|
||||
accessToken: jwt.sign(
|
||||
{
|
||||
amoid: this.amoid,
|
||||
login,
|
||||
},
|
||||
getTokenSecret(),
|
||||
{ expiresIn: getTokenLifetime() }
|
||||
),
|
||||
};
|
||||
} catch (e) {
|
||||
return { error: 'Login failed' };
|
||||
}
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
...super.toJson(),
|
||||
workflowType: 'credentials',
|
||||
name: 'Active Directory',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class LoginsProvider extends AuthProviderBase {
|
||||
amoid = 'logins';
|
||||
|
||||
async login(login, password, options = undefined) {
|
||||
if (password == process.env[`LOGIN_PASSWORD_${login}`]) {
|
||||
return {
|
||||
accessToken: jwt.sign(
|
||||
{
|
||||
amoid: this.amoid,
|
||||
login,
|
||||
},
|
||||
getTokenSecret(),
|
||||
{ expiresIn: getTokenLifetime() }
|
||||
),
|
||||
};
|
||||
}
|
||||
return { error: 'Invalid credentials' };
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
...super.toJson(),
|
||||
workflowType: 'credentials',
|
||||
name: 'Login & Password',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class DenyAllProvider extends AuthProviderBase {
|
||||
amoid = 'deny';
|
||||
|
||||
async login(login, password, options = undefined) {
|
||||
return { error: 'Login not allowed' };
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
...super.toJson(),
|
||||
workflowType: 'credentials',
|
||||
name: 'Deny all',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function hasEnvLogins() {
|
||||
if (process.env.LOGIN && process.env.PASSWORD) {
|
||||
return true;
|
||||
}
|
||||
for (const key in process.env) {
|
||||
if (key.startsWith('LOGIN_PASSWORD_')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function detectEnvAuthProvider() {
|
||||
if (process.env.AUTH_PROVIDER) {
|
||||
return process.env.AUTH_PROVIDER;
|
||||
}
|
||||
|
||||
if (process.env.STORAGE_DATABASE) {
|
||||
return 'denyall';
|
||||
}
|
||||
if (process.env.OAUTH_AUTH) {
|
||||
return 'oauth';
|
||||
}
|
||||
if (process.env.AD_URL) {
|
||||
return 'ad';
|
||||
}
|
||||
if (hasEnvLogins()) {
|
||||
return 'logins';
|
||||
}
|
||||
return 'none';
|
||||
}
|
||||
|
||||
function createEnvAuthProvider() {
|
||||
const authProvider = detectEnvAuthProvider();
|
||||
switch (authProvider) {
|
||||
case 'oauth':
|
||||
return new OAuthProvider();
|
||||
case 'ad':
|
||||
return new ADProvider();
|
||||
case 'logins':
|
||||
return new LoginsProvider();
|
||||
case 'denyall':
|
||||
return new DenyAllProvider();
|
||||
default:
|
||||
return new AuthProviderBase();
|
||||
}
|
||||
}
|
||||
|
||||
let defaultAuthProvider = createEnvAuthProvider();
|
||||
let authProviders = [defaultAuthProvider];
|
||||
|
||||
function getAuthProviders() {
|
||||
return authProviders;
|
||||
}
|
||||
|
||||
function getAuthProviderById(amoid) {
|
||||
return authProviders.find(x => x.amoid == amoid);
|
||||
}
|
||||
|
||||
function getDefaultAuthProvider() {
|
||||
return defaultAuthProvider;
|
||||
}
|
||||
|
||||
function getAuthProviderFromReq(req) {
|
||||
const authProviderId = req?.auth?.amoid || req?.user?.amoid;
|
||||
return getAuthProviderById(authProviderId) ?? getDefaultAuthProvider();
|
||||
}
|
||||
|
||||
function setAuthProviders(value, defaultProvider = null) {
|
||||
authProviders = value;
|
||||
defaultAuthProvider = defaultProvider || value[0];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
AuthProviderBase,
|
||||
detectEnvAuthProvider,
|
||||
getAuthProviders,
|
||||
getDefaultAuthProvider,
|
||||
setAuthProviders,
|
||||
getAuthProviderById,
|
||||
getAuthProviderFromReq,
|
||||
};
|
||||
@@ -1,24 +1,20 @@
|
||||
const axios = require('axios');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const getExpressPath = require('../utility/getExpressPath');
|
||||
const { getLogins } = require('../utility/hasPermission');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const AD = require('activedirectory2').promiseWrapper;
|
||||
const crypto = require('crypto');
|
||||
const { getTokenSecret, getTokenLifetime } = require('../auth/authCommon');
|
||||
const {
|
||||
getAuthProviderFromReq,
|
||||
getAuthProviders,
|
||||
getDefaultAuthProvider,
|
||||
getAuthProviderById,
|
||||
} = require('../auth/authProvider');
|
||||
const storage = require('./storage');
|
||||
|
||||
const logger = getLogger('auth');
|
||||
|
||||
const tokenSecret = crypto.randomUUID();
|
||||
|
||||
function shouldAuthorizeApi() {
|
||||
const logins = getLogins();
|
||||
return !!process.env.OAUTH_AUTH || !!process.env.AD_URL || (!!logins && !process.env.BASIC_AUTH);
|
||||
}
|
||||
|
||||
function getTokenLifetime() {
|
||||
return process.env.TOKEN_LIFETIME || '1d';
|
||||
}
|
||||
|
||||
function unauthorizedResponse(req, res, text) {
|
||||
// if (req.path == getExpressPath('/config/get-settings')) {
|
||||
// return res.json({});
|
||||
@@ -30,11 +26,31 @@ function unauthorizedResponse(req, res, text) {
|
||||
}
|
||||
|
||||
function authMiddleware(req, res, next) {
|
||||
const SKIP_AUTH_PATHS = ['/config/get', '/auth/oauth-token', '/auth/login', '/stream'];
|
||||
const SKIP_AUTH_PATHS = [
|
||||
'/config/get',
|
||||
'/config/logout',
|
||||
'/config/get-settings',
|
||||
'/config/save-license-key',
|
||||
'/auth/oauth-token',
|
||||
'/auth/login',
|
||||
'/auth/redirect',
|
||||
'/stream',
|
||||
'storage/get-connections-for-login-page',
|
||||
'auth/get-providers',
|
||||
'/connections/dblogin',
|
||||
'/connections/dblogin-auth',
|
||||
'/connections/dblogin-auth-token',
|
||||
];
|
||||
|
||||
if (!shouldAuthorizeApi()) {
|
||||
// console.log('********************* getAuthProvider()', getAuthProvider());
|
||||
|
||||
// const isAdminPage = req.headers['x-is-admin-page'] == 'true';
|
||||
|
||||
if (process.env.BASIC_AUTH) {
|
||||
// API is not authorized for basic auth
|
||||
return next();
|
||||
}
|
||||
|
||||
let skipAuth = !!SKIP_AUTH_PATHS.find(x => req.path == getExpressPath(x));
|
||||
|
||||
const authHeader = req.headers.authorization;
|
||||
@@ -46,7 +62,7 @@ function authMiddleware(req, res, next) {
|
||||
}
|
||||
const token = authHeader.split(' ')[1];
|
||||
try {
|
||||
const decoded = jwt.verify(token, tokenSecret);
|
||||
const decoded = jwt.verify(token, getTokenSecret());
|
||||
req.user = decoded;
|
||||
return next();
|
||||
} catch (err) {
|
||||
@@ -63,106 +79,49 @@ function authMiddleware(req, res, next) {
|
||||
module.exports = {
|
||||
oauthToken_meta: true,
|
||||
async oauthToken(params) {
|
||||
const { redirectUri, code } = params;
|
||||
|
||||
const scopeParam = process.env.OAUTH_SCOPE ? `&scope=${process.env.OAUTH_SCOPE}` : '';
|
||||
const resp = await axios.default.post(
|
||||
`${process.env.OAUTH_TOKEN}`,
|
||||
`grant_type=authorization_code&code=${encodeURIComponent(code)}&redirect_uri=${encodeURIComponent(
|
||||
redirectUri
|
||||
)}&client_id=${process.env.OAUTH_CLIENT_ID}&client_secret=${process.env.OAUTH_CLIENT_SECRET}${scopeParam}`
|
||||
);
|
||||
|
||||
const { access_token, refresh_token } = resp.data;
|
||||
|
||||
const payload = jwt.decode(access_token);
|
||||
|
||||
logger.info({ payload }, 'User payload returned from OAUTH');
|
||||
|
||||
const login =
|
||||
process.env.OAUTH_LOGIN_FIELD && payload && payload[process.env.OAUTH_LOGIN_FIELD]
|
||||
? payload[process.env.OAUTH_LOGIN_FIELD]
|
||||
: 'oauth';
|
||||
|
||||
if (
|
||||
process.env.OAUTH_ALLOWED_LOGINS &&
|
||||
!process.env.OAUTH_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim())
|
||||
) {
|
||||
return { error: `Username ${login} not allowed to log in` };
|
||||
}
|
||||
|
||||
const groups =
|
||||
process.env.OAUTH_GROUP_FIELD && payload && payload[process.env.OAUTH_GROUP_FIELD]
|
||||
? payload[process.env.OAUTH_GROUP_FIELD]
|
||||
: [];
|
||||
|
||||
const allowedGroups =
|
||||
process.env.OAUTH_ALLOWED_GROUPS
|
||||
? process.env.OAUTH_ALLOWED_GROUPS.split(',').map(group => group.toLowerCase().trim())
|
||||
: [];
|
||||
|
||||
if (
|
||||
process.env.OAUTH_ALLOWED_GROUPS &&
|
||||
!groups.some(group => allowedGroups.includes(group.toLowerCase().trim()))
|
||||
) {
|
||||
return { error: `Username ${login} does not belong to an allowed group` };
|
||||
}
|
||||
|
||||
if (access_token) {
|
||||
return {
|
||||
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
|
||||
};
|
||||
}
|
||||
|
||||
return { error: 'Token not found' };
|
||||
const { amoid } = params;
|
||||
return getAuthProviderById(amoid).oauthToken(params);
|
||||
},
|
||||
login_meta: true,
|
||||
async login(params) {
|
||||
const { login, password } = params;
|
||||
const { amoid, login, password, isAdminPage } = params;
|
||||
|
||||
if (process.env.AD_URL) {
|
||||
const adConfig = {
|
||||
url: process.env.AD_URL,
|
||||
baseDN: process.env.AD_BASEDN,
|
||||
username: process.env.AD_USERNAME,
|
||||
password: process.env.AD_PASSOWRD,
|
||||
};
|
||||
const ad = new AD(adConfig);
|
||||
try {
|
||||
const res = await ad.authenticate(login, password);
|
||||
if (!res) {
|
||||
return { error: 'Login failed' };
|
||||
}
|
||||
if (
|
||||
process.env.AD_ALLOWED_LOGINS &&
|
||||
!process.env.AD_ALLOWED_LOGINS.split(',').find(x => x.toLowerCase().trim() == login.toLowerCase().trim())
|
||||
) {
|
||||
return { error: `Username ${login} not allowed to log in` };
|
||||
}
|
||||
if (isAdminPage) {
|
||||
if (process.env.ADMIN_PASSWORD && process.env.ADMIN_PASSWORD == password) {
|
||||
return {
|
||||
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
|
||||
};
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Failed active directory authentization');
|
||||
return {
|
||||
error: err.message,
|
||||
accessToken: jwt.sign(
|
||||
{
|
||||
login: 'superadmin',
|
||||
permissions: await storage.loadSuperadminPermissions(),
|
||||
roleId: -3,
|
||||
},
|
||||
getTokenSecret(),
|
||||
{
|
||||
expiresIn: getTokenLifetime(),
|
||||
}
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return { error: 'Login failed' };
|
||||
}
|
||||
|
||||
const logins = getLogins();
|
||||
if (!logins) {
|
||||
return { error: 'Logins not configured' };
|
||||
}
|
||||
const foundLogin = logins.find(x => x.login == login);
|
||||
if (foundLogin && foundLogin.password && foundLogin.password == password) {
|
||||
return {
|
||||
accessToken: jwt.sign({ login }, tokenSecret, { expiresIn: getTokenLifetime() }),
|
||||
};
|
||||
}
|
||||
return { error: 'Invalid credentials' };
|
||||
return getAuthProviderById(amoid).login(login, password);
|
||||
},
|
||||
|
||||
getProviders_meta: true,
|
||||
getProviders() {
|
||||
return {
|
||||
providers: getAuthProviders().map(x => x.toJson()),
|
||||
default: getDefaultAuthProvider()?.amoid,
|
||||
};
|
||||
},
|
||||
|
||||
redirect_meta: true,
|
||||
async redirect(params) {
|
||||
const { amoid } = params;
|
||||
return getAuthProviderById(amoid).redirect(params);
|
||||
},
|
||||
|
||||
authMiddleware,
|
||||
shouldAuthorizeApi,
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ const os = require('os');
|
||||
const path = require('path');
|
||||
const axios = require('axios');
|
||||
const { datadir, getLogsFilePath } = require('../utility/directories');
|
||||
const { hasPermission, getLogins } = require('../utility/hasPermission');
|
||||
const { hasPermission } = require('../utility/hasPermission');
|
||||
const socket = require('../utility/socket');
|
||||
const _ = require('lodash');
|
||||
const AsyncLock = require('async-lock');
|
||||
@@ -11,6 +11,9 @@ const AsyncLock = require('async-lock');
|
||||
const currentVersion = require('../currentVersion');
|
||||
const platformInfo = require('../utility/platformInfo');
|
||||
const connections = require('../controllers/connections');
|
||||
const { getAuthProviderFromReq } = require('../auth/authProvider');
|
||||
const { checkLicense, checkLicenseKey } = require('../utility/checkLicense');
|
||||
const { storageWriteConfig } = require('./storageDb');
|
||||
|
||||
const lock = new AsyncLock();
|
||||
|
||||
@@ -27,27 +30,50 @@ module.exports = {
|
||||
|
||||
get_meta: true,
|
||||
async get(_params, req) {
|
||||
const logins = getLogins();
|
||||
const loginName =
|
||||
req && req.user && req.user.login ? req.user.login : req && req.auth && req.auth.user ? req.auth.user : null;
|
||||
const login = logins && loginName ? logins.find(x => x.login == loginName) : null;
|
||||
const permissions = login ? login.permissions : process.env.PERMISSIONS;
|
||||
const authProvider = getAuthProviderFromReq(req);
|
||||
const login = authProvider.getCurrentLogin(req);
|
||||
const permissions = authProvider.getCurrentPermissions(req);
|
||||
const isUserLoggedIn = authProvider.isUserLoggedIn(req);
|
||||
|
||||
const singleConid = authProvider.getSingleConnectionId(req);
|
||||
|
||||
const singleConnection = singleConid
|
||||
? await connections.getCore({ conid: singleConid })
|
||||
: connections.singleConnection;
|
||||
|
||||
let configurationError = null;
|
||||
if (process.env.STORAGE_DATABASE && process.env.BASIC_AUTH) {
|
||||
configurationError =
|
||||
'Basic authentization is not allowed, when using storage. Cannot use both STORAGE_DATABASE and BASIC_AUTH';
|
||||
}
|
||||
|
||||
const checkedLicense = await checkLicense();
|
||||
const isLicenseValid = checkedLicense?.status == 'ok';
|
||||
|
||||
return {
|
||||
runAsPortal: !!connections.portalConnections,
|
||||
singleDbConnection: connections.singleDbConnection,
|
||||
singleConnection: connections.singleConnection,
|
||||
singleConnection: singleConnection,
|
||||
isUserLoggedIn,
|
||||
// hideAppEditor: !!process.env.HIDE_APP_EDITOR,
|
||||
allowShellConnection: platformInfo.allowShellConnection,
|
||||
allowShellScripting: platformInfo.allowShellScripting,
|
||||
isDocker: platformInfo.isDocker,
|
||||
isElectron: platformInfo.isElectron,
|
||||
isLicenseValid,
|
||||
checkedLicense,
|
||||
configurationError,
|
||||
logoutUrl: await authProvider.getLogoutUrl(),
|
||||
permissions,
|
||||
login,
|
||||
oauth: process.env.OAUTH_AUTH,
|
||||
oauthClient: process.env.OAUTH_CLIENT_ID,
|
||||
oauthScope: process.env.OAUTH_SCOPE,
|
||||
oauthLogout: process.env.OAUTH_LOGOUT,
|
||||
isLoginForm: !!process.env.AD_URL || (!!logins && !process.env.BASIC_AUTH),
|
||||
// ...additionalConfigProps,
|
||||
isBasicAuth: !!process.env.BASIC_AUTH,
|
||||
isAdminLoginForm: !!(
|
||||
process.env.STORAGE_DATABASE &&
|
||||
process.env.ADMIN_PASSWORD &&
|
||||
!process.env.BASIC_AUTH &&
|
||||
platformInfo.checkedLicense?.type == 'premium'
|
||||
),
|
||||
storageDatabase: process.env.STORAGE_DATABASE,
|
||||
logsFilePath: getLogsFilePath(),
|
||||
connectionsFilePath: path.join(datadir(), 'connections.jsonl'),
|
||||
@@ -98,12 +124,38 @@ module.exports = {
|
||||
async loadSettings() {
|
||||
try {
|
||||
const settingsText = await fs.readFile(path.join(datadir(), 'settings.json'), { encoding: 'utf-8' });
|
||||
return this.fillMissingSettings(JSON.parse(settingsText));
|
||||
return {
|
||||
...this.fillMissingSettings(JSON.parse(settingsText)),
|
||||
'other.licenseKey': platformInfo.isElectron ? await this.loadLicenseKey() : undefined,
|
||||
};
|
||||
} catch (err) {
|
||||
return this.fillMissingSettings({});
|
||||
}
|
||||
},
|
||||
|
||||
async loadLicenseKey() {
|
||||
try {
|
||||
const licenseKey = await fs.readFile(path.join(datadir(), 'license.key'), { encoding: 'utf-8' });
|
||||
return licenseKey;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
saveLicenseKey_meta: true,
|
||||
async saveLicenseKey({ licenseKey }) {
|
||||
try {
|
||||
if (process.env.STORAGE_DATABASE) {
|
||||
await storageWriteConfig('license', { licenseKey });
|
||||
} else {
|
||||
await fs.writeFile(path.join(datadir(), 'license.key'), licenseKey);
|
||||
}
|
||||
socket.emitChanged(`config-changed`);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
updateSettings_meta: true,
|
||||
async updateSettings(values, req) {
|
||||
if (!hasPermission(`settings/change`, req)) return false;
|
||||
@@ -113,10 +165,16 @@ module.exports = {
|
||||
try {
|
||||
const updated = {
|
||||
...currentValue,
|
||||
...values,
|
||||
..._.omit(values, ['other.licenseKey']),
|
||||
};
|
||||
await fs.writeFile(path.join(datadir(), 'settings.json'), JSON.stringify(updated, undefined, 2));
|
||||
// this.settingsValue = updated;
|
||||
|
||||
if (currentValue['other.licenseKey'] != values['other.licenseKey']) {
|
||||
await this.saveLicenseKey({ licenseKey: values['other.licenseKey'] });
|
||||
socket.emitChanged(`config-changed`);
|
||||
}
|
||||
|
||||
socket.emitChanged(`settings-changed`);
|
||||
return updated;
|
||||
} catch (err) {
|
||||
@@ -131,4 +189,10 @@ module.exports = {
|
||||
const resp = await axios.default.get('https://raw.githubusercontent.com/dbgate/dbgate/master/CHANGELOG.md');
|
||||
return resp.data;
|
||||
},
|
||||
|
||||
checkLicense_meta: true,
|
||||
async checkLicense({ licenseKey }) {
|
||||
const resp = await checkLicenseKey(licenseKey);
|
||||
return resp;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -16,6 +16,8 @@ const { safeJsonParse, getLogger } = require('dbgate-tools');
|
||||
const platformInfo = require('../utility/platformInfo');
|
||||
const { connectionHasPermission, testConnectionPermission } = require('../utility/hasPermission');
|
||||
const pipeForkLogs = require('../utility/pipeForkLogs');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const { getAuthProviderById } = require('../auth/authProvider');
|
||||
|
||||
const logger = getLogger('connections');
|
||||
|
||||
@@ -70,6 +72,8 @@ function getPortalCollections() {
|
||||
displayName: process.env[`LABEL_${id}`],
|
||||
isReadOnly: process.env[`READONLY_${id}`],
|
||||
databases: process.env[`DBCONFIG_${id}`] ? safeJsonParse(process.env[`DBCONFIG_${id}`]) : null,
|
||||
allowedDatabases: process.env[`ALLOWED_DATABASES_${id}`]?.replace(/\|/g, '\n'),
|
||||
allowedDatabasesRegex: process.env[`ALLOWED_DATABASES_REGEX_${id}`],
|
||||
parent: process.env[`PARENT_${id}`] || undefined,
|
||||
|
||||
// SSH tunnel
|
||||
@@ -201,7 +205,7 @@ module.exports = {
|
||||
async list(_params, req) {
|
||||
const storage = require('./storage');
|
||||
|
||||
const storageConnections = await storage.connections();
|
||||
const storageConnections = await storage.connections(req);
|
||||
if (storageConnections) {
|
||||
return storageConnections;
|
||||
}
|
||||
@@ -242,14 +246,16 @@ module.exports = {
|
||||
},
|
||||
|
||||
saveVolatile_meta: true,
|
||||
async saveVolatile({ conid, user, password, test }) {
|
||||
async saveVolatile({ conid, user = undefined, password = undefined, accessToken = undefined, test = false }) {
|
||||
const old = await this.getCore({ conid });
|
||||
const res = {
|
||||
...old,
|
||||
_id: crypto.randomUUID(),
|
||||
password,
|
||||
accessToken,
|
||||
passwordMode: undefined,
|
||||
unsaved: true,
|
||||
useRedirectDbLogin: false,
|
||||
};
|
||||
if (old.passwordMode == 'askUser') {
|
||||
res.user = user;
|
||||
@@ -345,7 +351,7 @@ module.exports = {
|
||||
|
||||
const storage = require('./storage');
|
||||
|
||||
const storageConnection = await storage.getConnection({conid});
|
||||
const storageConnection = await storage.getConnection({ conid });
|
||||
if (storageConnection) {
|
||||
return storageConnection;
|
||||
}
|
||||
@@ -379,4 +385,76 @@ module.exports = {
|
||||
});
|
||||
return res;
|
||||
},
|
||||
|
||||
dblogin_meta: {
|
||||
raw: true,
|
||||
method: 'get',
|
||||
},
|
||||
async dblogin(req, res) {
|
||||
const { conid, state, redirectUri } = req.query;
|
||||
const connection = await this.getCore({ conid });
|
||||
const driver = requireEngineDriver(connection);
|
||||
const authUrl = await driver.getRedirectAuthUrl(connection, { redirectUri, state });
|
||||
res.redirect(authUrl);
|
||||
},
|
||||
|
||||
dbloginToken_meta: true,
|
||||
async dbloginToken({ code, conid, strmid, redirectUri, sid }) {
|
||||
try {
|
||||
const connection = await this.getCore({ conid });
|
||||
const driver = requireEngineDriver(connection);
|
||||
const accessToken = await driver.getAuthTokenFromCode(connection, { sid, code, redirectUri });
|
||||
const volatile = await this.saveVolatile({ conid, accessToken });
|
||||
// console.log('******************************** WE HAVE ACCESS TOKEN', accessToken);
|
||||
socket.emit('got-volatile-token', { strmid, savedConId: conid, volatileConId: volatile._id });
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error getting DB token');
|
||||
return { error: err.message };
|
||||
}
|
||||
},
|
||||
|
||||
dbloginAuthToken_meta: true,
|
||||
async dbloginAuthToken({ amoid, code, conid, redirectUri, sid }) {
|
||||
try {
|
||||
const connection = await this.getCore({ conid });
|
||||
const driver = requireEngineDriver(connection);
|
||||
const accessToken = await driver.getAuthTokenFromCode(connection, { code, redirectUri, sid });
|
||||
const volatile = await this.saveVolatile({ conid, accessToken });
|
||||
const authProvider = getAuthProviderById(amoid);
|
||||
const resp = await authProvider.login(null, null, { conid: volatile._id });
|
||||
return resp;
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'Error getting DB token');
|
||||
return { error: err.message };
|
||||
}
|
||||
},
|
||||
|
||||
dbloginAuth_meta: true,
|
||||
async dbloginAuth({ amoid, conid, user, password }) {
|
||||
if (user || password) {
|
||||
const saveResp = await this.saveVolatile({ conid, user, password, test: true });
|
||||
if (saveResp.msgtype == 'connected') {
|
||||
const loginResp = await getAuthProviderById(amoid).login(user, password, { conid: saveResp._id });
|
||||
return loginResp;
|
||||
}
|
||||
return saveResp;
|
||||
}
|
||||
|
||||
// user and password is stored in connection, volatile connection is not needed
|
||||
const loginResp = await getAuthProviderById(amoid).login(null, null, { conid });
|
||||
return loginResp;
|
||||
},
|
||||
|
||||
volatileDbloginFromAuth_meta: true,
|
||||
async volatileDbloginFromAuth({ conid }, req) {
|
||||
const connection = await this.getCore({ conid });
|
||||
const driver = requireEngineDriver(connection);
|
||||
const accessToken = await driver.getAccessTokenFromAuth(connection, req);
|
||||
if (accessToken) {
|
||||
const volatile = await this.saveVolatile({ conid, accessToken });
|
||||
return volatile;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -89,6 +89,9 @@ module.exports = {
|
||||
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
|
||||
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
|
||||
}
|
||||
if (connection.useRedirectDbLogin) {
|
||||
throw new MissingCredentialsError({ conid, redirectToDbLogin: true });
|
||||
}
|
||||
const subprocess = fork(
|
||||
global['API_PACKAGE'] || process.argv[1],
|
||||
[
|
||||
|
||||
@@ -56,7 +56,10 @@ module.exports = {
|
||||
if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
|
||||
throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
|
||||
}
|
||||
const subprocess = fork(
|
||||
if (connection.useRedirectDbLogin) {
|
||||
throw new MissingCredentialsError({ conid, redirectToDbLogin: true });
|
||||
}
|
||||
const subprocess = fork(
|
||||
global['API_PACKAGE'] || process.argv[1],
|
||||
[
|
||||
'--is-forked-api',
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
module.exports = {
|
||||
connections_meta: true,
|
||||
async connections() {
|
||||
async connections(req) {
|
||||
return null;
|
||||
},
|
||||
|
||||
getConnection_meta: true,
|
||||
async getConnection({ conid }) {
|
||||
return null;
|
||||
},
|
||||
|
||||
async loadSuperadminPermissions() {
|
||||
return [];
|
||||
},
|
||||
|
||||
getConnectionsForLoginPage_meta: true,
|
||||
async getConnectionsForLoginPage() {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -32,9 +32,9 @@ const onFinished = require('on-finished');
|
||||
const { rundir } = require('./utility/directories');
|
||||
const platformInfo = require('./utility/platformInfo');
|
||||
const getExpressPath = require('./utility/getExpressPath');
|
||||
const { getLogins } = require('./utility/hasPermission');
|
||||
const _ = require('lodash');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const { getDefaultAuthProvider } = require('./auth/authProvider');
|
||||
|
||||
const logger = getLogger('main');
|
||||
|
||||
@@ -45,11 +45,23 @@ function start() {
|
||||
|
||||
const server = http.createServer(app);
|
||||
|
||||
const logins = getLogins();
|
||||
if (logins && process.env.BASIC_AUTH) {
|
||||
if (process.env.BASIC_AUTH && !process.env.STORAGE_DATABASE) {
|
||||
async function authorizer(username, password, cb) {
|
||||
try {
|
||||
const resp = await getDefaultAuthProvider().login(username, password);
|
||||
if (resp.accessToken) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(null, false);
|
||||
}
|
||||
} catch (err) {
|
||||
cb(err, false);
|
||||
}
|
||||
}
|
||||
app.use(
|
||||
basicAuth({
|
||||
users: _.fromPairs(logins.filter(x => x.password).map(x => [x.login, x.password])),
|
||||
authorizer,
|
||||
authorizeAsync: true,
|
||||
challenge: true,
|
||||
realm: 'DbGate Web App',
|
||||
})
|
||||
@@ -73,9 +85,7 @@ function start() {
|
||||
});
|
||||
}
|
||||
|
||||
if (auth.shouldAuthorizeApi()) {
|
||||
app.use(auth.authMiddleware);
|
||||
}
|
||||
app.use(auth.authMiddleware);
|
||||
|
||||
app.get(getExpressPath('/stream'), async function (req, res) {
|
||||
const strmid = req.query.strmid;
|
||||
|
||||
@@ -16,7 +16,18 @@ let afterConnectCallbacks = [];
|
||||
async function handleRefresh() {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
const databases = await driver.listDatabases(systemConnection);
|
||||
let databases = await driver.listDatabases(systemConnection);
|
||||
if (storedConnection?.allowedDatabases?.trim()) {
|
||||
const allowedDatabaseList = storedConnection.allowedDatabases
|
||||
.split('\n')
|
||||
.map(x => x.trim().toLowerCase())
|
||||
.filter(x => x);
|
||||
databases = databases.filter(x => allowedDatabaseList.includes(x.name.toLocaleLowerCase()));
|
||||
}
|
||||
if (storedConnection?.allowedDatabasesRegex?.trim()) {
|
||||
const regex = new RegExp(storedConnection.allowedDatabasesRegex, 'i');
|
||||
databases = databases.filter(x => regex.test(x.name));
|
||||
}
|
||||
setStatusName('ok');
|
||||
const databasesString = stableStringify(databases);
|
||||
if (lastDatabases != databasesString) {
|
||||
|
||||
@@ -3,6 +3,7 @@ const fs = require('fs');
|
||||
const { pluginsdir, packagedPluginsDir, getPluginBackendPath } = require('../utility/directories');
|
||||
const nativeModules = require('../nativeModules');
|
||||
const platformInfo = require('../utility/platformInfo');
|
||||
const authProxy = require('../utility/authProxy');
|
||||
const { getLogger } = require('dbgate-tools');
|
||||
const logger = getLogger('requirePlugin');
|
||||
|
||||
@@ -11,6 +12,8 @@ const loadedPlugins = {};
|
||||
const dbgateEnv = {
|
||||
dbgateApi: null,
|
||||
nativeModules,
|
||||
platformInfo,
|
||||
authProxy,
|
||||
};
|
||||
function requirePlugin(packageName, requiredPlugin = null) {
|
||||
if (!packageName) throw new Error('Missing packageName in plugin');
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
function isAuthProxySupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
async function authProxyGetRedirectUrl(options) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function authProxyGetTokenFromCode(options) {
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isAuthProxySupported,
|
||||
authProxyGetRedirectUrl,
|
||||
authProxyGetTokenFromCode,
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
function checkLicenseWeb() {
|
||||
return {
|
||||
status: 'ok',
|
||||
type: 'community',
|
||||
};
|
||||
}
|
||||
|
||||
function checkLicenseApp() {
|
||||
return {
|
||||
status: 'ok',
|
||||
type: 'community',
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
checkLicenseWeb,
|
||||
checkLicenseApp,
|
||||
};
|
||||
@@ -1,72 +1,81 @@
|
||||
const { compilePermissions, testPermission } = require('dbgate-tools');
|
||||
const _ = require('lodash');
|
||||
const { getAuthProviderFromReq } = require('../auth/authProvider');
|
||||
|
||||
const userPermissions = {};
|
||||
const cachedPermissions = {};
|
||||
|
||||
function hasPermission(tested, req) {
|
||||
if (!req) {
|
||||
// request object not available, allow all
|
||||
return true;
|
||||
}
|
||||
const { user } = (req && req.auth) || {};
|
||||
const { login } = (process.env.OAUTH_PERMISSIONS && req && req.user) || {};
|
||||
const key = user || login || '';
|
||||
const logins = getLogins();
|
||||
|
||||
if (!userPermissions[key]) {
|
||||
if (logins) {
|
||||
const login = logins.find(x => x.login == user);
|
||||
userPermissions[key] = compilePermissions(login ? login.permissions : null);
|
||||
} else {
|
||||
userPermissions[key] = compilePermissions(process.env.PERMISSIONS);
|
||||
}
|
||||
const permissions = getAuthProviderFromReq(req).getCurrentPermissions(req);
|
||||
|
||||
if (!cachedPermissions[permissions]) {
|
||||
cachedPermissions[permissions] = compilePermissions(permissions);
|
||||
}
|
||||
return testPermission(tested, userPermissions[key]);
|
||||
|
||||
return testPermission(tested, cachedPermissions[permissions]);
|
||||
|
||||
// const { user } = (req && req.auth) || {};
|
||||
// const { login } = (process.env.OAUTH_PERMISSIONS && req && req.user) || {};
|
||||
// const key = user || login || '';
|
||||
// const logins = getLogins();
|
||||
|
||||
// if (!userPermissions[key]) {
|
||||
// if (logins) {
|
||||
// const login = logins.find(x => x.login == user);
|
||||
// userPermissions[key] = compilePermissions(login ? login.permissions : null);
|
||||
// } else {
|
||||
// userPermissions[key] = compilePermissions(process.env.PERMISSIONS);
|
||||
// }
|
||||
// }
|
||||
// return testPermission(tested, userPermissions[key]);
|
||||
}
|
||||
|
||||
let loginsCache = null;
|
||||
let loginsLoaded = false;
|
||||
// let loginsCache = null;
|
||||
// let loginsLoaded = false;
|
||||
|
||||
function getLogins() {
|
||||
if (loginsLoaded) {
|
||||
return loginsCache;
|
||||
}
|
||||
// function getLogins() {
|
||||
// if (loginsLoaded) {
|
||||
// return loginsCache;
|
||||
// }
|
||||
|
||||
const res = [];
|
||||
if (process.env.LOGIN && process.env.PASSWORD) {
|
||||
res.push({
|
||||
login: process.env.LOGIN,
|
||||
password: process.env.PASSWORD,
|
||||
permissions: process.env.PERMISSIONS,
|
||||
});
|
||||
}
|
||||
if (process.env.LOGINS) {
|
||||
const logins = _.compact(process.env.LOGINS.split(',').map(x => x.trim()));
|
||||
for (const login of logins) {
|
||||
const password = process.env[`LOGIN_PASSWORD_${login}`];
|
||||
const permissions = process.env[`LOGIN_PERMISSIONS_${login}`];
|
||||
if (password) {
|
||||
res.push({
|
||||
login,
|
||||
password,
|
||||
permissions,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (process.env.OAUTH_PERMISSIONS) {
|
||||
const login_permission_keys = Object.keys(process.env).filter((key) => _.startsWith(key, 'LOGIN_PERMISSIONS_'))
|
||||
for (const permissions_key of login_permission_keys) {
|
||||
const login = permissions_key.replace('LOGIN_PERMISSIONS_', '');
|
||||
const permissions = process.env[permissions_key];
|
||||
userPermissions[login] = compilePermissions(permissions);
|
||||
}
|
||||
}
|
||||
// const res = [];
|
||||
// if (process.env.LOGIN && process.env.PASSWORD) {
|
||||
// res.push({
|
||||
// login: process.env.LOGIN,
|
||||
// password: process.env.PASSWORD,
|
||||
// permissions: process.env.PERMISSIONS,
|
||||
// });
|
||||
// }
|
||||
// if (process.env.LOGINS) {
|
||||
// const logins = _.compact(process.env.LOGINS.split(',').map(x => x.trim()));
|
||||
// for (const login of logins) {
|
||||
// const password = process.env[`LOGIN_PASSWORD_${login}`];
|
||||
// const permissions = process.env[`LOGIN_PERMISSIONS_${login}`];
|
||||
// if (password) {
|
||||
// res.push({
|
||||
// login,
|
||||
// password,
|
||||
// permissions,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// } else if (process.env.OAUTH_PERMISSIONS) {
|
||||
// const login_permission_keys = Object.keys(process.env).filter(key => _.startsWith(key, 'LOGIN_PERMISSIONS_'));
|
||||
// for (const permissions_key of login_permission_keys) {
|
||||
// const login = permissions_key.replace('LOGIN_PERMISSIONS_', '');
|
||||
// const permissions = process.env[permissions_key];
|
||||
// userPermissions[login] = compilePermissions(permissions);
|
||||
// }
|
||||
// }
|
||||
|
||||
loginsCache = res.length > 0 ? res : null;
|
||||
loginsLoaded = true;
|
||||
return loginsCache;
|
||||
}
|
||||
// loginsCache = res.length > 0 ? res : null;
|
||||
// loginsLoaded = true;
|
||||
// return loginsCache;
|
||||
// }
|
||||
|
||||
function connectionHasPermission(connection, req) {
|
||||
if (!connection) {
|
||||
@@ -87,7 +96,6 @@ function testConnectionPermission(connection, req) {
|
||||
|
||||
module.exports = {
|
||||
hasPermission,
|
||||
getLogins,
|
||||
connectionHasPermission,
|
||||
testConnectionPermission,
|
||||
};
|
||||
|
||||
@@ -31,6 +31,9 @@ module.exports = {
|
||||
electronSender.send(message, data == null ? null : data);
|
||||
}
|
||||
for (const strmid in sseResponses) {
|
||||
if (data?.strmid && data?.strmid != strmid) {
|
||||
continue;
|
||||
}
|
||||
let skipThisStream = false;
|
||||
if (sseResponses[strmid].filter) {
|
||||
for (const key in sseResponses[strmid].filter) {
|
||||
@@ -47,7 +50,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
sseResponses[strmid].response?.write(
|
||||
`event: ${message}\ndata: ${stableStringify(data == null ? null : data)}\n\n`
|
||||
`event: ${message}\ndata: ${stableStringify(data == null ? null : _.omit(data, ['strmid']))}\n\n`
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -67,7 +67,7 @@ module.exports = function useController(app, electron, route, controller) {
|
||||
}
|
||||
|
||||
if (raw) {
|
||||
router[method](routeAction, controller[key]);
|
||||
router[method](routeAction, (req, res) => controller[key](req, res));
|
||||
} else {
|
||||
router[method](routeAction, async (req, res) => {
|
||||
// if (controller._init && !controller._init_called) {
|
||||
|
||||
@@ -47,6 +47,8 @@ var config = {
|
||||
],
|
||||
externals: {
|
||||
'better-sqlite3': 'commonjs better-sqlite3',
|
||||
'oracledb': 'commonjs oracledb',
|
||||
'msnodesqlv8': 'commonjs msnodesqlv8',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -530,3 +530,7 @@ export function changeSetContainsChanges(changeSet: ChangeSet) {
|
||||
changeSet.dataUpdateCommands?.length > 0
|
||||
);
|
||||
}
|
||||
|
||||
export function changeSetChangedCount(changeSet: ChangeSet) {
|
||||
return changeSet.deletes.length + changeSet.updates.length + changeSet.inserts.length;
|
||||
}
|
||||
|
||||
@@ -214,14 +214,14 @@ export abstract class GridDisplay {
|
||||
}
|
||||
|
||||
if (this.baseTableOrView && this.config.multiColumnFilter) {
|
||||
try {
|
||||
const condition = parseFilter(this.config.multiColumnFilter, 'string');
|
||||
if (condition) {
|
||||
const orCondition: CompoudCondition = {
|
||||
conditionType: 'or',
|
||||
conditions: [],
|
||||
};
|
||||
for (const column of this.baseTableOrView.columns) {
|
||||
const orCondition: CompoudCondition = {
|
||||
conditionType: 'or',
|
||||
conditions: [],
|
||||
};
|
||||
for (const column of this.baseTableOrView.columns) {
|
||||
try {
|
||||
const condition = parseFilter(this.config.multiColumnFilter, getFilterType(column.dataType));
|
||||
if (condition) {
|
||||
orCondition.conditions.push(
|
||||
_.cloneDeepWith(condition, (expr: Expression) => {
|
||||
if (expr.exprType == 'placeholder') {
|
||||
@@ -230,12 +230,13 @@ export abstract class GridDisplay {
|
||||
})
|
||||
);
|
||||
}
|
||||
if (orCondition.conditions.length > 0) {
|
||||
conditions.push(orCondition);
|
||||
}
|
||||
} catch (err) {
|
||||
// skip for this column
|
||||
continue;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(err.message);
|
||||
}
|
||||
if (orCondition.conditions.length > 0) {
|
||||
conditions.push(orCondition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,6 +483,7 @@ export abstract class GridDisplay {
|
||||
this.setConfig(cfg => ({
|
||||
...cfg,
|
||||
filters: {},
|
||||
multiColumnFilter: null,
|
||||
}));
|
||||
this.reload();
|
||||
}
|
||||
|
||||
@@ -28,6 +28,12 @@ export function dumpSqlExpression(dmp: SqlDumper, expr: Expression) {
|
||||
dmp.put('%s', expr.sql);
|
||||
break;
|
||||
|
||||
case 'unaryRaw':
|
||||
if (expr.beforeSql) dmp.putRaw(expr.beforeSql);
|
||||
dumpSqlExpression(dmp, expr.expr);
|
||||
if (expr.afterSql) dmp.putRaw(expr.afterSql);
|
||||
break;
|
||||
|
||||
case 'call':
|
||||
dmp.put('%s(', expr.func);
|
||||
if (expr.argsPrefix) dmp.put('%s ', expr.argsPrefix);
|
||||
@@ -36,7 +42,7 @@ export function dumpSqlExpression(dmp: SqlDumper, expr: Expression) {
|
||||
break;
|
||||
|
||||
case 'methodCall':
|
||||
dumpSqlExpression(dmp, expr.thisObject)
|
||||
dumpSqlExpression(dmp, expr.thisObject);
|
||||
dmp.put('.%s(', expr.method);
|
||||
dmp.putCollection(',', expr.args, x => dumpSqlExpression(dmp, x));
|
||||
dmp.put(')');
|
||||
|
||||
@@ -176,6 +176,13 @@ export interface RawExpression {
|
||||
sql: string;
|
||||
}
|
||||
|
||||
export interface UnaryRawExpression {
|
||||
exprType: 'unaryRaw';
|
||||
expr: Expression;
|
||||
beforeSql?: string;
|
||||
afterSql?: string;
|
||||
}
|
||||
|
||||
export interface CallExpression {
|
||||
exprType: 'call';
|
||||
func: string;
|
||||
@@ -206,6 +213,7 @@ export type Expression =
|
||||
| ValueExpression
|
||||
| PlaceholderExpression
|
||||
| RawExpression
|
||||
| UnaryRawExpression
|
||||
| CallExpression
|
||||
| MethodCallExpression
|
||||
| TranformExpression
|
||||
|
||||
@@ -146,4 +146,5 @@ export const driverBase = {
|
||||
},
|
||||
showConnectionField: (field, values) => false,
|
||||
showConnectionTab: field => true,
|
||||
getAccessTokenFromAuth: async (connection, req) => null,
|
||||
};
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ function getConnectionLabelCore(connection, { allowExplicitDatabase = true } = {
|
||||
return '';
|
||||
}
|
||||
|
||||
export default function getConnectionLabel(connection, { allowExplicitDatabase = true, showUnsaved = false } = {}) {
|
||||
export function getConnectionLabel(connection, { allowExplicitDatabase = true, showUnsaved = false } = {}) {
|
||||
const res = getConnectionLabelCore(connection, { allowExplicitDatabase });
|
||||
|
||||
if (res && showUnsaved && connection?.unsaved) {
|
||||
@@ -20,3 +20,4 @@ export * from './computeDiffRows';
|
||||
export * from './preloadedRowsTools';
|
||||
export * from './ScriptWriter';
|
||||
export * from './getLogger';
|
||||
export * from './getConnectionLabel';
|
||||
|
||||
@@ -73,3 +73,44 @@ export function testPermission(tested: string, permissions: CompiledPermissions)
|
||||
|
||||
return allow;
|
||||
}
|
||||
|
||||
export function testSubPermission(
|
||||
tested: string,
|
||||
permissions: string[],
|
||||
allowSamePermission = true
|
||||
): true | false | null {
|
||||
let result = null;
|
||||
for (const permWithSign of permissions) {
|
||||
const perm = permWithSign.startsWith('~') ? permWithSign.substring(1) : permWithSign;
|
||||
const deny = permWithSign.startsWith('~');
|
||||
|
||||
if (perm.endsWith('*')) {
|
||||
const prefix = perm.substring(0, perm.length - 1);
|
||||
if (tested.startsWith(prefix)) {
|
||||
result = !deny;
|
||||
}
|
||||
} else {
|
||||
if (allowSamePermission && tested == perm) {
|
||||
result = !deny;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getPredefinedPermissions(predefinedRoleName: string) {
|
||||
switch (predefinedRoleName) {
|
||||
case 'superadmin':
|
||||
return ['*', '~widgets/*', 'widgets/admin', 'widgets/database', '~all-connections'];
|
||||
case 'logged-user':
|
||||
return ['*', '~widgets/admin', '~admin/*', '~internal-storage', '~all-connections'];
|
||||
case 'anonymous-user':
|
||||
return ['*', '~widgets/admin', '~admin/*', '~internal-storage', '~all-connections'];
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function sortPermissionsFromTheSameLevel(permissions: string[]) {
|
||||
return [...permissions.filter(x => x.startsWith('~')), ...permissions.filter(x => !x.startsWith('~'))];
|
||||
}
|
||||
|
||||
Vendored
+10
-1
@@ -90,7 +90,13 @@ export interface EngineDriver {
|
||||
profilerChartMeasures?: { label: string; field: string }[];
|
||||
isElectronOnly?: boolean;
|
||||
supportedCreateDatabase?: boolean;
|
||||
showConnectionField?: (field: string, values: any) => boolean;
|
||||
showConnectionField?: (
|
||||
field: string,
|
||||
values: any,
|
||||
{
|
||||
config: {},
|
||||
}
|
||||
) => boolean;
|
||||
showConnectionTab?: (tab: 'ssl' | 'sshTunnel', values: any) => boolean;
|
||||
beforeConnectionSave?: (values: any) => any;
|
||||
databaseUrlPlaceholder?: string;
|
||||
@@ -143,6 +149,9 @@ export interface EngineDriver {
|
||||
summaryCommand(pool, command, row): Promise<void>;
|
||||
startProfiler(pool, options): Promise<any>;
|
||||
stopProfiler(pool, profiler): Promise<void>;
|
||||
getRedirectAuthUrl(connection, options): Promise<string>;
|
||||
getAuthTokenFromCode(connection, options): Promise<string>;
|
||||
getAccessTokenFromAuth(connection, req): Promise<string | null>;
|
||||
|
||||
analyserClass?: any;
|
||||
dumperClass?: any;
|
||||
|
||||
@@ -121,6 +121,15 @@ body {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.largeFormMarker textarea {
|
||||
width: 100%;
|
||||
padding: 10px 10px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--theme-border);
|
||||
}
|
||||
|
||||
body *::-webkit-scrollbar {
|
||||
height: 0.8em;
|
||||
width: 0.8em;
|
||||
@@ -169,6 +178,10 @@ textarea {
|
||||
border: 1px solid var(--theme-border);
|
||||
}
|
||||
|
||||
textarea[disabled] {
|
||||
background-color: var(--theme-bg-1);
|
||||
}
|
||||
|
||||
.ace_gutter-cell.ace-gutter-sql-run {
|
||||
background-repeat: no-repeat;
|
||||
background-position: 2px center;
|
||||
|
||||
@@ -14,13 +14,15 @@
|
||||
// import { shouldWaitForElectronInitialize } from './utility/getElectron';
|
||||
import { subscribeConnectionPingers } from './utility/connectionsPinger';
|
||||
import { subscribePermissionCompiler } from './utility/hasPermission';
|
||||
import { apiCall } from './utility/api';
|
||||
import { apiCall, installNewVolatileConnectionListener } from './utility/api';
|
||||
import { getConfig, getSettings, getUsedApps } from './utility/metadataLoaders';
|
||||
import AppTitleProvider from './utility/AppTitleProvider.svelte';
|
||||
import getElectron from './utility/getElectron';
|
||||
import AppStartInfo from './widgets/AppStartInfo.svelte';
|
||||
import SettingsListener from './utility/SettingsListener.svelte';
|
||||
import { handleAuthOnStartup, handleOauthCallback } from './clientAuth';
|
||||
import { handleAuthOnStartup } from './clientAuth';
|
||||
|
||||
export let isAdminPage = false;
|
||||
|
||||
let loadedApi = false;
|
||||
let loadedPlugins = false;
|
||||
@@ -35,19 +37,22 @@
|
||||
// console.log('************** LOADING API');
|
||||
|
||||
const config = await getConfig();
|
||||
await handleAuthOnStartup(config);
|
||||
await handleAuthOnStartup(config, isAdminPage);
|
||||
|
||||
const connections = await apiCall('connections/list');
|
||||
const settings = await getSettings();
|
||||
const apps = await getUsedApps();
|
||||
loadedApi = settings && connections && config && apps;
|
||||
const loadedApiValue = !!(settings && connections && config && apps);
|
||||
|
||||
if (loadedApi) {
|
||||
if (loadedApiValue) {
|
||||
subscribeApiDependendStores();
|
||||
subscribeConnectionPingers();
|
||||
subscribePermissionCompiler();
|
||||
installNewVolatileConnectionListener();
|
||||
}
|
||||
|
||||
loadedApi = loadedApiValue;
|
||||
|
||||
if (!loadedApi) {
|
||||
console.log('API not initialized correctly, trying again in 1s');
|
||||
setTimeout(loadApi, 1000);
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { useConfig } from './utility/metadataLoaders';
|
||||
import ErrorInfo from './elements/ErrorInfo.svelte';
|
||||
import Link from './elements/Link.svelte';
|
||||
import { internalRedirectTo } from './clientAuth';
|
||||
import TextAreaField from './forms/TextAreaField.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import FormProviderCore from './forms/FormProviderCore.svelte';
|
||||
import FormTextAreaField from './forms/FormTextAreaField.svelte';
|
||||
import FormSubmit from './forms/FormSubmit.svelte';
|
||||
import { apiCall } from './utility/api';
|
||||
|
||||
const config = useConfig();
|
||||
const values = writable({ amoid: null, databaseServer: null });
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
const error = params.get('error');
|
||||
|
||||
onMount(() => {
|
||||
const removed = document.getElementById('starting_dbgate_zero');
|
||||
if (removed) removed.remove();
|
||||
});
|
||||
</script>
|
||||
|
||||
<FormProviderCore {values}>
|
||||
<div class="root theme-light theme-type-light">
|
||||
<div class="text">DbGate</div>
|
||||
<div class="wrap">
|
||||
<div class="logo">
|
||||
<img class="img" src="logo192.png" />
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="heading">License</div>
|
||||
<FormTextAreaField label="License key" name="licenseKey" rows={5} />
|
||||
|
||||
<div class="submit">
|
||||
<FormSubmit
|
||||
value="Save license"
|
||||
on:click={async e => {
|
||||
const { licenseKey } = e.detail;
|
||||
await apiCall('config/save-license-key', { licenseKey });
|
||||
internalRedirectTo('/');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FormProviderCore>
|
||||
|
||||
<style>
|
||||
.logo {
|
||||
display: flex;
|
||||
margin-bottom: 1rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.img {
|
||||
width: 80px;
|
||||
}
|
||||
.text {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
font-size: 30pt;
|
||||
font-family: monospace;
|
||||
color: var(--theme-bg-2);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.root {
|
||||
color: var(--theme-font-1);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: var(--theme-bg-1);
|
||||
align-items: baseline;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.box {
|
||||
width: 600px;
|
||||
max-width: 80vw;
|
||||
/* max-width: 600px;
|
||||
width: 40vw; */
|
||||
border: 1px solid var(--theme-border);
|
||||
border-radius: 4px;
|
||||
background-color: var(--theme-bg-0);
|
||||
}
|
||||
|
||||
.wrap {
|
||||
margin-top: 20vh;
|
||||
}
|
||||
|
||||
.heading {
|
||||
text-align: center;
|
||||
margin: 1em;
|
||||
font-size: xx-large;
|
||||
}
|
||||
|
||||
.submit {
|
||||
margin: var(--dim-large-form-margin);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.submit :global(input) {
|
||||
flex: 1;
|
||||
font-size: larger;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,97 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { useConfig } from './utility/metadataLoaders';
|
||||
import ErrorInfo from './elements/ErrorInfo.svelte';
|
||||
import Link from './elements/Link.svelte';
|
||||
import { internalRedirectTo } from './clientAuth';
|
||||
|
||||
const config = useConfig();
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
const error = params.get('error');
|
||||
|
||||
onMount(() => {
|
||||
const removed = document.getElementById('starting_dbgate_zero');
|
||||
if (removed) removed.remove();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="root theme-light theme-type-light">
|
||||
<div class="text">DbGate</div>
|
||||
<div class="wrap">
|
||||
<div class="logo">
|
||||
<img class="img" src="logo192.png" />
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="heading">Configuration error</div>
|
||||
{#if $config?.checkedLicense?.status == 'error'}
|
||||
<ErrorInfo
|
||||
message={`Invalid license. Please contact sales@dbgate.eu for more details. ${$config?.checkedLicense?.error}`}
|
||||
/>
|
||||
{:else if $config?.configurationError}
|
||||
<ErrorInfo message={$config?.configurationError} />
|
||||
{:else if error}
|
||||
<ErrorInfo message={error} />
|
||||
{:else}
|
||||
<ErrorInfo message="No error found, try to open app again" />
|
||||
<div class="m-2">
|
||||
<Link onClick={() => internalRedirectTo('/')}>Back to app</Link>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.logo {
|
||||
display: flex;
|
||||
margin-bottom: 1rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.img {
|
||||
width: 80px;
|
||||
}
|
||||
.text {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
font-size: 30pt;
|
||||
font-family: monospace;
|
||||
color: var(--theme-bg-2);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.root {
|
||||
color: var(--theme-font-1);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: var(--theme-bg-1);
|
||||
align-items: baseline;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.box {
|
||||
width: 600px;
|
||||
max-width: 80vw;
|
||||
/* max-width: 600px;
|
||||
width: 40vw; */
|
||||
border: 1px solid var(--theme-border);
|
||||
border-radius: 4px;
|
||||
background-color: var(--theme-bg-0);
|
||||
}
|
||||
|
||||
.wrap {
|
||||
margin-top: 20vh;
|
||||
}
|
||||
|
||||
.heading {
|
||||
text-align: center;
|
||||
margin: 1em;
|
||||
font-size: xx-large;
|
||||
}
|
||||
</style>
|
||||
@@ -1,17 +1,124 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { internalRedirectTo } from './clientAuth';
|
||||
import FormButton from './forms/FormButton.svelte';
|
||||
import FormPasswordField from './forms/FormPasswordField.svelte';
|
||||
import FormProvider from './forms/FormProvider.svelte';
|
||||
import FormSubmit from './forms/FormSubmit.svelte';
|
||||
import FormTextField from './forms/FormTextField.svelte';
|
||||
import { apiCall, enableApi } from './utility/api';
|
||||
import { apiCall, enableApi, strmid } from './utility/api';
|
||||
import { useConfig } from './utility/metadataLoaders';
|
||||
import ErrorInfo from './elements/ErrorInfo.svelte';
|
||||
import FormSelectField from './forms/FormSelectField.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import FormProviderCore from './forms/FormProviderCore.svelte';
|
||||
import { openWebLink } from './utility/exportFileTools';
|
||||
import FontIcon from './icons/FontIcon.svelte';
|
||||
import createRef from './utility/createRef';
|
||||
|
||||
export let isAdminPage;
|
||||
|
||||
const config = useConfig();
|
||||
|
||||
let availableConnections = null;
|
||||
let availableProviders = [];
|
||||
let isTesting = false;
|
||||
const testIdRef = createRef(0);
|
||||
let sqlConnectResult;
|
||||
|
||||
let serversLoadedForAmoId = null;
|
||||
|
||||
const values = writable({ amoid: null, databaseServer: null });
|
||||
|
||||
$: selectedConnection = availableConnections?.find(x => x.conid == $values.databaseServer);
|
||||
|
||||
$: selectedProvider = availableProviders?.find(x => x.amoid == $values.amoid);
|
||||
$: workflowType = selectedProvider?.workflowType ?? 'credentials';
|
||||
|
||||
async function loadAvailableServers(amoid) {
|
||||
if (amoid) {
|
||||
availableConnections = await apiCall('storage/get-connections-for-login-page', { amoid });
|
||||
if (availableConnections?.length > 0) {
|
||||
values.update(x => ({ ...x, databaseServer: availableConnections[0].conid }));
|
||||
}
|
||||
serversLoadedForAmoId = amoid;
|
||||
} else {
|
||||
availableConnections = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function processSingleProvider(provider) {
|
||||
if (provider.workflowType == 'redirect') {
|
||||
await processRedirectLogin(provider.amoid);
|
||||
}
|
||||
if (provider.workflowType == 'anonymous') {
|
||||
processCredentialsLogin(provider.amoid, {});
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAvailableAuthProviders() {
|
||||
const resp = await apiCall('auth/get-providers');
|
||||
availableProviders = resp.providers;
|
||||
values.update(x => ({ ...x, amoid: resp.default }));
|
||||
|
||||
if (availableProviders.length == 1) {
|
||||
processSingleProvider(availableProviders[0]);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const removed = document.getElementById('starting_dbgate_zero');
|
||||
if (removed) removed.remove();
|
||||
|
||||
if (!isAdminPage) {
|
||||
loadAvailableAuthProviders();
|
||||
}
|
||||
});
|
||||
|
||||
$: if ($values.amoid != serversLoadedForAmoId) {
|
||||
loadAvailableServers($values.amoid);
|
||||
}
|
||||
|
||||
async function processRedirectLogin(amoid) {
|
||||
const state = `dbg-oauth:${strmid}:${amoid}`;
|
||||
|
||||
sessionStorage.setItem('oauthState', state);
|
||||
console.log('Redirecting to OAUTH provider');
|
||||
|
||||
const resp = await apiCall('auth/redirect', {
|
||||
amoid: amoid,
|
||||
state,
|
||||
redirectUri: location.origin + location.pathname,
|
||||
});
|
||||
|
||||
const { uri } = resp;
|
||||
if (uri) {
|
||||
location.replace(uri);
|
||||
}
|
||||
}
|
||||
|
||||
async function processCredentialsLogin(amoid, detail) {
|
||||
const resp = await apiCall('auth/login', {
|
||||
amoid,
|
||||
isAdminPage,
|
||||
...detail,
|
||||
});
|
||||
if (resp.error) {
|
||||
internalRedirectTo(
|
||||
`/?page=not-logged&error=${encodeURIComponent(resp.error)}&is-admin=${isAdminPage ? 'true' : ''}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
const { accessToken } = resp;
|
||||
if (accessToken) {
|
||||
localStorage.setItem(isAdminPage ? 'adminAccessToken' : 'accessToken', accessToken);
|
||||
if (isAdminPage) {
|
||||
internalRedirectTo('/?page=admin');
|
||||
} else {
|
||||
internalRedirectTo('/');
|
||||
}
|
||||
return;
|
||||
}
|
||||
internalRedirectTo(`/?page=not-logged`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="root theme-light theme-type-light">
|
||||
@@ -22,31 +129,131 @@
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="heading">Log In</div>
|
||||
<FormProvider>
|
||||
<FormTextField label="Username" name="login" autocomplete="username" saveOnInput />
|
||||
<FormPasswordField label="Password" name="password" autocomplete="current-password" saveOnInput />
|
||||
<FormProviderCore {values}>
|
||||
{#if !isAdminPage && availableProviders?.length >= 2}
|
||||
<FormSelectField
|
||||
label="Authentization method"
|
||||
name="amoid"
|
||||
isNative
|
||||
options={availableProviders.map(mtd => ({ value: mtd.amoid, label: mtd.name }))}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if !isAdminPage && availableConnections && workflowType == 'database'}
|
||||
<FormSelectField
|
||||
label="Database server"
|
||||
name="databaseServer"
|
||||
isNative
|
||||
options={availableConnections.map(conn => ({ value: conn.conid, label: conn.label }))}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if selectedConnection}
|
||||
{#if selectedConnection.passwordMode == 'askUser'}
|
||||
<FormTextField label="Username" name="login" autocomplete="username" saveOnInput />
|
||||
{/if}
|
||||
{#if selectedConnection.passwordMode == 'askUser' || selectedConnection.passwordMode == 'askPassword'}
|
||||
<FormPasswordField label="Password" name="password" autocomplete="current-password" saveOnInput />
|
||||
{/if}
|
||||
{:else}
|
||||
{#if !isAdminPage && workflowType == 'credentials'}
|
||||
<FormTextField label="Username" name="login" autocomplete="username" saveOnInput />
|
||||
{/if}
|
||||
{#if workflowType == 'credentials'}
|
||||
<FormPasswordField label="Password" name="password" autocomplete="current-password" saveOnInput />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if isAdminPage && $config && !$config.isAdminLoginForm}
|
||||
<ErrorInfo message="Admin login is not configured. Please set ADMIN_PASSWORD environment variable" />
|
||||
{/if}
|
||||
|
||||
{#if isTesting}
|
||||
<div class="ml-5">
|
||||
<FontIcon icon="icon loading" /> Testing connection
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !isTesting && sqlConnectResult && sqlConnectResult.msgtype == 'error'}
|
||||
<div class="error-result ml-5">
|
||||
Connect failed: <FontIcon icon="img error" />
|
||||
{sqlConnectResult.error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="submit">
|
||||
<FormSubmit
|
||||
value="Log In"
|
||||
on:click={async e => {
|
||||
enableApi();
|
||||
const resp = await apiCall('auth/login', e.detail);
|
||||
if (resp.error) {
|
||||
internalRedirectTo(`/?page=not-logged&error=${encodeURIComponent(resp.error)}`);
|
||||
return;
|
||||
}
|
||||
const { accessToken } = resp;
|
||||
if (accessToken) {
|
||||
localStorage.setItem('accessToken', accessToken);
|
||||
internalRedirectTo('/');
|
||||
return;
|
||||
}
|
||||
internalRedirectTo(`/?page=not-logged`);
|
||||
}}
|
||||
/>
|
||||
{#if selectedConnection?.useRedirectDbLogin}
|
||||
<FormSubmit
|
||||
value="Open database login page"
|
||||
on:click={async e => {
|
||||
const state = `dbg-dblogin:${strmid}:${selectedConnection?.conid}:${$values.amoid}`;
|
||||
sessionStorage.setItem('dbloginAuthState', state);
|
||||
// openWebLink(
|
||||
// `connections/dblogin?conid=${selectedConnection?.conid}&state=${encodeURIComponent(state)}&redirectUri=${
|
||||
// location.origin + location.pathname
|
||||
// }`
|
||||
// );
|
||||
internalRedirectTo(
|
||||
`/connections/dblogin?conid=${selectedConnection?.conid}&state=${encodeURIComponent(state)}&redirectUri=${
|
||||
location.origin + location.pathname
|
||||
}`
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{:else if selectedConnection}
|
||||
<FormSubmit
|
||||
value="Log In"
|
||||
on:click={async e => {
|
||||
if (selectedConnection.passwordMode == 'askUser' || selectedConnection.passwordMode == 'askPassword') {
|
||||
enableApi();
|
||||
isTesting = true;
|
||||
testIdRef.update(x => x + 1);
|
||||
const testid = testIdRef.get();
|
||||
const resp = await apiCall('connections/dblogin-auth', {
|
||||
amoid: $values.amoid,
|
||||
conid: selectedConnection.conid,
|
||||
user: $values['login'],
|
||||
password: $values['password'],
|
||||
});
|
||||
if (testIdRef.get() != testid) return;
|
||||
isTesting = false;
|
||||
if (resp.accessToken) {
|
||||
localStorage.setItem('accessToken', resp.accessToken);
|
||||
internalRedirectTo('/');
|
||||
} else {
|
||||
sqlConnectResult = resp;
|
||||
}
|
||||
} else {
|
||||
enableApi();
|
||||
const resp = await apiCall('connections/dblogin-auth', {
|
||||
amoid: $values.amoid,
|
||||
conid: selectedConnection.conid,
|
||||
});
|
||||
localStorage.setItem('accessToken', resp.accessToken);
|
||||
internalRedirectTo('/');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<FormSubmit
|
||||
value={isAdminPage
|
||||
? 'Log In as Administrator'
|
||||
: workflowType == 'redirect'
|
||||
? 'Redirect to login page'
|
||||
: 'Log In'}
|
||||
on:click={async e => {
|
||||
enableApi();
|
||||
|
||||
if (isAdminPage || workflowType == 'credentials' || workflowType == 'anonymous') {
|
||||
await processCredentialsLogin($values.amoid, e.detail);
|
||||
} else if (workflowType == 'redirect') {
|
||||
await processRedirectLogin($values.amoid);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</FormProvider>
|
||||
</FormProviderCore>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import FormStyledButton from './buttons/FormStyledButton.svelte';
|
||||
import { doLogout, redirectToLogin } from './clientAuth';
|
||||
import { doLogout, redirectToAdminLogin, redirectToLogin } from './clientAuth';
|
||||
|
||||
onMount(() => {
|
||||
const removed = document.getElementById('starting_dbgate_zero');
|
||||
@@ -10,9 +10,14 @@
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
const error = params.get('error');
|
||||
const isAdmin = params.get('is-admin') == 'true';
|
||||
|
||||
function handleLogin() {
|
||||
redirectToLogin(undefined, true);
|
||||
if (isAdmin) {
|
||||
redirectToAdminLogin();
|
||||
} else {
|
||||
redirectToLogin(undefined, true);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -98,7 +98,6 @@
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import { getDatabaseMenuItems } from './DatabaseAppObject.svelte';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import { getDatabaseList, useUsedApps } from '../utility/metadataLoaders';
|
||||
import { getLocalStorage } from '../utility/storageCache';
|
||||
import { apiCall, removeVolatileMapping } from '../utility/api';
|
||||
@@ -106,6 +105,7 @@
|
||||
import { closeMultipleTabs } from '../tabpanel/TabsPanel.svelte';
|
||||
import AboutModal from '../modals/AboutModal.svelte';
|
||||
import { tick } from 'svelte';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
|
||||
export let data;
|
||||
export let passProps;
|
||||
|
||||
@@ -340,7 +340,6 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import uuidv1 from 'uuid/v1';
|
||||
|
||||
import _, { find } from 'lodash';
|
||||
@@ -365,7 +364,7 @@
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import AppObjectCore from './AppObjectCore.svelte';
|
||||
import { showSnackbarError, showSnackbarSuccess } from '../utility/snackbar';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import { findEngineDriver, getConnectionLabel } from 'dbgate-tools';
|
||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||
import { getDatabaseInfo, useUsedApps } from '../utility/metadataLoaders';
|
||||
import { openJsonDocument } from '../tabs/JsonTab.svelte';
|
||||
|
||||
@@ -776,7 +776,7 @@
|
||||
pinnedTables,
|
||||
} from '../stores';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import { filterName, generateDbPairingId, getAlterDatabaseScript } from 'dbgate-tools';
|
||||
import { filterName, generateDbPairingId, getAlterDatabaseScript, getConnectionLabel } from 'dbgate-tools';
|
||||
import { getConnectionInfo, getDatabaseInfo } from '../utility/metadataLoaders';
|
||||
import fullDisplayName from '../utility/fullDisplayName';
|
||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||
@@ -784,7 +784,6 @@
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import uuidv1 from 'uuid/v1';
|
||||
import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import { exportQuickExportFile } from '../utility/exportFileTools';
|
||||
import createQuickExportMenu from '../utility/createQuickExportMenu';
|
||||
import ConfirmSqlModal, { saveScriptToDatabase } from '../modals/ConfirmSqlModal.svelte';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" context="module">
|
||||
import { filterName } from 'dbgate-tools';
|
||||
import { filterName, getConnectionLabel } from 'dbgate-tools';
|
||||
|
||||
interface FileTypeHandler {
|
||||
icon: string;
|
||||
@@ -100,7 +100,6 @@
|
||||
import { currentDatabase } from '../stores';
|
||||
import { apiCall } from '../utility/api';
|
||||
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { useDatabaseList } from '../utility/metadataLoaders';
|
||||
import AppObjectList from './AppObjectList.svelte';
|
||||
import * as databaseAppObject from './DatabaseAppObject.svelte';
|
||||
import { volatileConnectionMapStore } from '../utility/api';
|
||||
|
||||
export let filter;
|
||||
export let data;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
export let disabled = false;
|
||||
export let icon = null;
|
||||
export let title = null;
|
||||
export let iconAfter = null;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -18,6 +19,9 @@
|
||||
<div class="inner" class:disabled on:click={handleClick}>
|
||||
<span class="icon" class:disabled><FontIcon {icon} /></span>
|
||||
<slot />
|
||||
{#if iconAfter}
|
||||
<span class="icon" class:disabled><FontIcon icon={iconAfter} /></span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
export let component = ToolStripButton;
|
||||
export let hideDisabled = false;
|
||||
export let buttonLabel = null;
|
||||
export let iconAfter = null;
|
||||
|
||||
$: cmd = Object.values($commandsCustomized).find((x: any) => x.id == command) as any;
|
||||
</script>
|
||||
@@ -28,6 +29,7 @@
|
||||
icon={cmd.icon}
|
||||
on:click={cmd.onClick}
|
||||
disabled={!cmd.enabled}
|
||||
{iconAfter}
|
||||
{...$$restProps}
|
||||
>
|
||||
{buttonLabel || cmd.toolbarName || cmd.name}
|
||||
|
||||
@@ -1,11 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { get_current_component } from 'svelte/internal';
|
||||
import createActivator, { isComponentActiveStore } from '../utility/createActivator';
|
||||
|
||||
const thisInstance = get_current_component();
|
||||
|
||||
export const activator = createActivator('ToolStripContainer', true);
|
||||
|
||||
$: isComponentActive = $isComponentActiveStore('ToolStripContainer', thisInstance);
|
||||
|
||||
export function activate() {
|
||||
activator?.activate();
|
||||
}
|
||||
|
||||
export let scrollContent = false;
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
<div class="content">
|
||||
<div class="content" class:scrollContent>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<div class="toolstrip">
|
||||
<slot name="toolstrip" />
|
||||
</div>
|
||||
{#if isComponentActive}
|
||||
<div class="toolstrip">
|
||||
<slot name="toolstrip" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@@ -19,6 +38,7 @@
|
||||
display: flex;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.toolstrip {
|
||||
@@ -26,4 +46,8 @@
|
||||
flex-wrap: wrap;
|
||||
background: var(--theme-bg-1);
|
||||
}
|
||||
|
||||
.scrollContent {
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
+150
-26
@@ -1,5 +1,8 @@
|
||||
import { apiCall, enableApi } from './utility/api';
|
||||
import { ca } from 'date-fns/locale';
|
||||
import { apiCall, enableApi, getAuthCategory } from './utility/api';
|
||||
import { getConfig } from './utility/metadataLoaders';
|
||||
import { isAdminPage } from './utility/pageDefs';
|
||||
import getElectron from './utility/getElectron';
|
||||
|
||||
export function isOauthCallback() {
|
||||
const params = new URLSearchParams(location.search);
|
||||
@@ -11,15 +14,43 @@ export function isOauthCallback() {
|
||||
);
|
||||
}
|
||||
|
||||
export function isDbLoginCallback() {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const sentCode = params.get('code');
|
||||
const sentState = params.get('state');
|
||||
|
||||
return (
|
||||
sentCode && sentState && sentState.startsWith('dbg-dblogin:') && sentState == localStorage.getItem('dbloginState')
|
||||
);
|
||||
}
|
||||
|
||||
export function isDbLoginAuthCallback() {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const sentCode = params.get('code');
|
||||
const sentState = params.get('state');
|
||||
|
||||
return (
|
||||
sentCode &&
|
||||
sentState &&
|
||||
sentState.startsWith('dbg-dblogin:') &&
|
||||
sentState == sessionStorage.getItem('dbloginAuthState')
|
||||
);
|
||||
}
|
||||
|
||||
export function handleOauthCallback() {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const sentCode = params.get('code');
|
||||
const sid = params.get('sid');
|
||||
|
||||
if (isOauthCallback()) {
|
||||
const [_prefix, strmid, amoid] = sessionStorage.getItem('oauthState').split(':');
|
||||
|
||||
sessionStorage.removeItem('oauthState');
|
||||
apiCall('auth/oauth-token', {
|
||||
code: sentCode,
|
||||
amoid,
|
||||
redirectUri: location.origin + location.pathname,
|
||||
sid,
|
||||
}).then(authResp => {
|
||||
const { accessToken, error, errorMessage } = authResp;
|
||||
|
||||
@@ -36,14 +67,83 @@ export function handleOauthCallback() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isDbLoginCallback()) {
|
||||
const [_prefix, strmid, conid] = localStorage.getItem('dbloginState').split(':');
|
||||
localStorage.removeItem('dbloginState');
|
||||
|
||||
apiCall('connections/dblogin-token', {
|
||||
code: sentCode,
|
||||
conid,
|
||||
strmid,
|
||||
sid,
|
||||
redirectUri: location.origin + location.pathname,
|
||||
}).then(authResp => {
|
||||
if (authResp.success) {
|
||||
window.close();
|
||||
} else if (authResp.error) {
|
||||
internalRedirectTo(`/?page=error&error=${encodeURIComponent(authResp.error)}`);
|
||||
} else {
|
||||
internalRedirectTo(`/?page=error`);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isDbLoginAuthCallback()) {
|
||||
const [_prefix, strmid, conid, amoid] = sessionStorage.getItem('dbloginAuthState').split(':');
|
||||
sessionStorage.removeItem('dbloginAuthState');
|
||||
|
||||
apiCall('connections/dblogin-auth-token', {
|
||||
code: sentCode,
|
||||
conid,
|
||||
redirectUri: location.origin + location.pathname,
|
||||
amoid,
|
||||
sid,
|
||||
}).then(authResp => {
|
||||
if (authResp.accessToken) {
|
||||
localStorage.setItem('accessToken', authResp.accessToken);
|
||||
internalRedirectTo('/');
|
||||
} else if (authResp.error) {
|
||||
internalRedirectTo(`/?page=error&error=${encodeURIComponent(authResp.error)}`);
|
||||
} else {
|
||||
internalRedirectTo(`/?page=error`);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function handleAuthOnStartup(config) {
|
||||
if (config.oauth) {
|
||||
console.log('OAUTH callback URL:', location.origin + location.pathname);
|
||||
export async function handleAuthOnStartup(config, isAdminPage = false) {
|
||||
if (config.configurationError) {
|
||||
internalRedirectTo(`/?page=error`);
|
||||
return;
|
||||
}
|
||||
if (config.oauth || config.isLoginForm) {
|
||||
|
||||
if (!config.isLicenseValid) {
|
||||
if (config.storageDatabase || getElectron()) {
|
||||
internalRedirectTo(`/?page=license`);
|
||||
} else {
|
||||
internalRedirectTo(`/?page=error`);
|
||||
}
|
||||
}
|
||||
|
||||
if (getAuthCategory(config) == 'admin') {
|
||||
if (localStorage.getItem('adminAccessToken')) {
|
||||
return;
|
||||
}
|
||||
|
||||
redirectToAdminLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
// if (config.oauth) {
|
||||
// console.log('OAUTH callback URL:', location.origin + location.pathname);
|
||||
// }
|
||||
if (getAuthCategory(config) == 'token') {
|
||||
if (localStorage.getItem('accessToken')) {
|
||||
return;
|
||||
}
|
||||
@@ -52,16 +152,21 @@ export async function handleAuthOnStartup(config) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function redirectToAdminLogin() {
|
||||
internalRedirectTo('/?page=admin-login');
|
||||
return;
|
||||
}
|
||||
|
||||
export async function redirectToLogin(config = null, force = false) {
|
||||
if (!config) {
|
||||
enableApi();
|
||||
config = await getConfig();
|
||||
}
|
||||
|
||||
if (config.isLoginForm) {
|
||||
if (getAuthCategory(config) == 'token') {
|
||||
if (!force) {
|
||||
const params = new URLSearchParams(location.search);
|
||||
if (params.get('page') == 'login' || params.get('page') == 'not-logged') {
|
||||
if (params.get('page') == 'login' || params.get('page') == 'admin-login' || params.get('page') == 'not-logged') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -69,18 +174,18 @@ export async function redirectToLogin(config = null, force = false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.oauth) {
|
||||
const state = `dbg-oauth:${Math.random().toString().substr(2)}`;
|
||||
const scopeParam = config.oauthScope ? `&scope=${config.oauthScope}` : '';
|
||||
sessionStorage.setItem('oauthState', state);
|
||||
console.log('Redirecting to OAUTH provider');
|
||||
location.replace(
|
||||
`${config.oauth}?client_id=${config.oauthClient}&response_type=code&redirect_uri=${encodeURIComponent(
|
||||
location.origin + location.pathname
|
||||
)}&state=${encodeURIComponent(state)}${scopeParam}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
// if (config.oauth) {
|
||||
// const state = `dbg-oauth:${Math.random().toString().substr(2)}`;
|
||||
// const scopeParam = config.oauthScope ? `&scope=${config.oauthScope}` : '';
|
||||
// sessionStorage.setItem('oauthState', state);
|
||||
// console.log('Redirecting to OAUTH provider');
|
||||
// location.replace(
|
||||
// `${config.oauth}?client_id=${config.oauthClient}&response_type=code&redirect_uri=${encodeURIComponent(
|
||||
// location.origin + location.pathname
|
||||
// )}&state=${encodeURIComponent(state)}${scopeParam}`
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
}
|
||||
|
||||
export function internalRedirectTo(path) {
|
||||
@@ -92,17 +197,36 @@ export function internalRedirectTo(path) {
|
||||
export async function doLogout() {
|
||||
enableApi();
|
||||
const config = await getConfig();
|
||||
if (config.oauth) {
|
||||
const category = getAuthCategory(config);
|
||||
|
||||
if (category == 'admin') {
|
||||
localStorage.removeItem('adminAccessToken');
|
||||
internalRedirectTo('/?page=admin-login&is-admin=true');
|
||||
} else if (category == 'token') {
|
||||
localStorage.removeItem('accessToken');
|
||||
if (config.oauthLogout) {
|
||||
window.location.href = config.oauthLogout;
|
||||
if (config.logoutUrl) {
|
||||
window.location.href = config.logoutUrl;
|
||||
} else {
|
||||
internalRedirectTo('/?page=not-logged');
|
||||
}
|
||||
} else if (config.isLoginForm) {
|
||||
localStorage.removeItem('accessToken');
|
||||
internalRedirectTo('/?page=not-logged');
|
||||
} else {
|
||||
} else if (category == 'basic') {
|
||||
window.location.href = 'config/logout';
|
||||
}
|
||||
|
||||
// if (config.oauth) {
|
||||
// localStorage.removeItem(isAdminPage() ? 'adminAccessToken' : 'accessToken');
|
||||
// if (config.oauthLogout) {
|
||||
// window.location.href = config.oauthLogout;
|
||||
// } else {
|
||||
// internalRedirectTo('/?page=not-logged');
|
||||
// }
|
||||
// } else if (config.isLoginForm) {
|
||||
// localStorage.removeItem(isAdminPage() ? 'adminAccessToken' : 'accessToken');
|
||||
// internalRedirectTo(`/?page=not-logged&is-admin=${isAdminPage() ? 'true' : ''}`);
|
||||
// } else if (config.isAdminLoginForm && isAdminPage()) {
|
||||
// localStorage.removeItem('adminAccessToken');
|
||||
// internalRedirectTo('/?page=admin-login&is-admin=true');
|
||||
// } else {
|
||||
// window.location.href = 'config/logout';
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { filterName } from 'dbgate-tools';
|
||||
import { filterName, getConnectionLabel } from 'dbgate-tools';
|
||||
|
||||
import _ from 'lodash';
|
||||
import { onMount } from 'svelte';
|
||||
@@ -75,7 +75,6 @@
|
||||
visibleCommandPalette,
|
||||
} from '../stores';
|
||||
import clickOutside from '../utility/clickOutside';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import { isElectronAvailable } from '../utility/getElectron';
|
||||
import keycodes from '../utility/keycodes';
|
||||
import { useConnectionList, useDatabaseInfo } from '../utility/metadataLoaders';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import { recentDatabases, currentDatabase, getRecentDatabases } from '../stores';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import registerCommand from './registerCommand';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
|
||||
currentDatabase.subscribe(value => {
|
||||
if (!value) return;
|
||||
|
||||
@@ -104,7 +104,7 @@ registerCommand({
|
||||
category: 'New',
|
||||
toolbarOrder: 1,
|
||||
name: 'Connection',
|
||||
testEnabled: () => !getCurrentConfig()?.runAsPortal,
|
||||
testEnabled: () => !getCurrentConfig()?.runAsPortal && !getCurrentConfig()?.storageDatabase,
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
title: 'New Connection',
|
||||
@@ -121,7 +121,7 @@ registerCommand({
|
||||
toolbarName: 'Add connection folder',
|
||||
category: 'New',
|
||||
toolbarOrder: 1,
|
||||
name: 'Connection',
|
||||
name: 'Connection folder',
|
||||
testEnabled: () => !getCurrentConfig()?.runAsPortal,
|
||||
onClick: () => {
|
||||
showModal(InputTextModal, {
|
||||
@@ -551,7 +551,7 @@ registerCommand({
|
||||
id: 'app.logout',
|
||||
category: 'App',
|
||||
name: 'Logout',
|
||||
testEnabled: () => getCurrentConfig()?.login != null,
|
||||
testEnabled: () => getCurrentConfig()?.isUserLoggedIn,
|
||||
onClick: doLogout,
|
||||
});
|
||||
|
||||
@@ -559,7 +559,7 @@ registerCommand({
|
||||
id: 'app.disconnect',
|
||||
category: 'App',
|
||||
name: 'Disconnect',
|
||||
testEnabled: () => getCurrentConfig()?.singleConnection != null,
|
||||
testEnabled: () => getCurrentConfig()?.singleConnection != null && !getCurrentConfig()?.isUserLoggedIn,
|
||||
onClick: () => disconnectServerConnection(getCurrentConfig()?.singleConnection?._id),
|
||||
});
|
||||
|
||||
@@ -873,7 +873,6 @@ registerCommand({
|
||||
onClick: () => showModal(UploadErrorModal),
|
||||
});
|
||||
|
||||
|
||||
const electron = getElectron();
|
||||
if (electron) {
|
||||
electron.addEventListener('run-command', (e, commandId) => runCommand(commandId));
|
||||
|
||||
@@ -2,10 +2,19 @@
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
|
||||
export let collapsed;
|
||||
export let vertical = false;
|
||||
</script>
|
||||
|
||||
<div on:click|stopPropagation class="collapseButtonMarker">
|
||||
<FontIcon icon={collapsed ? 'icon triple-right' : 'icon triple-left'} />
|
||||
<FontIcon
|
||||
icon={collapsed
|
||||
? vertical
|
||||
? 'icon triple-down'
|
||||
: 'icon triple-right'
|
||||
: vertical
|
||||
? 'icon triple-up'
|
||||
: 'icon triple-left'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@@ -17,10 +26,12 @@
|
||||
top: 4px;
|
||||
background-color: var(--theme-bg-1);
|
||||
border: 1px solid var(--theme-bg-1); */
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
div:hover {
|
||||
color: var(--theme-font-hover);
|
||||
border: 1px solid var(--theme-font-1);
|
||||
margin: 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -401,6 +401,7 @@
|
||||
import { getDatabaseInfo, useDatabaseStatus } from '../utility/metadataLoaders';
|
||||
import { showSnackbarSuccess } from '../utility/snackbar';
|
||||
import { openJsonLinesData } from '../utility/openJsonLinesData';
|
||||
import contextMenuActivator from '../utility/contextMenuActivator';
|
||||
|
||||
export let onLoadNextData = undefined;
|
||||
export let grider = undefined;
|
||||
@@ -1082,17 +1083,15 @@
|
||||
$: {
|
||||
const stringified = stableStringify(selectedCells);
|
||||
if (
|
||||
(lastPublishledSelectedCellsRef.get() != stringified || changeSetValueRef.get() != $changeSetStore.value) &&
|
||||
(lastPublishledSelectedCellsRef.get() != stringified || changeSetValueRef.get() != $changeSetStore?.value) &&
|
||||
realColumnUniqueNames?.length > 0
|
||||
) {
|
||||
const rowIndexes = _.uniq(selectedCells.map(x => x[0]));
|
||||
if (rowIndexes.every(x => grider.getRowData(x))) {
|
||||
tick().then(() => {
|
||||
tick().then(() => {
|
||||
const rowIndexes = _.uniq(selectedCells.map(x => x[0]));
|
||||
if (rowIndexes.every(x => grider.getRowData(x))) {
|
||||
lastPublishledSelectedCellsRef.set(stringified);
|
||||
const cellsValue = () => getCellsPublished(selectedCells);
|
||||
changeSetValueRef.set($changeSetStore.value);
|
||||
// selectedCellsPublished = cellsValue;
|
||||
$selectedCellsCallback = cellsValue;
|
||||
changeSetValueRef.set($changeSetStore?.value);
|
||||
$selectedCellsCallback = () => getCellsPublished(selectedCells);
|
||||
|
||||
if (onChangeSelectedColumns) {
|
||||
onChangeSelectedColumns(getSelectedColumns().map(x => x.columnName));
|
||||
@@ -1101,9 +1100,8 @@
|
||||
if (onPublishedCellsChanged) {
|
||||
onPublishedCellsChanged(getCellsPublished(selectedCells));
|
||||
}
|
||||
});
|
||||
}
|
||||
// if (onSelectedCellsPublishedChanged) onSelectedCellsPublishedChanged(getCellsPublished(selectedCells));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1805,6 +1803,7 @@
|
||||
bind:clientWidth={containerWidth}
|
||||
bind:clientHeight={containerHeight}
|
||||
use:contextMenu={buildMenu}
|
||||
use:contextMenuActivator={activator}
|
||||
on:wheel={handleGridWheel}
|
||||
>
|
||||
<input
|
||||
|
||||
@@ -62,11 +62,11 @@
|
||||
.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.main.flex1 {
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
@@ -75,6 +75,12 @@
|
||||
min-height: var(--dim-tabs-height);
|
||||
right: 0;
|
||||
background-color: var(--theme-bg-2);
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.tabs::-webkit-scrollbar {
|
||||
height: 7px;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { getFormContext } from './FormProviderCore.svelte';
|
||||
import FormTextAreaFieldRaw from './FormTextAreaFieldRaw.svelte';
|
||||
|
||||
export let label;
|
||||
export let name;
|
||||
export let templateProps = {};
|
||||
export let focused = false;
|
||||
|
||||
const { template } = getFormContext();
|
||||
</script>
|
||||
|
||||
<svelte:component this={template} type="text" {label} {...templateProps}>
|
||||
<FormTextAreaFieldRaw {name} {...$$restProps} {focused} />
|
||||
</svelte:component>
|
||||
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { getFormContext } from './FormProviderCore.svelte';
|
||||
import TextAreaField from './TextAreaField.svelte';
|
||||
|
||||
export let name;
|
||||
export let defaultValue = undefined;
|
||||
export let saveOnInput = false;
|
||||
export let onChange = null;
|
||||
|
||||
const { values, setFieldValue } = getFormContext();
|
||||
</script>
|
||||
|
||||
<TextAreaField
|
||||
{...$$restProps}
|
||||
value={$values[name] ?? defaultValue}
|
||||
on:input={e => setFieldValue(name, e.target['value'])}
|
||||
on:input={e => {
|
||||
if (saveOnInput) {
|
||||
setFieldValue(name, e.target['value']);
|
||||
}
|
||||
if (onChange) {
|
||||
onChange(e.target['value']);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -2,7 +2,7 @@
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let value;
|
||||
export let focused;
|
||||
export let focused = false;
|
||||
|
||||
let domEditor;
|
||||
|
||||
|
||||
@@ -1,3 +1,30 @@
|
||||
<script context="module">
|
||||
export function getNumberIcon(number) {
|
||||
switch (number) {
|
||||
case 1:
|
||||
return 'mdi mdi-numeric-1-circle';
|
||||
case 2:
|
||||
return 'mdi mdi-numeric-2-circle';
|
||||
case 3:
|
||||
return 'mdi mdi-numeric-3-circle';
|
||||
case 4:
|
||||
return 'mdi mdi-numeric-4-circle';
|
||||
case 5:
|
||||
return 'mdi mdi-numeric-5-circle';
|
||||
case 6:
|
||||
return 'mdi mdi-numeric-6-circle';
|
||||
case 7:
|
||||
return 'mdi mdi-numeric-7-circle';
|
||||
case 8:
|
||||
return 'mdi mdi-numeric-8-circle';
|
||||
case 9:
|
||||
return 'mdi mdi-numeric-9-circle';
|
||||
}
|
||||
if (number > 9) return 'mdi mdi-numeric-9-plus-circle';
|
||||
return null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export let icon;
|
||||
export let title = null;
|
||||
@@ -27,6 +54,7 @@
|
||||
'icon users': 'mdi mdi-account-multiple',
|
||||
'icon role': 'mdi mdi-account-group',
|
||||
'icon admin': 'mdi mdi-security',
|
||||
'icon auth': 'mdi mdi-account-key',
|
||||
'icon version': 'mdi mdi-ticket-confirmation',
|
||||
'icon pin': 'mdi mdi-pin',
|
||||
'icon arrange': 'mdi mdi-arrange-send-to-back',
|
||||
@@ -88,6 +116,8 @@
|
||||
'icon arrow-right-bold': 'mdi mdi-arrow-right-bold',
|
||||
'icon triple-left': 'mdi mdi-chevron-triple-left',
|
||||
'icon triple-right': 'mdi mdi-chevron-triple-right',
|
||||
'icon triple-up': 'mdi mdi-chevron-triple-up',
|
||||
'icon triple-down': 'mdi mdi-chevron-triple-down',
|
||||
'icon format-code': 'mdi mdi-code-tags-check',
|
||||
'icon show-wizard': 'mdi mdi-comment-edit',
|
||||
'icon disconnected': 'mdi mdi-lan-disconnect',
|
||||
@@ -191,6 +221,7 @@
|
||||
'img users': 'mdi mdi-account-multiple color-icon-blue',
|
||||
'img role': 'mdi mdi-account-group color-icon-blue',
|
||||
'img admin': 'mdi mdi-security color-icon-blue',
|
||||
'img auth': 'mdi mdi-account-key color-icon-blue',
|
||||
|
||||
'img add': 'mdi mdi-plus-circle color-icon-green',
|
||||
'img minus': 'mdi mdi-minus-circle color-icon-red',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import FormSelectField from '../forms/FormSelectField.svelte';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import { useConnectionList } from '../utility/metadataLoaders';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
|
||||
export let allowChooseModel = false;
|
||||
export let direction;
|
||||
|
||||
@@ -6,12 +6,14 @@ import localStorageGarbageCollector from './utility/localStorageGarbageCollector
|
||||
import { handleOauthCallback } from './clientAuth';
|
||||
import LoginPage from './LoginPage.svelte';
|
||||
import NotLoggedPage from './NotLoggedPage.svelte';
|
||||
|
||||
const isOauthCallback = handleOauthCallback();
|
||||
import ErrorPage from './ErrorPage.svelte';
|
||||
import EnterLicensePage from './EnterLicensePage.svelte';
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
const page = params.get('page');
|
||||
|
||||
const isOauthCallback = handleOauthCallback();
|
||||
|
||||
localStorageGarbageCollector();
|
||||
|
||||
function createApp() {
|
||||
@@ -22,14 +24,40 @@ function createApp() {
|
||||
switch (page) {
|
||||
case 'login':
|
||||
return new LoginPage({
|
||||
target: document.body,
|
||||
props: {
|
||||
isAdminPage: false,
|
||||
},
|
||||
});
|
||||
case 'error':
|
||||
return new ErrorPage({
|
||||
target: document.body,
|
||||
props: {},
|
||||
});
|
||||
case 'license':
|
||||
return new EnterLicensePage({
|
||||
target: document.body,
|
||||
props: {},
|
||||
});
|
||||
case 'admin-login':
|
||||
return new LoginPage({
|
||||
target: document.body,
|
||||
props: {
|
||||
isAdminPage: true,
|
||||
},
|
||||
});
|
||||
case 'not-logged':
|
||||
return new NotLoggedPage({
|
||||
target: document.body,
|
||||
props: {},
|
||||
});
|
||||
case 'admin':
|
||||
return new App({
|
||||
target: document.body,
|
||||
props: {
|
||||
isAdminPage: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return new App({
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
import ErrorMessageModal from './ErrorMessageModal.svelte';
|
||||
import ModalBase from './ModalBase.svelte';
|
||||
import { closeCurrentModal, showModal } from './modalTools';
|
||||
import { callServerPing } from '../utility/connectionsPinger';
|
||||
|
||||
export let conid;
|
||||
export let passwordMode;
|
||||
@@ -83,6 +84,7 @@
|
||||
isTesting = false;
|
||||
if (resp.msgtype == 'connected') {
|
||||
setVolatileConnectionRemapping(conid, resp._id);
|
||||
await callServerPing();
|
||||
dispatchCacheChange({ key: `server-status-changed` });
|
||||
batchDispatchCacheTriggers(x => x.conid == conid);
|
||||
closeCurrentModal();
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import { closeCurrentModal, showModal } from './modalTools';
|
||||
import InputTextModal from './InputTextModal.svelte';
|
||||
import { apiCall } from '../utility/api';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
|
||||
export let connection;
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
import { currentDropDownMenu } from '../stores';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { importSqlDump } from '../utility/exportFileTools';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import { setUploadListener } from '../utility/uploadFiles';
|
||||
import ChangeDownloadUrlModal from './ChangeDownloadUrlModal.svelte';
|
||||
import ModalBase from './ModalBase.svelte';
|
||||
import { closeCurrentModal, showModal } from './modalTools';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
|
||||
export let connection;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||
import ModalBase from '../modals/ModalBase.svelte';
|
||||
import { closeCurrentModal } from '../modals/modalTools';
|
||||
import { fullNameFromString, fullNameToLabel, fullNameToString } from 'dbgate-tools';
|
||||
import { fullNameFromString, fullNameToLabel, fullNameToString, getConnectionLabel } from 'dbgate-tools';
|
||||
import SelectField from '../forms/SelectField.svelte';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
@@ -18,7 +18,6 @@
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { createPerspectiveNodeConfig, PerspectiveTreeNode } from 'dbgate-datalib';
|
||||
import type { ChangePerspectiveConfigFunc, PerspectiveConfig, PerspectiveCustomJoinConfig } from 'dbgate-datalib';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import uuidv1 from 'uuid/v1';
|
||||
import TextField from '../forms/TextField.svelte';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import { getCurrentDatabase } from '../stores';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
|
||||
export default function newQuery({
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import FormTextField from '../forms/FormTextField.svelte';
|
||||
import { openedConnections, openedSingleDatabaseConnections } from '../stores';
|
||||
import { getFormContext } from '../forms/FormProviderCore.svelte';
|
||||
import FormTextAreaField from '../forms/FormTextAreaField.svelte';
|
||||
|
||||
const { values } = getFormContext();
|
||||
|
||||
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
|
||||
</script>
|
||||
|
||||
<FormTextAreaField label="Allowed databases, one per line" name="allowedDatabases" disabled={isConnected} rows={8} />
|
||||
<FormTextField label="Allowed databases regular expression" name="allowedDatabasesRegex" disabled={isConnected} />
|
||||
@@ -12,7 +12,7 @@
|
||||
import FormTextField from '../forms/FormTextField.svelte';
|
||||
import { extensions, getCurrentConfig, openedConnections, openedSingleDatabaseConnections } from '../stores';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import { useAuthTypes } from '../utility/metadataLoaders';
|
||||
import { useAuthTypes, useConfig } from '../utility/metadataLoaders';
|
||||
import FormColorField from '../forms/FormColorField.svelte';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
|
||||
@@ -27,13 +27,17 @@
|
||||
$: disabledFields = (currentAuthType ? currentAuthType.disabledFields : null) || [];
|
||||
$: driver = $extensions.drivers.find(x => x.engine == engine);
|
||||
$: defaultDatabase = $values.defaultDatabase;
|
||||
$: config = useConfig();
|
||||
|
||||
$: showUser = driver?.showConnectionField('user', $values) && $values.passwordMode != 'askUser';
|
||||
$: showConnectionFieldArgs = { config: $config };
|
||||
|
||||
$: showUser =
|
||||
driver?.showConnectionField('user', $values, showConnectionFieldArgs) && $values.passwordMode != 'askUser';
|
||||
$: showPassword =
|
||||
driver?.showConnectionField('password', $values) &&
|
||||
driver?.showConnectionField('password', $values, showConnectionFieldArgs) &&
|
||||
$values.passwordMode != 'askPassword' &&
|
||||
$values.passwordMode != 'askUser';
|
||||
$: showPasswordMode = driver?.showConnectionField('password', $values);
|
||||
$: showPasswordMode = driver?.showConnectionField('password', $values, showConnectionFieldArgs);
|
||||
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
|
||||
</script>
|
||||
|
||||
@@ -53,11 +57,11 @@
|
||||
]}
|
||||
/>
|
||||
|
||||
{#if driver?.showConnectionField('databaseFile', $values)}
|
||||
{#if driver?.showConnectionField('databaseFile', $values, showConnectionFieldArgs)}
|
||||
<FormElectronFileSelector label="Database file" name="databaseFile" disabled={isConnected || !electron} />
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('useDatabaseUrl', $values)}
|
||||
{#if driver?.showConnectionField('useDatabaseUrl', $values, showConnectionFieldArgs)}
|
||||
<div class="radio">
|
||||
<FormRadioGroupField
|
||||
disabled={isConnected}
|
||||
@@ -70,7 +74,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('databaseUrl', $values)}
|
||||
{#if driver?.showConnectionField('databaseUrl', $values, showConnectionFieldArgs)}
|
||||
<FormTextField
|
||||
label="Database URL"
|
||||
name="databaseUrl"
|
||||
@@ -79,21 +83,27 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if $authTypes && driver?.showConnectionField('authType', $values)}
|
||||
<FormSelectField
|
||||
label={driver?.authTypeLabel ?? 'Authentication'}
|
||||
name="authType"
|
||||
isNative
|
||||
disabled={isConnected}
|
||||
defaultValue={driver?.defaultAuthTypeName}
|
||||
options={$authTypes.map(auth => ({
|
||||
value: auth.name,
|
||||
label: auth.title,
|
||||
}))}
|
||||
/>
|
||||
{#if $authTypes && driver?.showConnectionField('authType', $values, showConnectionFieldArgs)}
|
||||
{#key $authTypes}
|
||||
<FormSelectField
|
||||
label={driver?.authTypeLabel ?? 'Authentication'}
|
||||
name="authType"
|
||||
isNative
|
||||
disabled={isConnected}
|
||||
defaultValue={driver?.defaultAuthTypeName}
|
||||
options={$authTypes.map(auth => ({
|
||||
value: auth.name,
|
||||
label: auth.title,
|
||||
}))}
|
||||
/>
|
||||
{/key}
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('server', $values)}
|
||||
{#if driver?.showConnectionField('clientLibraryPath', $values, showConnectionFieldArgs)}
|
||||
<FormTextField label="Client library path" name="clientLibraryPath" disabled={isConnected} />
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('server', $values, showConnectionFieldArgs)}
|
||||
<div class="row">
|
||||
<div class="col-9 mr-1">
|
||||
<FormTextField
|
||||
@@ -103,7 +113,7 @@
|
||||
templateProps={{ noMargin: true }}
|
||||
/>
|
||||
</div>
|
||||
{#if driver?.showConnectionField('port', $values)}
|
||||
{#if driver?.showConnectionField('port', $values, showConnectionFieldArgs)}
|
||||
<div class="col-3 mr-1">
|
||||
<FormTextField
|
||||
label="Port"
|
||||
@@ -123,11 +133,11 @@
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('serviceName', $values)}
|
||||
{#if driver?.showConnectionField('serviceName', $values, showConnectionFieldArgs)}
|
||||
<FormTextField label="Service name" name="serviceName" disabled={isConnected} />
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('socketPath', $values)}
|
||||
{#if driver?.showConnectionField('socketPath', $values, showConnectionFieldArgs)}
|
||||
<FormTextField
|
||||
label="Socket path"
|
||||
name="socketPath"
|
||||
@@ -183,27 +193,27 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('treeKeySeparator', $values)}
|
||||
{#if driver?.showConnectionField('treeKeySeparator', $values, showConnectionFieldArgs)}
|
||||
<FormTextField label="Key separator" name="treeKeySeparator" disabled={isConnected} placeholder=":" />
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('windowsDomain', $values)}
|
||||
{#if driver?.showConnectionField('windowsDomain', $values, showConnectionFieldArgs)}
|
||||
<FormTextField label="Domain (specify to use NTLM authentication)" name="windowsDomain" disabled={isConnected} />
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('isReadOnly', $values)}
|
||||
{#if driver?.showConnectionField('isReadOnly', $values, showConnectionFieldArgs)}
|
||||
<FormCheckboxField label="Is read only" name="isReadOnly" disabled={isConnected} />
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('trustServerCertificate', $values)}
|
||||
{#if driver?.showConnectionField('trustServerCertificate', $values, showConnectionFieldArgs)}
|
||||
<FormCheckboxField label="Trust server certificate" name="trustServerCertificate" disabled={isConnected} />
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('defaultDatabase', $values)}
|
||||
{#if driver?.showConnectionField('defaultDatabase', $values, showConnectionFieldArgs)}
|
||||
<FormTextField label="Default database" name="defaultDatabase" disabled={isConnected} />
|
||||
{/if}
|
||||
|
||||
{#if defaultDatabase && driver?.showConnectionField('singleDatabase', $values)}
|
||||
{#if defaultDatabase && driver?.showConnectionField('singleDatabase', $values, showConnectionFieldArgs)}
|
||||
<FormCheckboxField label={`Use only database ${defaultDatabase}`} name="singleDatabase" disabled={isConnected} />
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -31,9 +31,15 @@
|
||||
import { isMac } from '../utility/common';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import ThemeSkeleton from './ThemeSkeleton.svelte';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
import FormTextAreaField from '../forms/FormTextAreaField.svelte';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { useSettings } from '../utility/metadataLoaders';
|
||||
import { derived } from 'svelte/store';
|
||||
|
||||
const electron = getElectron();
|
||||
let restartWarning = false;
|
||||
let licenseKeyCheckResult = null;
|
||||
|
||||
export let selectedTab = 0;
|
||||
|
||||
@@ -58,6 +64,23 @@ ORDER BY
|
||||
$selectedWidget = 'plugins';
|
||||
$visibleWidgetSideBar = true;
|
||||
}
|
||||
|
||||
const settings = useSettings();
|
||||
const settingsValues = derived(settings, $settings => {
|
||||
if (!$settings) {
|
||||
return {};
|
||||
}
|
||||
return $settings;
|
||||
});
|
||||
|
||||
$: licenseKey = $settingsValues['other.licenseKey'];
|
||||
let checkedLicenseKey = false;
|
||||
$: if (licenseKey && !checkedLicenseKey) {
|
||||
checkedLicenseKey = true;
|
||||
apiCall('config/check-license', { licenseKey }).then(result => {
|
||||
licenseKeyCheckResult = result;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<SettingsFormProvider>
|
||||
@@ -70,6 +93,7 @@ ORDER BY
|
||||
isInline
|
||||
tabs={[
|
||||
{ label: 'General', slot: 1 },
|
||||
isProApp() && electron && { label: 'License', slot: 7 },
|
||||
{ label: 'Connection', slot: 2 },
|
||||
{ label: 'Themes', slot: 3 },
|
||||
{ label: 'Default Actions', slot: 4 },
|
||||
@@ -317,11 +341,34 @@ ORDER BY
|
||||
<svelte:fragment slot="6">
|
||||
<div class="heading">Other</div>
|
||||
|
||||
<FormTextField
|
||||
name="other.gistCreateToken"
|
||||
label="API token for creating error gists"
|
||||
defaultValue=""
|
||||
<FormTextField name="other.gistCreateToken" label="API token for creating error gists" defaultValue="" />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="7">
|
||||
<div class="heading">License</div>
|
||||
<FormTextAreaField
|
||||
name="other.licenseKey"
|
||||
label="License key"
|
||||
rows={7}
|
||||
onChange={async value => {
|
||||
licenseKeyCheckResult = await apiCall('config/check-license', { licenseKey: value });
|
||||
}}
|
||||
/>
|
||||
{#if licenseKeyCheckResult}
|
||||
<div class="m-3 ml-5">
|
||||
{#if licenseKeyCheckResult.status == 'ok'}
|
||||
<div>
|
||||
<FontIcon icon="img ok" /> License key is valid
|
||||
</div>
|
||||
<div>
|
||||
License valid to: {licenseKeyCheckResult.validTo}
|
||||
</div>
|
||||
<div>License key expiration: {licenseKeyCheckResult.expiration}</div>
|
||||
{:else if licenseKeyCheckResult.status == 'error'}
|
||||
<FontIcon icon="img error" /> License key is invalid
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TabControl>
|
||||
</FormValues>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { getSettings, useConfig, useSettings } from './utility/metadataLoaders';
|
||||
import _ from 'lodash';
|
||||
import { safeJsonParse } from 'dbgate-tools';
|
||||
import { apiCall } from './utility/api';
|
||||
import { getOpenedTabsStorageName, isAdminPage } from './utility/pageDefs';
|
||||
|
||||
export interface TabDefinition {
|
||||
title: string;
|
||||
@@ -19,6 +20,7 @@ export interface TabDefinition {
|
||||
tabComponent: string;
|
||||
tabOrder?: number;
|
||||
multiTabIndex?: number;
|
||||
unsaved?: boolean;
|
||||
}
|
||||
|
||||
export function writableWithStorage<T>(defaultValue: T, storageName) {
|
||||
@@ -72,7 +74,10 @@ function subscribeCssVariable(store, transform, cssVariable) {
|
||||
store.subscribe(value => document.documentElement.style.setProperty(cssVariable, transform(value)));
|
||||
}
|
||||
|
||||
export const selectedWidget = writableWithStorage('database', 'selectedWidget');
|
||||
export const selectedWidget = writableWithStorage(
|
||||
isAdminPage() ? 'admin' : 'database',
|
||||
isAdminPage() ? 'selectedAdminWidget' : 'selectedWidget'
|
||||
);
|
||||
export const lockedDatabaseMode = writableWithStorage<boolean>(false, 'lockedDatabaseMode');
|
||||
export const visibleWidgetSideBar = writableWithStorage(true, 'visibleWidgetSideBar');
|
||||
export const visibleSelectedWidget = derived(
|
||||
@@ -86,7 +91,7 @@ export const temporaryOpenedConnections = writable([]);
|
||||
export const openedSingleDatabaseConnections = writable([]);
|
||||
export const expandedConnections = writable([]);
|
||||
export const currentDatabase = writable(null);
|
||||
export const openedTabs = writableWithForage<TabDefinition[]>([], 'openedTabs', x => [...(x || [])]);
|
||||
export const openedTabs = writableWithForage<TabDefinition[]>([], getOpenedTabsStorageName(), x => [...(x || [])]);
|
||||
export const copyRowsFormat = writableWithStorage('textWithoutHeaders', 'copyRowsFormat');
|
||||
export const extensions = writable<ExtensionsDirectory>(null);
|
||||
export const visibleCommandPalette = writable(null);
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
tabComponent={mountedTabs[tabid]}
|
||||
{...openedTabsByTabId[tabid]?.props}
|
||||
{tabid}
|
||||
unsaved={openedTabsByTabId[tabid]?.unsaved}
|
||||
tabVisible={tabid == shownTab?.tabid}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
@@ -287,7 +287,6 @@
|
||||
import tabs from '../tabs';
|
||||
import { setSelectedTab } from '../utility/common';
|
||||
import contextMenu from '../utility/contextMenu';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import { isElectronAvailable } from '../utility/getElectron';
|
||||
import { getConnectionInfo, useConnectionList } from '../utility/metadataLoaders';
|
||||
import { duplicateTab, getTabDbKey, sortTabs, groupTabs } from '../utility/openNewTab';
|
||||
@@ -295,6 +294,7 @@
|
||||
import TabCloseButton from '../elements/TabCloseButton.svelte';
|
||||
import CloseTabModal from '../modals/CloseTabModal.svelte';
|
||||
import SwitchDatabaseModal from '../modals/SwitchDatabaseModal.svelte';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
|
||||
export let multiTabIndex;
|
||||
export let shownTab;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
CollectionGridDisplay,
|
||||
changeSetContainsChanges,
|
||||
runMacroOnChangeSet,
|
||||
changeSetChangedCount,
|
||||
} from 'dbgate-datalib';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import { writable } from 'svelte/store';
|
||||
@@ -54,6 +55,7 @@
|
||||
import { getBoolSettingsValue } from '../settings/settingsTools';
|
||||
import useEditorData from '../query/useEditorData';
|
||||
import { markTabSaved, markTabUnsaved } from '../utility/common';
|
||||
import { getNumberIcon } from '../icons/FontIcon.svelte';
|
||||
|
||||
export let tabid;
|
||||
export let conid;
|
||||
@@ -197,7 +199,10 @@
|
||||
<svelte:fragment slot="toolstrip">
|
||||
<ToolStripCommandButton command="dataGrid.refresh" hideDisabled />
|
||||
<ToolStripCommandButton command="dataForm.refresh" hideDisabled />
|
||||
<ToolStripCommandButton command="collectionTable.save" />
|
||||
<ToolStripCommandButton
|
||||
command="collectionTable.save"
|
||||
iconAfter={getNumberIcon(changeSetChangedCount($changeSetStore?.value))}
|
||||
/>
|
||||
<ToolStripCommandButton command="dataGrid.revertAllChanges" hideDisabled />
|
||||
<ToolStripCommandButton command="dataGrid.insertNewRow" hideDisabled />
|
||||
<ToolStripCommandButton command="dataGrid.deleteSelectedRows" hideDisabled />
|
||||
|
||||
@@ -28,10 +28,12 @@
|
||||
import { apiCall } from '../utility/api';
|
||||
import { showSnackbarError, showSnackbarSuccess } from '../utility/snackbar';
|
||||
import { changeTab } from '../utility/common';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
import { onMount } from 'svelte';
|
||||
import { disconnectServerConnection, openConnection } from '../appobj/ConnectionAppObject.svelte';
|
||||
import { disconnectDatabaseConnection } from '../appobj/DatabaseAppObject.svelte';
|
||||
import { useConfig } from '../utility/metadataLoaders';
|
||||
import ConnectionAdvancedDriverFields from '../settings/ConnectionAdvancedDriverFields.svelte';
|
||||
|
||||
export let connection;
|
||||
export let tabid;
|
||||
@@ -57,6 +59,7 @@
|
||||
|
||||
$: engine = $values.engine;
|
||||
$: driver = $extensions.drivers.find(x => x.engine == engine);
|
||||
$: config = useConfig();
|
||||
|
||||
const testIdRef = createRef(0);
|
||||
|
||||
@@ -91,7 +94,7 @@
|
||||
'socketPath',
|
||||
'serviceName',
|
||||
];
|
||||
const visibleProps = allProps.filter(x => driver?.showConnectionField(x, $values));
|
||||
const visibleProps = allProps.filter(x => driver?.showConnectionField(x, $values, { config: $config }));
|
||||
const omitProps = _.difference(allProps, visibleProps);
|
||||
if (!$values.defaultDatabase) omitProps.push('singleDatabase');
|
||||
|
||||
@@ -179,6 +182,11 @@
|
||||
}
|
||||
});
|
||||
|
||||
export function changeConnectionBeforeSave(connection) {
|
||||
if (driver?.beforeConnectionSave) return driver.beforeConnectionSave(connection);
|
||||
return connection;
|
||||
}
|
||||
|
||||
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
|
||||
|
||||
// $: console.log('CONN VALUES', $values);
|
||||
@@ -203,6 +211,10 @@
|
||||
label: 'SSL',
|
||||
component: ConnectionSslFields,
|
||||
},
|
||||
{
|
||||
label: 'Advanced',
|
||||
component: ConnectionAdvancedDriverFields,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
export const activator = createActivator('JsonEditorTab', false);
|
||||
|
||||
let domEditor;
|
||||
let domToolStrip;
|
||||
|
||||
$: if ($tabVisible && domEditor) {
|
||||
domEditor?.getEditor()?.focus();
|
||||
@@ -72,13 +73,14 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<ToolStripContainer>
|
||||
<ToolStripContainer bind:this={domToolStrip}>
|
||||
<AceEditor
|
||||
value={$editorState.value || ''}
|
||||
menu={createMenu()}
|
||||
on:input={e => setEditorData(e.detail)}
|
||||
on:focus={() => {
|
||||
activator.activate();
|
||||
domToolStrip?.activate();
|
||||
invalidateCommands();
|
||||
}}
|
||||
bind:this={domEditor}
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
export const activator = createActivator('JsonLinesEditorTab', false);
|
||||
|
||||
let domEditor;
|
||||
let domToolStrip;
|
||||
|
||||
$: if ($tabVisible && domEditor) {
|
||||
domEditor?.getEditor()?.focus();
|
||||
@@ -172,7 +173,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<ToolStripContainer>
|
||||
<ToolStripContainer bind:this={domToolStrip}>
|
||||
<VerticalSplitter isSplitter={jslid}>
|
||||
<svelte:fragment slot="1">
|
||||
<AceEditor
|
||||
@@ -181,6 +182,7 @@
|
||||
on:input={e => setEditorData(e.detail)}
|
||||
on:focus={() => {
|
||||
activator.activate();
|
||||
domToolStrip?.activate();
|
||||
invalidateCommands();
|
||||
}}
|
||||
bind:this={domEditor}
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
let resultCount;
|
||||
let errorMessages;
|
||||
let domEditor;
|
||||
let domToolStrip;
|
||||
let intervalId;
|
||||
|
||||
onMount(() => {
|
||||
@@ -350,7 +351,7 @@
|
||||
let isInitialized = false;
|
||||
</script>
|
||||
|
||||
<ToolStripContainer>
|
||||
<ToolStripContainer bind:this={domToolStrip}>
|
||||
<VerticalSplitter isSplitter={visibleResultTabs}>
|
||||
<svelte:fragment slot="1">
|
||||
{#if driver?.databaseEngineTypes?.includes('sql')}
|
||||
@@ -370,6 +371,7 @@
|
||||
}}
|
||||
on:focus={() => {
|
||||
activator.activate();
|
||||
domToolStrip?.activate();
|
||||
invalidateCommands();
|
||||
setTimeout(() => {
|
||||
isInitialized = true;
|
||||
@@ -388,6 +390,7 @@
|
||||
on:input={e => setEditorData(e.detail)}
|
||||
on:focus={() => {
|
||||
activator.activate();
|
||||
domToolStrip?.activate();
|
||||
invalidateCommands();
|
||||
}}
|
||||
bind:this={domEditor}
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
let executeNumber = 0;
|
||||
|
||||
let domEditor;
|
||||
let domToolStrip;
|
||||
|
||||
// const status = writable({
|
||||
// busy,
|
||||
@@ -221,7 +222,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<ToolStripContainer>
|
||||
<ToolStripContainer bind:this={domToolStrip}>
|
||||
<VerticalSplitter>
|
||||
<svelte:fragment slot="1">
|
||||
<AceEditor
|
||||
@@ -230,6 +231,7 @@
|
||||
on:input={e => setEditorData(e.detail)}
|
||||
on:focus={() => {
|
||||
activator.activate();
|
||||
domToolStrip?.activate();
|
||||
invalidateCommands();
|
||||
}}
|
||||
bind:this={domEditor}
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
import TableDataGrid from '../datagrid/TableDataGrid.svelte';
|
||||
import useGridConfig from '../utility/useGridConfig';
|
||||
import {
|
||||
changeSetChangedCount,
|
||||
changeSetContainsChanges,
|
||||
changeSetToSql,
|
||||
createChangeSet,
|
||||
@@ -103,6 +104,7 @@
|
||||
import useEditorData from '../query/useEditorData';
|
||||
import { markTabSaved, markTabUnsaved } from '../utility/common';
|
||||
import ToolStripButton from '../buttons/ToolStripButton.svelte';
|
||||
import { getNumberIcon } from '../icons/FontIcon.svelte';
|
||||
|
||||
export let tabid;
|
||||
export let conid;
|
||||
@@ -277,7 +279,10 @@
|
||||
<ToolStripCommandButton command="dataForm.goToNext" hideDisabled />
|
||||
<ToolStripCommandButton command="dataForm.goToLast" hideDisabled />
|
||||
|
||||
<ToolStripCommandButton command="tableData.save" />
|
||||
<ToolStripCommandButton
|
||||
command="tableData.save"
|
||||
iconAfter={getNumberIcon(changeSetChangedCount($changeSetStore?.value))}
|
||||
/>
|
||||
<ToolStripCommandButton command="dataGrid.revertAllChanges" hideDisabled />
|
||||
<ToolStripCommandButton command="dataGrid.insertNewRow" hideDisabled />
|
||||
<ToolStripCommandButton command="dataGrid.deleteSelectedRows" hideDisabled />
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import _ from 'lodash';
|
||||
import { TabDefinition } from '../stores';
|
||||
import getElectron from './getElectron';
|
||||
import { getOpenedTabsStorageName } from './pageDefs';
|
||||
|
||||
let counter = 0;
|
||||
$: counterCopy = counter;
|
||||
@@ -26,15 +27,15 @@
|
||||
)
|
||||
) {
|
||||
try {
|
||||
let openedTabs = (await localforage.getItem<TabDefinition[]>('openedTabs')) || [];
|
||||
let openedTabs = (await localforage.getItem<TabDefinition[]>(getOpenedTabsStorageName())) || [];
|
||||
if (!_.isArray(openedTabs)) openedTabs = [];
|
||||
openedTabs = openedTabs
|
||||
.map(tab => (tab.closedTime ? tab : { ...tab, closedTime: new Date().getTime() }))
|
||||
.map(tab => ({ ...tab, selected: false }));
|
||||
await localforage.setItem('openedTabs', openedTabs);
|
||||
await localforage.setItem(getOpenedTabsStorageName(), openedTabs);
|
||||
await localStorage.setItem('selectedWidget', 'history');
|
||||
} catch (err) {
|
||||
localforage.removeItem('openedTabs');
|
||||
localforage.removeItem(getOpenedTabsStorageName());
|
||||
}
|
||||
// try {
|
||||
// await localforage.clear();
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
import createActivator from './createActivator';
|
||||
|
||||
export let name;
|
||||
export let activateOnTabVisible = false;
|
||||
|
||||
export const activator = createActivator(name, activateOnTabVisible);
|
||||
</script>
|
||||
@@ -4,7 +4,7 @@
|
||||
import runCommand from '../commands/runCommand';
|
||||
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import { openedTabs } from '../stores';
|
||||
import { commandsCustomized, openedTabs } from '../stores';
|
||||
|
||||
import { getConfig, getConnectionList, useFavorites } from './metadataLoaders';
|
||||
import openNewTab from './openNewTab';
|
||||
@@ -49,7 +49,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (!$openedTabs.find(x => x.closedTime == null) && !(await getConnectionList()).find(x => !x.unsaved)) {
|
||||
if (
|
||||
!$openedTabs.find(x => x.closedTime == null) &&
|
||||
!(await getConnectionList()).find(x => !x.unsaved) &&
|
||||
$commandsCustomized['new.connection']?.enabled
|
||||
) {
|
||||
openNewTab({
|
||||
title: 'New Connection',
|
||||
icon: 'img connection',
|
||||
|
||||
@@ -4,11 +4,15 @@ import { writable } from 'svelte/store';
|
||||
import getElectron from './getElectron';
|
||||
// import socket from './socket';
|
||||
import { showSnackbarError } from '../utility/snackbar';
|
||||
import { isOauthCallback, redirectToLogin } from '../clientAuth';
|
||||
import { isOauthCallback, redirectToAdminLogin, redirectToLogin } from '../clientAuth';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import DatabaseLoginModal, { isDatabaseLoginVisible } from '../modals/DatabaseLoginModal.svelte';
|
||||
import _ from 'lodash';
|
||||
import uuidv1 from 'uuid/v1';
|
||||
import { openWebLink } from './exportFileTools';
|
||||
import { callServerPing } from './connectionsPinger';
|
||||
import { batchDispatchCacheTriggers, dispatchCacheChange } from './cache';
|
||||
import { isAdminPage } from './pageDefs';
|
||||
|
||||
export const strmid = uuidv1();
|
||||
|
||||
@@ -18,8 +22,20 @@ let apiLogging = false;
|
||||
let apiDisabled = false;
|
||||
const disabledOnOauth = isOauthCallback();
|
||||
|
||||
const volatileConnectionMap = {};
|
||||
const volatileConnectionMapInv = {};
|
||||
export const volatileConnectionMapStore = writable({});
|
||||
export const volatileConnectionMapInvStore = writable({});
|
||||
|
||||
let volatileConnectionMapValue = {};
|
||||
volatileConnectionMapStore.subscribe(value => {
|
||||
volatileConnectionMapValue = value;
|
||||
});
|
||||
export const getVolatileConnectionMap = () => volatileConnectionMapValue;
|
||||
|
||||
let volatileConnectionMapInvValue = {};
|
||||
volatileConnectionMapInvStore.subscribe(value => {
|
||||
volatileConnectionMapInvValue = value;
|
||||
});
|
||||
export const getVolatileConnectionInvMap = () => volatileConnectionMapInvValue;
|
||||
|
||||
export function disableApi() {
|
||||
apiDisabled = true;
|
||||
@@ -30,23 +46,29 @@ export function enableApi() {
|
||||
}
|
||||
|
||||
export function setVolatileConnectionRemapping(existingConnectionId, volatileConnectionId) {
|
||||
volatileConnectionMap[existingConnectionId] = volatileConnectionId;
|
||||
volatileConnectionMapInv[volatileConnectionId] = existingConnectionId;
|
||||
volatileConnectionMapStore.update(x => ({
|
||||
...x,
|
||||
[existingConnectionId]: volatileConnectionId,
|
||||
}));
|
||||
volatileConnectionMapInvStore.update(x => ({
|
||||
...x,
|
||||
[volatileConnectionId]: existingConnectionId,
|
||||
}));
|
||||
}
|
||||
|
||||
export function getVolatileRemapping(conid) {
|
||||
return volatileConnectionMap[conid] || conid;
|
||||
return volatileConnectionMapValue[conid] || conid;
|
||||
}
|
||||
|
||||
export function getVolatileRemappingInv(conid) {
|
||||
return volatileConnectionMapInv[conid] || conid;
|
||||
return volatileConnectionMapInvValue[conid] || conid;
|
||||
}
|
||||
|
||||
export function removeVolatileMapping(conid) {
|
||||
const mapped = volatileConnectionMap[conid];
|
||||
const mapped = volatileConnectionMapValue[conid];
|
||||
if (mapped) {
|
||||
delete volatileConnectionMap[conid];
|
||||
delete volatileConnectionMapInv[mapped];
|
||||
volatileConnectionMapStore.update(x => _.omit(x, conid));
|
||||
volatileConnectionMapInvStore.update(x => _.omit(x, mapped));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,13 +79,30 @@ function wantEventSource() {
|
||||
}
|
||||
}
|
||||
|
||||
function processApiResponse(route, args, resp) {
|
||||
async function processApiResponse(route, args, resp) {
|
||||
// if (apiLogging) {
|
||||
// console.log('<<< API RESPONSE', route, args, resp);
|
||||
// }
|
||||
|
||||
if (resp?.missingCredentials) {
|
||||
if (!isDatabaseLoginVisible()) {
|
||||
if (resp.detail.redirectToDbLogin) {
|
||||
const volatile = await apiCall('connections/volatile-dblogin-from-auth', { conid: resp.detail.conid });
|
||||
if (volatile) {
|
||||
setVolatileConnectionRemapping(resp.detail.conid, volatile._id);
|
||||
await callServerPing();
|
||||
dispatchCacheChange({ key: `server-status-changed` });
|
||||
batchDispatchCacheTriggers(x => x.conid == resp.detail.conid);
|
||||
return null;
|
||||
}
|
||||
|
||||
const state = `dbg-dblogin:${strmid}:${resp.detail.conid}`;
|
||||
localStorage.setItem('dbloginState', state);
|
||||
openWebLink(
|
||||
`connections/dblogin?conid=${resp.detail.conid}&state=${encodeURIComponent(state)}&redirectUri=${
|
||||
location.origin + location.pathname
|
||||
}`
|
||||
);
|
||||
} else if (!isDatabaseLoginVisible()) {
|
||||
showModal(DatabaseLoginModal, resp.detail);
|
||||
}
|
||||
return null;
|
||||
@@ -83,16 +122,16 @@ function processApiResponse(route, args, resp) {
|
||||
|
||||
export function transformApiArgs(args) {
|
||||
return _.mapValues(args, (v, k) => {
|
||||
if (k == 'conid' && v && volatileConnectionMap[v]) return volatileConnectionMap[v];
|
||||
if (k == 'conidArray' && _.isArray(v)) return v.map(x => volatileConnectionMap[x] || x);
|
||||
if (k == 'conid' && v && volatileConnectionMapValue[v]) return volatileConnectionMapValue[v];
|
||||
if (k == 'conidArray' && _.isArray(v)) return v.map(x => volatileConnectionMapValue[x] || x);
|
||||
return v;
|
||||
});
|
||||
}
|
||||
|
||||
export function transformApiArgsInv(args) {
|
||||
return _.mapValues(args, (v, k) => {
|
||||
if (k == 'conid' && v && volatileConnectionMapInv[v]) return volatileConnectionMapInv[v];
|
||||
if (k == 'conidArray' && _.isArray(v)) return v.map(x => volatileConnectionMapInv[x] || x);
|
||||
if (k == 'conid' && v && volatileConnectionMapInvValue[v]) return volatileConnectionMapInvValue[v];
|
||||
if (k == 'conidArray' && _.isArray(v)) return v.map(x => volatileConnectionMapInvValue[x] || x);
|
||||
return v;
|
||||
});
|
||||
}
|
||||
@@ -115,7 +154,7 @@ export async function apiCall(route: string, args: {} = undefined) {
|
||||
const electron = getElectron();
|
||||
if (electron) {
|
||||
const resp = await electron.invoke(route.replace('/', '-'), args);
|
||||
return processApiResponse(route, args, resp);
|
||||
return await processApiResponse(route, args, resp);
|
||||
} else {
|
||||
const resp = await fetch(`${resolveApi()}/${route}`, {
|
||||
method: 'POST',
|
||||
@@ -132,15 +171,19 @@ export async function apiCall(route: string, args: {} = undefined) {
|
||||
|
||||
disableApi();
|
||||
console.log('Disabling API', route);
|
||||
if (params.get('page') != 'login' && params.get('page') != 'not-logged') {
|
||||
if (params.get('page') != 'login' && params.get('page') != 'admin-login' && params.get('page') != 'not-logged') {
|
||||
// unauthorized
|
||||
redirectToLogin();
|
||||
if (params.get('page') == 'admin') {
|
||||
redirectToAdminLogin();
|
||||
} else {
|
||||
redirectToLogin();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const json = await resp.json();
|
||||
return processApiResponse(route, args, json);
|
||||
return await processApiResponse(route, args, json);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +248,32 @@ export function useApiCall(route, args, defaultValue) {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getVolatileConnections() {
|
||||
return Object.values(volatileConnectionMapValue);
|
||||
}
|
||||
|
||||
export function installNewVolatileConnectionListener() {
|
||||
apiOn('got-volatile-token', async ({ savedConId, volatileConId }) => {
|
||||
setVolatileConnectionRemapping(savedConId, volatileConId);
|
||||
await callServerPing();
|
||||
dispatchCacheChange({ key: `server-status-changed` });
|
||||
batchDispatchCacheTriggers(x => x.conid == savedConId);
|
||||
});
|
||||
}
|
||||
|
||||
export function getAuthCategory(config) {
|
||||
if (config.isBasicAuth) {
|
||||
return 'basic';
|
||||
}
|
||||
if (isAdminPage() && config.isAdminLoginForm) {
|
||||
return 'admin';
|
||||
}
|
||||
if (getElectron()) {
|
||||
return 'electron';
|
||||
}
|
||||
return 'token';
|
||||
}
|
||||
|
||||
function enableApiLog() {
|
||||
apiLogging = true;
|
||||
console.log('API loggin enabled');
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { getOpenedTabs, openedTabs } from '../stores';
|
||||
import _ from 'lodash';
|
||||
import getElectron from './getElectron';
|
||||
|
||||
export class LoadingToken {
|
||||
isCanceled = false;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import { openedConnections, currentDatabase, openedConnectionsWithTemporary } from '../stores';
|
||||
import { apiCall, strmid } from './api';
|
||||
import { getConnectionList } from './metadataLoaders';
|
||||
import { currentDatabase, openedConnectionsWithTemporary, getCurrentConfig, getOpenedConnections } from '../stores';
|
||||
import { apiCall, getVolatileConnections, strmid } from './api';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
|
||||
// const doServerPing = async value => {
|
||||
// const connectionList = getConnectionList();
|
||||
@@ -10,7 +10,21 @@ import { getConnectionList } from './metadataLoaders';
|
||||
// };
|
||||
|
||||
const doServerPing = value => {
|
||||
apiCall('server-connections/ping', { conidArray: ['__storage', ...value], strmid });
|
||||
const config = getCurrentConfig();
|
||||
|
||||
const conidArray = [...value];
|
||||
if (config.storageDatabase && hasPermission('internal-storage')) {
|
||||
conidArray.push('__storage');
|
||||
}
|
||||
conidArray.push(...getVolatileConnections());
|
||||
if (config.singleConnection) {
|
||||
conidArray.push(config.singleConnection._id);
|
||||
}
|
||||
|
||||
apiCall('server-connections/ping', {
|
||||
conidArray,
|
||||
strmid,
|
||||
});
|
||||
};
|
||||
|
||||
const doDatabasePing = value => {
|
||||
@@ -38,3 +52,8 @@ export function subscribeConnectionPingers() {
|
||||
currentDatabaseHandle = window.setInterval(() => doDatabasePing(value), 20 * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
export function callServerPing() {
|
||||
const connections = getOpenedConnections();
|
||||
doServerPing(connections);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
export default function contextMenuActivator(node, activator) {
|
||||
const handleContextMenu = async e => {
|
||||
activator.activate();
|
||||
};
|
||||
|
||||
node.addEventListener('contextmenu', handleContextMenu);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
node.removeEventListener('contextmenu', handleContextMenu);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import { getContext } from 'svelte';
|
||||
import { get_current_component, onMount, setContext } from 'svelte/internal';
|
||||
import invalidateCommands from '../commands/invalidateCommands';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
const lastActiveDictionary = {};
|
||||
|
||||
export const isComponentActiveStore = writable((key: string, component) => false as boolean);
|
||||
|
||||
function isParent(parent, child) {
|
||||
while (child && child.activator) {
|
||||
if (parent == child) return true;
|
||||
@@ -54,7 +57,7 @@ export default function createActivator(
|
||||
}
|
||||
|
||||
// console.log('toDelete', toDelete);
|
||||
|
||||
|
||||
for (const del of toDelete) {
|
||||
delete lastActiveDictionary[del];
|
||||
}
|
||||
@@ -63,6 +66,10 @@ export default function createActivator(
|
||||
parentActivatorInstance.activator.activate();
|
||||
}
|
||||
// console.log('Active components', lastActiveDictionary);
|
||||
|
||||
isComponentActiveStore.set((key, component) => {
|
||||
return lastActiveDictionary[key] == component;
|
||||
});
|
||||
};
|
||||
|
||||
const getTabVisible = () => tabVisibleValue;
|
||||
|
||||
@@ -4,13 +4,17 @@ import { useConfig } from './metadataLoaders';
|
||||
let compiled = null;
|
||||
|
||||
export default function hasPermission(tested) {
|
||||
// console.log('TESTING PERM', tested, compiled, testPermission(tested, compiled));
|
||||
return testPermission(tested, compiled);
|
||||
}
|
||||
|
||||
export function subscribePermissionCompiler() {
|
||||
// console.log('subscribePermissionCompiler', compiled);
|
||||
|
||||
useConfig().subscribe(value => {
|
||||
if (!value) return;
|
||||
const { permissions } = value;
|
||||
compiled = compilePermissions(permissions);
|
||||
// console.log('COMPILED PERMS', compiled);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import moment from 'moment';
|
||||
import localforage from 'localforage';
|
||||
import { getOpenedTabsStorageName } from './pageDefs';
|
||||
|
||||
export default async function localStorageGarbageCollector() {
|
||||
const openedTabsJson = await localforage.getItem('openedTabs');
|
||||
const openedTabsJson = await localforage.getItem(getOpenedTabsStorageName());
|
||||
let openedTabs = openedTabsJson ?? [];
|
||||
|
||||
const closeLimit = moment().add(-7, 'day').valueOf();
|
||||
|
||||
openedTabs = openedTabs.filter(x => !x.closedTime || x.closedTime > closeLimit);
|
||||
await localforage.setItem('openedTabs', openedTabs);
|
||||
await localforage.setItem(getOpenedTabsStorageName(), openedTabs);
|
||||
|
||||
const toRemove = [];
|
||||
for (const key in localStorage) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||
import getElectron from './getElectron';
|
||||
import { currentDatabase, extensions, getCurrentDatabase } from '../stores';
|
||||
import { getUploadListener } from './uploadFiles';
|
||||
import getConnectionLabel, { getDatabaseFileLabel } from './getConnectionLabel';
|
||||
import {getConnectionLabel, getDatabaseFileLabel } from 'dbgate-tools';
|
||||
import { apiCall } from './api';
|
||||
import openNewTab from './openNewTab';
|
||||
import { openJsonDocument } from '../tabs/JsonTab.svelte';
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
let isAdminPageCache;
|
||||
|
||||
export function isAdminPage() {
|
||||
if (isAdminPageCache == null) {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const urlPage = params.get('page');
|
||||
|
||||
isAdminPageCache = urlPage == 'admin';
|
||||
}
|
||||
return isAdminPageCache;
|
||||
}
|
||||
|
||||
export function getOpenedTabsStorageName() {
|
||||
return isAdminPage() ? 'adminOpenedTabs' : 'openedTabs';
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export function isProApp() {
|
||||
return false;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import getElectron from './getElectron';
|
||||
import { isAdminPage } from './pageDefs';
|
||||
|
||||
let apiUrl = null;
|
||||
try {
|
||||
@@ -16,9 +17,12 @@ export function resolveApiHeaders() {
|
||||
const electron = getElectron();
|
||||
|
||||
const res = {};
|
||||
const accessToken = localStorage.getItem('accessToken');
|
||||
const accessToken = localStorage.getItem(isAdminPage() ? 'adminAccessToken' : 'accessToken');
|
||||
if (accessToken) {
|
||||
res['Authorization'] = `Bearer ${accessToken}`;
|
||||
}
|
||||
// if (isAdminPage()) {
|
||||
// res['x-is-admin-page'] = 'true';
|
||||
// }
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
collapsedConnectionGroupNames,
|
||||
} from '../stores';
|
||||
import runCommand from '../commands/runCommand';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
import { useConnectionColorFactory } from '../utility/useConnectionColor';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
|
||||
import { apiCall, getVolatileRemapping } from '../utility/api';
|
||||
import { apiCall, volatileConnectionMapStore } from '../utility/api';
|
||||
import LargeButton from '../buttons/LargeButton.svelte';
|
||||
import { plusExpandIcon, chevronExpandIcon } from '../icons/expandIcons';
|
||||
import { safeJsonParse } from 'dbgate-tools';
|
||||
@@ -37,7 +37,10 @@
|
||||
|
||||
$: connectionsWithStatus =
|
||||
$connections && $serverStatus
|
||||
? $connections.map(conn => ({ ...conn, status: $serverStatus[getVolatileRemapping(conn._id)] }))
|
||||
? $connections.map(conn => ({
|
||||
...conn,
|
||||
status: $serverStatus[$volatileConnectionMapStore[conn._id] || conn._id],
|
||||
}))
|
||||
: $connections;
|
||||
|
||||
$: connectionsWithStatusFiltered = connectionsWithStatus?.filter(
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
selectedWidget,
|
||||
visibleCommandPalette,
|
||||
} from '../stores';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
import { useConnectionList, useDatabaseServerVersion, useDatabaseStatus } from '../utility/metadataLoaders';
|
||||
import { findCommand } from '../commands/runCommand';
|
||||
import { useConnectionColor } from '../utility/useConnectionColor';
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
visibleWidgetSideBar,
|
||||
visibleHamburgerMenuWidget,
|
||||
lockedDatabaseMode,
|
||||
getCurrentConfig,
|
||||
} from '../stores';
|
||||
import mainMenuDefinition from '../../../../app/src/mainMenuDefinition';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
@@ -16,7 +17,7 @@
|
||||
let domMainMenu;
|
||||
|
||||
const widgets = [
|
||||
{
|
||||
getCurrentConfig().storageDatabase && {
|
||||
icon: 'icon admin',
|
||||
name: 'admin',
|
||||
title: 'Administration',
|
||||
@@ -103,7 +104,7 @@
|
||||
<FontIcon icon="icon menu" />
|
||||
</div>
|
||||
{/if}
|
||||
{#each widgets.filter(x => hasPermission(`widgets/${x.name}`)) as item}
|
||||
{#each widgets.filter(x => x && hasPermission(`widgets/${x.name}`)) as item}
|
||||
<div
|
||||
class="wrapper"
|
||||
class:selected={item.name == $visibleSelectedWidget}
|
||||
@@ -124,6 +125,7 @@
|
||||
>
|
||||
<FontIcon icon={$lockedDatabaseMode ? 'icon locked-database-mode' : 'icon unlocked-database-mode'} />
|
||||
</div>
|
||||
|
||||
<div class="wrapper" on:click={handleSettingsMenu} bind:this={domSettings}>
|
||||
<FontIcon icon="icon settings" />
|
||||
</div>
|
||||
|
||||
@@ -31,12 +31,12 @@
|
||||
"plugout": "dbgate-plugout dbgate-plugin-mssql"
|
||||
},
|
||||
"devDependencies": {
|
||||
"async-lock": "^1.2.6",
|
||||
"dbgate-plugin-tools": "^1.0.7",
|
||||
"dbgate-query-splitter": "^4.10.1",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"dbgate-tools": "^5.0.0-alpha.1",
|
||||
"tedious": "^18.2.0",
|
||||
"async-lock": "^1.2.6"
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@ const nativeDriver = require('./nativeDriver');
|
||||
const lock = new AsyncLock();
|
||||
const { tediousConnect, tediousQueryCore, tediousReadQuery, tediousStream } = require('./tediousDriver');
|
||||
const { nativeConnect, nativeQueryCore, nativeReadQuery, nativeStream } = nativeDriver;
|
||||
|
||||
let requireMsnodesqlv8;
|
||||
let platformInfo;
|
||||
let authProxy;
|
||||
|
||||
const versionQuery = `
|
||||
SELECT
|
||||
@@ -52,7 +55,26 @@ const driver = {
|
||||
analyserClass: MsSqlAnalyser,
|
||||
|
||||
getAuthTypes() {
|
||||
return requireMsnodesqlv8 ? windowsAuthTypes : null;
|
||||
const res = [];
|
||||
if (requireMsnodesqlv8) res.push(...windowsAuthTypes);
|
||||
|
||||
if (authProxy.isAuthProxySupported()) {
|
||||
res.push(
|
||||
{
|
||||
title: 'NodeJs portable driver (tedious) - recomended',
|
||||
name: 'tedious',
|
||||
},
|
||||
{
|
||||
title: 'Microsoft Entra ID (with MFA support)',
|
||||
name: 'msentra',
|
||||
disabledFields: ['user', 'password'],
|
||||
}
|
||||
);
|
||||
}
|
||||
if (res.length > 0) {
|
||||
return _.uniqBy(res, 'name');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
async connect(conn) {
|
||||
@@ -115,12 +137,28 @@ const driver = {
|
||||
const { rows } = await this.query(pool, 'SELECT name FROM sys.databases order by name');
|
||||
return rows;
|
||||
},
|
||||
getRedirectAuthUrl(connection, options) {
|
||||
if (connection.authType != 'msentra') return null;
|
||||
return authProxy.authProxyGetRedirectUrl({
|
||||
...options,
|
||||
client: platformInfo.isElectron ? 'app' : 'web',
|
||||
type: 'msentra',
|
||||
});
|
||||
},
|
||||
getAuthTokenFromCode(connection, options) {
|
||||
return authProxy.authProxyGetTokenFromCode(options);
|
||||
},
|
||||
getAccessTokenFromAuth: (connection, req) => {
|
||||
return req?.user?.msentraToken;
|
||||
},
|
||||
};
|
||||
|
||||
driver.initialize = dbgateEnv => {
|
||||
if (dbgateEnv.nativeModules && dbgateEnv.nativeModules.msnodesqlv8) {
|
||||
requireMsnodesqlv8 = dbgateEnv.nativeModules.msnodesqlv8;
|
||||
}
|
||||
platformInfo = dbgateEnv.platformInfo;
|
||||
authProxy = dbgateEnv.authProxy;
|
||||
nativeDriver.initialize(dbgateEnv);
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user