@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2021 Li4n0
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -1,4 +1,244 @@
|
|||||||
# RevSuit - A Reverse Platform Developed in Golang
|
# RevSuit - A Flexible and Powerful Reverse Connection Platform
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
RevSuit is an open-sourced Reverse Platform designed for receive connection from target host in bug bounty.
|
|
||||||
|
RevSuit is a flexible and powerful reverse connection platform designed for receiving connection from target host in
|
||||||
|
penetration. It currently supports HTTP, DNS, RMI, MySQL and FTP protocols.
|
||||||
|
|
||||||
|
Flexible:
|
||||||
|
|
||||||
|
1. Revsuit can set flexible rules to capture different connections.
|
||||||
|
2. Deploy and run via binaries without worrying about nasty dependency errors.
|
||||||
|
|
||||||
|
Powerful:
|
||||||
|
|
||||||
|
1. Revsuit allows users to customize the response for different connections through rules and supports dynamic response
|
||||||
|
generation using template variables.
|
||||||
|
2. Support a variety of protocols, and support the in-depth utilization of some protocols, such as DNS rebinding, MySQL
|
||||||
|
Load Local Data, JDBC Deserialize Exploit, FTP Passive Mode SSRF, etc.
|
||||||
|
3. Native support for use with scanners.
|
||||||
|
4. Support push notifications to popular office software.
|
||||||
|
5. Other thoughtful and detailed features.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
### Preparation
|
||||||
|
|
||||||
|
Download the latest release firstly.
|
||||||
|
|
||||||
|
RevSuit will generate default configuration file on first run. Modify the configuration file according to your needs,
|
||||||
|
then re-run.
|
||||||
|
|
||||||
|
To confirm the location of the connection IP, if there is no qqwry.dat in the current directory or its modification time
|
||||||
|
is more than 5 days ago. RevSuit will download it.(Support for GeoIP is in the planning stages).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ./revsuit
|
||||||
|
2021/05/16 22:55:10 [ INFO] Downloading qqwry.dat...
|
||||||
|
|
||||||
|
____ _____ _ __
|
||||||
|
/ __ \___ _ __/ ___/__ __(_) /_
|
||||||
|
/ /_/ / _ \ | / /\__ \/ / / / / __/
|
||||||
|
/ _, _/ __/ |/ /___/ / /_/ / / /_
|
||||||
|
/_/ |_|\___/|___//____/\__,_/_/\__/
|
||||||
|
vBeta0.1
|
||||||
|
https://revsuit.pro
|
||||||
|
2021/05/16 22:55:22 [ INFO] Starting HTTP Server at :80, token:your_token
|
||||||
|
2021/05/16 22:55:22 [ INFO] Start to listen FTP PASV port at :2020, PasvIP is 10.9.8.7
|
||||||
|
2021/05/16 22:55:22 [ INFO] Starting FTP Server at :21
|
||||||
|
2021/05/16 22:55:22 [ INFO] Starting MySQL Server at :3306
|
||||||
|
2021/05/16 22:55:22 [ INFO] Starting RMI Server at :1099
|
||||||
|
2021/05/16 22:55:22 [ INFO] Starting DNS Server at :53
|
||||||
|
```
|
||||||
|
|
||||||
|
After running, you can visit the `/revsuit/admin/` path of HTTP Server and enter the token to access the control panel.
|
||||||
|
|
||||||
|
### Create Rules and Receive Connection
|
||||||
|
|
||||||
|
Take the creation of HTTP rules as an example:
|
||||||
|
|
||||||
|
A few notes are as follows:
|
||||||
|
|
||||||
|
1. The `name` and `flagFormat` of rule are unique.
|
||||||
|
2. `FlagFormat` uses regular expression syntax, and for different protocols, the fields matched by flagFormat are
|
||||||
|
different, you can check the corresponding hints to get details when creating rules.
|
||||||
|
3. Rules with high rank will take effect first.
|
||||||
|
4. For different protocol rules, you can make different custom configurations, and for some protocols, template
|
||||||
|
variables can be used.
|
||||||
|
5. You can set the named grouping of regular expressions in `flagFormat`, and the result of the matching grouping will
|
||||||
|
also be used as a template variable.
|
||||||
|
|
||||||
|
As shown below, we create a rule that uses the built-in template variables and custom template variables of the http
|
||||||
|
protocol, and names it `test_create_rule`:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Then make a request that satisfies the rule and view the response.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The request will be logged on the platform at the same time.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Notice
|
||||||
|
|
||||||
|
If you want to be notified of new connections on your office software, you can configure the webhook address of the
|
||||||
|
relevant software in the configuration file and turn on the `Notice` switch for the corresponding rule. Only support to
|
||||||
|
four software types currently: dingtalk,wechat,lark,slack.(Discord and telegram support is in the plan.)
|
||||||
|
|
||||||
|
### Import/Export Rules
|
||||||
|
|
||||||
|
If you're migrating platforms or purging data, it can be a hassle to recreate rules. That's why the platform supports
|
||||||
|
import and export of rules.
|
||||||
|
|
||||||
|
The entry point for this function is located at `Settings>RULES`.
|
||||||
|
|
||||||
|
The rules are stored in yaml format for import and export, like the following:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
http:
|
||||||
|
- name: test_create_rule
|
||||||
|
flag_format: (?P<what>\w+)\?
|
||||||
|
rank: 0
|
||||||
|
push_to_client: false
|
||||||
|
notice: false
|
||||||
|
response_status_code: "302"
|
||||||
|
response_headers:
|
||||||
|
Location: ${query.url}
|
||||||
|
response_body: ${header.say} ${what}
|
||||||
|
- name: other_rule
|
||||||
|
flag_format: other
|
||||||
|
rank: 1
|
||||||
|
push_to_client: false
|
||||||
|
notice: true
|
||||||
|
response_status_code: "200"
|
||||||
|
response_headers: { }
|
||||||
|
response_body: Hello Revsuit!
|
||||||
|
dns:
|
||||||
|
... ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advance Usage
|
||||||
|
|
||||||
|
### Use with Scanner
|
||||||
|
|
||||||
|
RevSuit was split from my scanner project, so its native support works with scanners.
|
||||||
|
|
||||||
|
From RevSuit's perspective, we call a scanner a client.
|
||||||
|
|
||||||
|
#### Create Connection
|
||||||
|
|
||||||
|
RevSuit
|
||||||
|
uses [HTTP Server-sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)
|
||||||
|
(SSE) to establish a one-way communication channel with the client.
|
||||||
|
|
||||||
|
The API for the channel is: `/revsuit/api/events?message` .The client first needs to add the `Token: your token` header
|
||||||
|
to the Header, and then access the API to establish the channel. When the platform receives a new connection, the `flag`
|
||||||
|
captured by the rule will be passed to the client through this channel.
|
||||||
|

|
||||||
|
|
||||||
|
Here is a [simple demo](https://gist.github.com/Li4n0/21aa0bec2d626114a729ca2677efb05a) using Golang's sse library as an
|
||||||
|
example.
|
||||||
|
|
||||||
|
#### Multi-client
|
||||||
|
|
||||||
|
RevSuit supports multiple clients, and each client in the connected state receives a push of `flag`, so distributed
|
||||||
|
scanning can be supported.
|
||||||
|
|
||||||
|
#### Temporary storage queue
|
||||||
|
|
||||||
|
RevSuit will store `flag` temporarily in the queue when there is no client connection and send it when the client
|
||||||
|
connects, so you don't have to worry about missing the vulnerability because the client disconnects. (This is especially
|
||||||
|
helpful for discovering delay-triggered vulnerabilities.)
|
||||||
|
|
||||||
|
#### Use flagGroup
|
||||||
|
|
||||||
|
`FlagGroup` is the content matched by the anonymous group in the `flagFormat` field of the rule. The platform will check
|
||||||
|
the content matched in the grouping,and the flag is only pushed to the client when then content(`flagGroup`) captured
|
||||||
|
for the first time.
|
||||||
|
|
||||||
|
In a real-world vulnerability scanning scenario, you may send a large number of different payloads for a single
|
||||||
|
vulnerability point, and they may all be valid, which can result in the backlink platform receiving many requests, yet
|
||||||
|
they are caused by the same vulnerability. If you don't want the client to receive so many `flags` for the same
|
||||||
|
vulnerability, you can take advantage of the `flagGroup` feature of rule's flagFormat.
|
||||||
|
|
||||||
|
For example, SSRF scanning.
|
||||||
|
|
||||||
|
Firstly create a rule like the following:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
http:
|
||||||
|
- name: ssrf
|
||||||
|
flag_format: (ssrf[a-z0-9]{6})[0-9]{1,3}
|
||||||
|
rank: 0
|
||||||
|
push_to_client: false
|
||||||
|
notice: false
|
||||||
|
response_status_code: "200"
|
||||||
|
response_headers: { }
|
||||||
|
response_body: "Here is a SSRF!"
|
||||||
|
```
|
||||||
|
|
||||||
|
Suppose our target is `https://www.testvuln.com?url=api.com&p=useless`, and for SSRF we have 5 payloads. The final
|
||||||
|
request sent by the scanner may
|
||||||
|
be `['https://www.testvuln.com?url=http://revsuit.com/ssrfa98oni1&p=useless, ... ,'https://www.testvuln.com?url=//revsuit.com/ssrfa98oni5&p=useless`']
|
||||||
|
. They may all attack successfully.
|
||||||
|
|
||||||
|
However, because anonymous grouping is used in the rule's `flagFormat`, The platform will query the
|
||||||
|
connected `flagGroup`, in this case `ssrfa98oni`, and push `flag` to the client only at its first appearance, so the
|
||||||
|
client will only receive one flag:`ssrfa98oni1`. But this is enough, because it already proves that the `url` parameter
|
||||||
|
of the target is vulnerable.
|
||||||
|
|
||||||
|
### Multi-module collocation
|
||||||
|
|
||||||
|
In actual penetration testing scenarios, certain tasks can be done easily and quickly by combining and matching various
|
||||||
|
modules of RevSuit. The following is an example of a blind XXE scenario, showing how to use RevSuit's HTTP and FTP
|
||||||
|
modules, combined with template variables, to quickly complete a port scan.
|
||||||
|
|
||||||
|
First use create an HTTP rule to return evil.dtd, customize the response to the contents of dtd so that it goes to
|
||||||
|
connect to RevSuit's FTP service, and use template variables to pass the Host and Port to be scanned to FTP via FTP's
|
||||||
|
user and password.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Then create an FTP rule that receives the Host and port to be scanned from the user and password template variables, set
|
||||||
|
to Pasv Address.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Then use BurpSuit to launch the scan, by setting the host and port parameters in the evil.dtd URL to set the target of
|
||||||
|
the port scan.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The running effect is as follows:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
It because the FTP connection will be crashed if `Passive Address` is not accessible, we can determine whether the port
|
||||||
|
is open or not based on whether the connection exits normally.For this example, we successfully detected that port 8005
|
||||||
|
and 8080 are open.
|
||||||
|
|
||||||
|
## More Usage
|
||||||
|
|
||||||
|
A more detailed wiki is being prepared, you can explore by yourself before it.
|
||||||
|
|
||||||
|
## Advice and Communication
|
||||||
|
|
||||||
|
Submit an issue or contact me through Weixin: `Li4n06`
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
|
||||||
|
### Reference
|
||||||
|
|
||||||
|
* [https://github.com/rmb122/rogue_mysql_server](https://github.com/rmb122/rogue_mysql_server)
|
||||||
|
* [https://github.com/256dpi/newdns](https://github.com/256dpi/newdns)
|
||||||
|
|
||||||
|
### Specifically
|
||||||
|
|
||||||
|
Thanks to my friend [@E99plant](https://github.com/wuhan005/) for all the help and advice I received during the
|
||||||
|
development of this project.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
@Apache License 2.0
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
.custom-icons-list[data-v-05a908c2] .anticon{margin-right:6px}body[data-v-4ae4fda9],html[data-v-4ae4fda9]{height:100%;margin:0}.fade-transform-enter-active[data-v-4ae4fda9],.fade-transform-leave-active[data-v-4ae4fda9]{transition:all .3s;opacity:0}.fade-transform-enter[data-v-4ae4fda9],.fade-transform-leave[data-v-4ae4fda9]{opacity:0}.fade-transform-enter-to[data-v-4ae4fda9]{opacity:0}.ant-menu-item>span>a[data-v-4ae4fda9]{color:hsla(0,0%,100%,.65)}.ant-menu-item-selected>span>a[data-v-4ae4fda9]{color:#fff}#nav[data-v-4ae4fda9]{height:100%}#nav .trigger[data-v-4ae4fda9]{font-size:18px;line-height:64px;padding:0 24px;cursor:pointer;transition:color .3s}#nav .trigger[data-v-4ae4fda9]:hover{color:#1890ff}#nav .logo[data-v-4ae4fda9]{height:32px;background:#0a1d2d;margin:16px;text-align:center;font-size:1.2rem;color:#fff;padding-bottom:5px;border-bottom:2px solid #b6befa}.copyright[data-v-4ae4fda9]{color:#888;text-align:right;margin-right:1rem}
|
.custom-icons-list[data-v-05a908c2] .anticon{margin-right:6px}body[data-v-07ed630b],html[data-v-07ed630b]{height:100%;margin:0}.fade-transform-enter-active[data-v-07ed630b],.fade-transform-leave-active[data-v-07ed630b]{transition:all .3s;opacity:0}.fade-transform-enter[data-v-07ed630b],.fade-transform-leave[data-v-07ed630b]{opacity:0}.fade-transform-enter-to[data-v-07ed630b]{opacity:0}.ant-menu-item>span>a[data-v-07ed630b]{color:hsla(0,0%,100%,.65)}.ant-menu-item-selected>span>a[data-v-07ed630b]{color:#fff}#nav[data-v-07ed630b]{height:100%}#nav .trigger[data-v-07ed630b]{font-size:18px;line-height:64px;padding:0 24px;cursor:pointer;transition:color .3s}#nav .trigger[data-v-07ed630b]:hover{color:#1890ff}#nav .logo[data-v-07ed630b]{height:32px;background:#0a1d2d;margin:16px;text-align:center;font-size:1.2rem;color:#fff;padding-bottom:5px;border-bottom:2px solid #b6befa}.copyright[data-v-07ed630b]{color:#888;text-align:right;margin-right:1rem}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#add-rule[data-v-50ca3b7c]{margin-bottom:10px}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#add-rule[data-v-4fed8448]{margin-bottom:10px}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#add-rule[data-v-1f4c0376]{margin-bottom:10px}.full-screen-icon[data-v-1f4c0376]{position:absolute;z-index:5;color:#9e9e9e;right:1px;font-size:1rem;cursor:pointer;transition:font-size .1s}.full-screen-icon[data-v-1f4c0376]:hover{font-size:1.1rem;transition:font-size .1s}
|
||||||
@@ -1 +0,0 @@
|
|||||||
#add-rule[data-v-ae6501fc]{margin-bottom:10px}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#add-rule[data-v-5aa75eae]{margin-bottom:10px}
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#add-rule[data-v-1361da51]{margin-bottom:10px}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
#add-rule[data-v-6f29426c]{margin-bottom:10px}
|
||||||
@@ -1 +0,0 @@
|
|||||||
#add-rule[data-v-4bcc6b83]{margin-bottom:10px}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#add-rule[data-v-79ad429e]{margin-bottom:10px}.full-screen-icon[data-v-79ad429e]{position:absolute;z-index:5;color:#9e9e9e;right:1px;font-size:1rem;cursor:pointer;transition:font-size .1s}.full-screen-icon[data-v-79ad429e]:hover{font-size:1.1rem;transition:font-size .1s}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#add-rule[data-v-376729b6]{margin-bottom:10px}
|
|
||||||
@@ -1 +1 @@
|
|||||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>RevSuit Control Panel</title><link href="css/chunk-1da7f23b.css" rel="prefetch"><link href="css/chunk-330a7e95.css" rel="prefetch"><link href="css/chunk-36391b77.css" rel="prefetch"><link href="css/chunk-3d13e56e.css" rel="prefetch"><link href="css/chunk-42105840.css" rel="prefetch"><link href="css/chunk-65a35ecf.css" rel="prefetch"><link href="css/chunk-7bcb83e6.css" rel="prefetch"><link href="css/chunk-b8f538aa.css" rel="prefetch"><link href="css/chunk-e039e75a.css" rel="prefetch"><link href="css/chunk-fef965c2.css" rel="prefetch"><link href="js/chunk-08f9fbe8.js" rel="prefetch"><link href="js/chunk-1da7f23b.js" rel="prefetch"><link href="js/chunk-330a7e95.js" rel="prefetch"><link href="js/chunk-36391b77.js" rel="prefetch"><link href="js/chunk-3d13e56e.js" rel="prefetch"><link href="js/chunk-42105840.js" rel="prefetch"><link href="js/chunk-65a35ecf.js" rel="prefetch"><link href="js/chunk-7bcb83e6.js" rel="prefetch"><link href="js/chunk-b241c712.js" rel="prefetch"><link href="js/chunk-b8f538aa.js" rel="prefetch"><link href="js/chunk-e039e75a.js" rel="prefetch"><link href="js/chunk-fef965c2.js" rel="prefetch"><link href="css/app.css" rel="preload" as="style"><link href="css/chunk-vendors.css" rel="preload" as="style"><link href="js/app.js" rel="preload" as="script"><link href="js/chunk-vendors.js" rel="preload" as="script"><link href="css/chunk-vendors.css" rel="stylesheet"><link href="css/app.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but revsuit-frontend doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.js"></script><script src="js/app.js"></script></body></html>
|
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>RevSuit Control Panel</title><link href="css/chunk-016c45cf.css" rel="prefetch"><link href="css/chunk-0cb5e9e6.css" rel="prefetch"><link href="css/chunk-1da7f23b.css" rel="prefetch"><link href="css/chunk-247d6284.css" rel="prefetch"><link href="css/chunk-330a7e95.css" rel="prefetch"><link href="css/chunk-3d13e56e.css" rel="prefetch"><link href="css/chunk-43748c28.css" rel="prefetch"><link href="css/chunk-49fbda44.css" rel="prefetch"><link href="css/chunk-e039e75a.css" rel="prefetch"><link href="css/chunk-fef965c2.css" rel="prefetch"><link href="js/chunk-016c45cf.js" rel="prefetch"><link href="js/chunk-08f9fbe8.js" rel="prefetch"><link href="js/chunk-0cb5e9e6.js" rel="prefetch"><link href="js/chunk-1da7f23b.js" rel="prefetch"><link href="js/chunk-247d6284.js" rel="prefetch"><link href="js/chunk-330a7e95.js" rel="prefetch"><link href="js/chunk-3d13e56e.js" rel="prefetch"><link href="js/chunk-43748c28.js" rel="prefetch"><link href="js/chunk-49fbda44.js" rel="prefetch"><link href="js/chunk-b241c712.js" rel="prefetch"><link href="js/chunk-e039e75a.js" rel="prefetch"><link href="js/chunk-fef965c2.js" rel="prefetch"><link href="css/app.css" rel="preload" as="style"><link href="css/chunk-vendors.css" rel="preload" as="style"><link href="js/app.js" rel="preload" as="script"><link href="js/chunk-vendors.js" rel="preload" as="script"><link href="css/chunk-vendors.css" rel="stylesheet"><link href="css/app.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but revsuit-frontend doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.js"></script><script src="js/app.js"></script></body></html>
|
||||||
@@ -148,6 +148,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
if (localStorage.getItem("autoRefresh") === null) {
|
||||||
|
this.autoRefresh = true;
|
||||||
|
}
|
||||||
if (this.autoRefresh && this.isLogMode) {
|
if (this.autoRefresh && this.isLogMode) {
|
||||||
this.timing()
|
this.timing()
|
||||||
}
|
}
|
||||||
@@ -176,8 +179,6 @@ export default {
|
|||||||
isLogMode(val) {
|
isLogMode(val) {
|
||||||
if (!val) {
|
if (!val) {
|
||||||
clearInterval(this.timer)
|
clearInterval(this.timer)
|
||||||
} else {
|
|
||||||
this.timing()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
refreshInterval(val) {
|
refreshInterval(val) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-table
|
<a-table
|
||||||
|
style="overflow-x: auto;"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="data"
|
:data-source="data"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -139,14 +140,9 @@ export default {
|
|||||||
this.pagination = pagination;
|
this.pagination = pagination;
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.$notification.error({
|
if (e.response.status !== 403) {
|
||||||
message: 'Unknown error: ' + e.response.status,
|
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||||
style: {
|
}
|
||||||
width: '100px',
|
|
||||||
marginLeft: `${335 - 600}px`,
|
|
||||||
},
|
|
||||||
duration: 4
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-table
|
<a-table
|
||||||
|
style="overflow-x: auto;"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="data"
|
:data-source="data"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -228,14 +229,9 @@ export default {
|
|||||||
this.pagination = pagination;
|
this.pagination = pagination;
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.$notification.error({
|
if (e.response.status !== 403) {
|
||||||
message: 'Unknown error: ' + e.response.status,
|
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||||
style: {
|
}
|
||||||
width: '100px',
|
|
||||||
marginLeft: `${335 - 600}px`,
|
|
||||||
},
|
|
||||||
duration: 4
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-table
|
<a-table
|
||||||
|
style="overflow-x: auto;"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="data"
|
:data-source="data"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -104,9 +105,9 @@ const columns = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'PATH',
|
title: 'URI',
|
||||||
dataIndex: 'path',
|
dataIndex: 'uri',
|
||||||
key: 'path',
|
key: 'uri',
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
scopedSlots: {
|
scopedSlots: {
|
||||||
filterDropdown: 'filterDropdown',
|
filterDropdown: 'filterDropdown',
|
||||||
@@ -173,14 +174,9 @@ export default {
|
|||||||
this.pagination = pagination;
|
this.pagination = pagination;
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.$notification.error({
|
if (e.response.status !== 403) {
|
||||||
message: 'Unknown error: ' + e.response.status,
|
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||||
style: {
|
}
|
||||||
width: '100px',
|
|
||||||
marginLeft: `${335 - 600}px`,
|
|
||||||
},
|
|
||||||
duration: 4
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-table
|
<a-table
|
||||||
|
style="overflow-x: auto;"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="data"
|
:data-source="data"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -120,6 +121,15 @@ const columns = [
|
|||||||
filterIcon: 'filterIcon',
|
filterIcon: 'filterIcon',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'SCHEMA',
|
||||||
|
dataIndex: 'schema',
|
||||||
|
key: 'schema',
|
||||||
|
scopedSlots: {
|
||||||
|
filterDropdown: 'filterDropdown',
|
||||||
|
filterIcon: 'filterIcon',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'LOAD DATA',
|
title: 'LOAD DATA',
|
||||||
dataIndex: 'load_local_data',
|
dataIndex: 'load_local_data',
|
||||||
@@ -216,14 +226,9 @@ export default {
|
|||||||
this.pagination = pagination;
|
this.pagination = pagination;
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.$notification.error({
|
if (e.response.status !== 403) {
|
||||||
message: 'Unknown error: ' + e.response.status,
|
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||||
style: {
|
}
|
||||||
width: '100px',
|
|
||||||
marginLeft: `${335 - 600}px`,
|
|
||||||
},
|
|
||||||
duration: 4
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-table
|
<a-table
|
||||||
|
style="overflow-x: auto;"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="data"
|
:data-source="data"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -109,7 +110,7 @@ export default {
|
|||||||
onShowSizeChange: (current, size) => {
|
onShowSizeChange: (current, size) => {
|
||||||
store.pageSize = size
|
store.pageSize = size
|
||||||
}
|
}
|
||||||
}, filters: {},
|
}, filters: {},
|
||||||
order: "desc",
|
order: "desc",
|
||||||
loading: false,
|
loading: false,
|
||||||
columns,
|
columns,
|
||||||
@@ -140,14 +141,9 @@ export default {
|
|||||||
this.pagination = pagination;
|
this.pagination = pagination;
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.$notification.error({
|
if (e.response.status !== 403) {
|
||||||
message: 'Unknown error: ' + e.response.status,
|
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||||
style: {
|
}
|
||||||
width: '100px',
|
|
||||||
marginLeft: `${335 - 600}px`,
|
|
||||||
},
|
|
||||||
duration: 4
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
@close="closeDrawer"
|
@close="closeDrawer"
|
||||||
>
|
>
|
||||||
<a-form-model :model="form" ref="form" layout="vertical" @submit="handleSubmit">
|
<a-form-model :model="form" ref="form" layout="vertical" @submit="handleSubmit">
|
||||||
<BasicRule :form="form" :readOnly="formReadOnly"/>
|
<BasicRule :form="form" :readOnly="formReadOnly" flagFormat="domain"/>
|
||||||
<a-row :gutter="24">
|
<a-row :gutter="24">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-model-item label="Type" :rules="rules.type">
|
<a-form-model-item label="Type" :rules="rules.type">
|
||||||
@@ -90,6 +90,7 @@
|
|||||||
</a-drawer>
|
</a-drawer>
|
||||||
<!-- rule table -->
|
<!-- rule table -->
|
||||||
<a-table
|
<a-table
|
||||||
|
style="overflow-x: auto;"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="data"
|
:data-source="data"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -296,7 +297,7 @@ export default {
|
|||||||
onShowSizeChange: (current, size) => {
|
onShowSizeChange: (current, size) => {
|
||||||
store.pageSize = size
|
store.pageSize = size
|
||||||
}
|
}
|
||||||
}, filters: {},
|
}, filters: {},
|
||||||
loading: false,
|
loading: false,
|
||||||
columns,
|
columns,
|
||||||
colors,
|
colors,
|
||||||
@@ -332,14 +333,9 @@ export default {
|
|||||||
this.pagination = pagination;
|
this.pagination = pagination;
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.$notification.error({
|
if (e.response.status !== 403) {
|
||||||
message: 'Unknown error: ' + e.response.status,
|
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||||
style: {
|
}
|
||||||
width: '100px',
|
|
||||||
marginLeft: `${335 - 600}px`,
|
|
||||||
},
|
|
||||||
duration: 4
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
clickSwitch(record, prop) {
|
clickSwitch(record, prop) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
@close="closeDrawer"
|
@close="closeDrawer"
|
||||||
>
|
>
|
||||||
<a-form-model :model="form" ref="form" layout="vertical" @submit="handleSubmit">
|
<a-form-model :model="form" ref="form" layout="vertical" @submit="handleSubmit">
|
||||||
<BasicRule :form="form" :readOnly="formReadOnly"/>
|
<BasicRule :form="form" :readOnly="formReadOnly" flagFormat="user or password"/>
|
||||||
<a-row :gutter="24">
|
<a-row :gutter="24">
|
||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
<a-form-model-item>
|
<a-form-model-item>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<a-input
|
<a-input
|
||||||
v-model="form.pasv_address"
|
v-model="form.pasv_address"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
placeholder="Use external IP by default"
|
placeholder="Use `external_ip:pasv_port` by default"
|
||||||
:readOnly="formReadOnly"
|
:readOnly="formReadOnly"
|
||||||
/>
|
/>
|
||||||
</a-form-model-item>
|
</a-form-model-item>
|
||||||
@@ -78,6 +78,7 @@
|
|||||||
</a-drawer>
|
</a-drawer>
|
||||||
<!-- rule table -->
|
<!-- rule table -->
|
||||||
<a-table
|
<a-table
|
||||||
|
style="overflow-x: auto;"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="data"
|
:data-source="data"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -267,14 +268,9 @@ export default {
|
|||||||
this.pagination = pagination;
|
this.pagination = pagination;
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.$notification.error({
|
if (e.response.status !== 403) {
|
||||||
message: 'Unknown error: ' + e.response.status,
|
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||||
style: {
|
}
|
||||||
width: '100px',
|
|
||||||
marginLeft: `${335 - 600}px`,
|
|
||||||
},
|
|
||||||
duration: 4
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
clickSwitch(record, prop) {
|
clickSwitch(record, prop) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
@close="closeDrawer"
|
@close="closeDrawer"
|
||||||
>
|
>
|
||||||
<a-form-model :model="form" ref="form" layout="vertical" @submit="handleSubmit">
|
<a-form-model :model="form" ref="form" layout="vertical" @submit="handleSubmit">
|
||||||
<BasicRule :form="form" :readOnly="formReadOnly"/>
|
<BasicRule :form="form" :readOnly="formReadOnly" flagFormat="raw message"/>
|
||||||
<a-row :gutter="24">
|
<a-row :gutter="24">
|
||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
<a-form-model-item :rules="rules.response_status_code"
|
<a-form-model-item :rules="rules.response_status_code"
|
||||||
@@ -130,6 +130,7 @@
|
|||||||
</a-drawer>
|
</a-drawer>
|
||||||
<!-- rule table -->
|
<!-- rule table -->
|
||||||
<a-table
|
<a-table
|
||||||
|
style="overflow-x: auto;"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="data"
|
:data-source="data"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -374,14 +375,9 @@ export default {
|
|||||||
this.pagination = pagination;
|
this.pagination = pagination;
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.$notification.error({
|
if (e.response.status !== 403) {
|
||||||
message: 'Unknown error: ' + e.response.status,
|
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||||
style: {
|
}
|
||||||
width: '100px',
|
|
||||||
marginLeft: `${335 - 600}px`,
|
|
||||||
},
|
|
||||||
duration: 4
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
clickSwitch(record, prop) {
|
clickSwitch(record, prop) {
|
||||||
|
|||||||
@@ -100,6 +100,7 @@
|
|||||||
</a-drawer>
|
</a-drawer>
|
||||||
<!-- rule table -->
|
<!-- rule table -->
|
||||||
<a-table
|
<a-table
|
||||||
|
style="overflow-x: auto;"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="data"
|
:data-source="data"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -300,7 +301,7 @@ export default {
|
|||||||
this.$notification.error({
|
this.$notification.error({
|
||||||
message: 'Unknown error: ' + e.response.status,
|
message: 'Unknown error: ' + e.response.status,
|
||||||
style: {
|
style: {
|
||||||
width: '100px',
|
width: '400px',
|
||||||
marginLeft: `${335 - 600}px`,
|
marginLeft: `${335 - 600}px`,
|
||||||
},
|
},
|
||||||
duration: 4
|
duration: 4
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
@close="closeDrawer"
|
@close="closeDrawer"
|
||||||
>
|
>
|
||||||
<a-form-model :model="form" ref="form" layout="vertical" @submit="handleSubmit">
|
<a-form-model :model="form" ref="form" layout="vertical" @submit="handleSubmit">
|
||||||
<BasicRule :form="form" :readOnly="formReadOnly"/>
|
<BasicRule :form="form" :readOnly="formReadOnly" flagFormat="path"/>
|
||||||
</a-form-model>
|
</a-form-model>
|
||||||
<div
|
<div
|
||||||
:style="{
|
:style="{
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
</a-drawer>
|
</a-drawer>
|
||||||
<!-- rule table -->
|
<!-- rule table -->
|
||||||
<a-table
|
<a-table
|
||||||
|
style="overflow-x: auto;"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data-source="data"
|
:data-source="data"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -193,7 +194,8 @@ export default {
|
|||||||
onShowSizeChange: (current, size) => {
|
onShowSizeChange: (current, size) => {
|
||||||
store.pageSize = size
|
store.pageSize = size
|
||||||
}
|
}
|
||||||
}, filters: {},
|
},
|
||||||
|
filters: {},
|
||||||
loading: false,
|
loading: false,
|
||||||
columns,
|
columns,
|
||||||
form: {},
|
form: {},
|
||||||
@@ -225,14 +227,9 @@ export default {
|
|||||||
this.pagination = pagination;
|
this.pagination = pagination;
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.$notification.error({
|
if (e.response.status !== 403) {
|
||||||
message: 'Unknown error: ' + e.response.status,
|
this.$message.error('Unknown error with status code: ' + e.response.status)
|
||||||
style: {
|
}
|
||||||
width: '100px',
|
|
||||||
marginLeft: `${335 - 600}px`,
|
|
||||||
},
|
|
||||||
duration: 4
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
clickSwitch(record, prop) {
|
clickSwitch(record, prop) {
|
||||||
|
|||||||
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 895 KiB |
|
After Width: | Height: | Size: 135 KiB |
@@ -1,12 +0,0 @@
|
|||||||
language: go
|
|
||||||
go:
|
|
||||||
- "1.12"
|
|
||||||
- tip
|
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
before_install:
|
|
||||||
- go get github.com/mattn/goveralls
|
|
||||||
install:
|
|
||||||
- go get -t ./...
|
|
||||||
script:
|
|
||||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
all: fmt vet lint
|
|
||||||
|
|
||||||
fmt:
|
|
||||||
go fmt .
|
|
||||||
|
|
||||||
vet:
|
|
||||||
go vet .
|
|
||||||
|
|
||||||
lint:
|
|
||||||
golint .
|
|
||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var recordChan = make(chan BaseRecord, 100)
|
var recordChan = make(chan BaseRecord, 1000)
|
||||||
|
|
||||||
type Record interface {
|
type Record interface {
|
||||||
GetFlag() string
|
GetFlag() string
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func (s *Server) getRules() []*Rule {
|
|||||||
return s.rules
|
return s.rules
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) updateRules() error {
|
func (s *Server) UpdateRules() error {
|
||||||
db := database.DB.Model(new(Rule))
|
db := database.DB.Model(new(Rule))
|
||||||
defer s.rulesLock.Unlock()
|
defer s.rulesLock.Unlock()
|
||||||
s.rulesLock.Lock()
|
s.rulesLock.Lock()
|
||||||
@@ -209,7 +209,7 @@ func (s *Server) Run() {
|
|||||||
s.livingLock.Unlock()
|
s.livingLock.Unlock()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := s.updateRules(); err != nil {
|
if err := s.UpdateRules(); err != nil {
|
||||||
log.Error(err.Error())
|
log.Error(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (r *Rule) CreateOrUpdate() (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = GetServer().updateRules()
|
err = GetServer().UpdateRules()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ func (r *Rule) Delete() (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = GetServer().updateRules()
|
err = GetServer().UpdateRules()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ func (s *Server) getRules() []*Rule {
|
|||||||
return s.rules
|
return s.rules
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) updateRules() error {
|
func (s *Server) UpdateRules() error {
|
||||||
db := database.DB.Model(new(Rule))
|
db := database.DB.Model(new(Rule))
|
||||||
defer s.rulesLock.Unlock()
|
defer s.rulesLock.Unlock()
|
||||||
s.rulesLock.Lock()
|
s.rulesLock.Lock()
|
||||||
@@ -94,6 +94,7 @@ func (s *Server) authenticate(user, password string) (_rule *Rule, flag, flagGro
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getPasvAddressFromCache(ip, pasvAddressTpl string) (pasvAddress string) {
|
func (s *Server) getPasvAddressFromCache(ip, pasvAddressTpl string) (pasvAddress string) {
|
||||||
|
pasvAddress = pasvAddressTpl
|
||||||
if strings.Contains(pasvAddressTpl, ",") {
|
if strings.Contains(pasvAddressTpl, ",") {
|
||||||
values, ok := rebindingCache.Get(ip)
|
values, ok := rebindingCache.Get(ip)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -202,8 +203,8 @@ loop:
|
|||||||
if pasvAddress = s.getPasvAddressFromCache(ip, _rule.PasvAddress); pasvAddress == "" {
|
if pasvAddress = s.getPasvAddressFromCache(ip, _rule.PasvAddress); pasvAddress == "" {
|
||||||
pasvAddress = fmt.Sprintf("%s:%d", s.pasvIP, s.PasvPort)
|
pasvAddress = fmt.Sprintf("%s:%d", s.pasvIP, s.PasvPort)
|
||||||
}
|
}
|
||||||
|
pasvAddress = rule.CompileTpl(pasvAddress, vars)
|
||||||
isRedirect = rule.CompileTpl(pasvAddress, vars) != fmt.Sprintf("%s:%d", s.pasvIP, s.PasvPort)
|
isRedirect = pasvAddress != fmt.Sprintf("%s:%d", s.pasvIP, s.PasvPort)
|
||||||
case "SIZE":
|
case "SIZE":
|
||||||
path += strings.TrimLeft(args, "/")
|
path += strings.TrimLeft(args, "/")
|
||||||
if _rule == nil || isRedirect || len(_rule.Data) == 0 {
|
if _rule == nil || isRedirect || len(_rule.Data) == 0 {
|
||||||
@@ -217,8 +218,6 @@ loop:
|
|||||||
case "PASV":
|
case "PASV":
|
||||||
//Just so that ide does not prompt that there may be a nil value
|
//Just so that ide does not prompt that there may be a nil value
|
||||||
if _rule != nil {
|
if _rule != nil {
|
||||||
// return rule's pasv_address or default pasv address
|
|
||||||
pasvAddress := rule.CompileTpl(pasvAddress, vars)
|
|
||||||
pasvIP, pasvPort, err := net.SplitHostPort(pasvAddress)
|
pasvIP, pasvPort, err := net.SplitHostPort(pasvAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("FTP failed to split rule[id:%d] pasv_address(%s) :%s", _rule.ID, pasvAddress, err)
|
log.Warn("FTP failed to split rule[id:%d] pasv_address(%s) :%s", _rule.ID, pasvAddress, err)
|
||||||
@@ -365,7 +364,7 @@ func (s *Server) Run() {
|
|||||||
s.livingLock.Unlock()
|
s.livingLock.Unlock()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := s.updateRules(); err != nil {
|
if err := s.UpdateRules(); err != nil {
|
||||||
log.Error(err.Error())
|
log.Error(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (r *Rule) CreateOrUpdate() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetServer().updateRules()
|
return GetServer().UpdateRules()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes the ftp rule in database and ruleSet
|
// Delete deletes the ftp rule in database and ruleSet
|
||||||
@@ -65,7 +65,7 @@ func (r *Rule) Delete() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetServer().updateRules()
|
return GetServer().UpdateRules()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListRules lists all ftp rules those satisfy the filter
|
// ListRules lists all ftp rules those satisfy the filter
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func (s *Server) getRules() []*Rule {
|
|||||||
return s.rules
|
return s.rules
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) updateRules() error {
|
func (s *Server) UpdateRules() error {
|
||||||
db := database.DB.Model(new(Rule))
|
db := database.DB.Model(new(Rule))
|
||||||
defer s.rulesLock.Unlock()
|
defer s.rulesLock.Unlock()
|
||||||
s.rulesLock.Lock()
|
s.rulesLock.Lock()
|
||||||
@@ -105,11 +105,11 @@ func (s *Server) ConnectionClosed(c *vmysql.Conn) {
|
|||||||
var clientName, clientOS, flag, flagGroup string
|
var clientName, clientOS, flag, flagGroup string
|
||||||
|
|
||||||
user := c.User
|
user := c.User
|
||||||
|
schema := c.SchemaName
|
||||||
supportLoadLocalData := c.SupportLoadDataLocal
|
supportLoadLocalData := c.SupportLoadDataLocal
|
||||||
|
|
||||||
cr, ok := s.connRulePool.Load(c.ConnectionID)
|
cr, ok := s.connRulePool.Load(c.ConnectionID)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Warn("MySQL Connection rule(%d) not match flag", c.ConnectionID)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ func (s *Server) ConnectionClosed(c *vmysql.Conn) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := newRecord(_rule, flag, user, clientName, clientOS, ip, qqwry.Area(ip), supportLoadLocalData, files)
|
r, err := newRecord(_rule, flag, user, schema, clientName, clientOS, ip, qqwry.Area(ip), supportLoadLocalData, files)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("MySQL record[rule_id: %s] created failed: %s", _rule.Name, err)
|
log.Warn("MySQL record[rule_id: %s] created failed: %s", _rule.Name, err)
|
||||||
return
|
return
|
||||||
@@ -288,7 +288,7 @@ func (s *Server) Run() {
|
|||||||
s.Enable = false
|
s.Enable = false
|
||||||
s.livingLock.Unlock()
|
s.livingLock.Unlock()
|
||||||
}()
|
}()
|
||||||
if err := s.updateRules(); err != nil {
|
if err := s.UpdateRules(); err != nil {
|
||||||
log.Error(err.Error())
|
log.Error(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type Record struct {
|
|||||||
record.BaseRecord
|
record.BaseRecord
|
||||||
Username string `gorm:"index" form:"username" json:"username" notice:"username"`
|
Username string `gorm:"index" form:"username" json:"username" notice:"username"`
|
||||||
ClientName string `gorm:"index" form:"client_name" json:"client_name" notice:"client_name"`
|
ClientName string `gorm:"index" form:"client_name" json:"client_name" notice:"client_name"`
|
||||||
|
Schema string `gorm:"index" form:"schema" json:"schema" notice:"schema"`
|
||||||
ClientOS string `gorm:"index" form:"client_os" json:"client_os" notice:"client_os"`
|
ClientOS string `gorm:"index" form:"client_os" json:"client_os" notice:"client_os"`
|
||||||
LoadLocalData bool `gorm:"index" form:"load_local_data" json:"load_local_data" notice:"load_local_data"`
|
LoadLocalData bool `gorm:"index" form:"load_local_data" json:"load_local_data" notice:"load_local_data"`
|
||||||
Files []file.MySQLFile `form:"-" json:"files" notice:"-"`
|
Files []file.MySQLFile `form:"-" json:"files" notice:"-"`
|
||||||
@@ -31,7 +32,7 @@ func (r Record) Notice() {
|
|||||||
notice.Notice(r)
|
notice.Notice(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRecord(rule *Rule, flag, username, clientName, clientOS, remoteIp, ipArea string, supportLoadLocalData bool, files []file.MySQLFile) (r *Record, err error) {
|
func newRecord(rule *Rule, flag, username, schema, clientName, clientOS, remoteIp, ipArea string, supportLoadLocalData bool, files []file.MySQLFile) (r *Record, err error) {
|
||||||
r = &Record{
|
r = &Record{
|
||||||
BaseRecord: record.BaseRecord{
|
BaseRecord: record.BaseRecord{
|
||||||
Flag: flag,
|
Flag: flag,
|
||||||
@@ -40,6 +41,7 @@ func newRecord(rule *Rule, flag, username, clientName, clientOS, remoteIp, ipAre
|
|||||||
RequestTime: time.Now(),
|
RequestTime: time.Now(),
|
||||||
},
|
},
|
||||||
Username: username,
|
Username: username,
|
||||||
|
Schema: schema,
|
||||||
ClientName: clientName,
|
ClientName: clientName,
|
||||||
ClientOS: clientOS,
|
ClientOS: clientOS,
|
||||||
LoadLocalData: supportLoadLocalData,
|
LoadLocalData: supportLoadLocalData,
|
||||||
@@ -90,6 +92,9 @@ func ListRecords(c *gin.Context) {
|
|||||||
if mysqlRecord.ClientName != "" {
|
if mysqlRecord.ClientName != "" {
|
||||||
db.Where("client_name like ?", "%"+mysqlRecord.ClientName)
|
db.Where("client_name like ?", "%"+mysqlRecord.ClientName)
|
||||||
}
|
}
|
||||||
|
if mysqlRecord.Schema != "" {
|
||||||
|
db.Where("schema like ?", "%"+mysqlRecord.Schema)
|
||||||
|
}
|
||||||
if c.Query("load_local_data") != "" {
|
if c.Query("load_local_data") != "" {
|
||||||
if c.Query("load_local_data") == "true" {
|
if c.Query("load_local_data") == "true" {
|
||||||
db.Where("load_local_data = ?", true)
|
db.Where("load_local_data = ?", true)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func (r *Rule) CreateOrUpdate() (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = GetServer().updateRules()
|
err = GetServer().UpdateRules()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ func (r *Rule) Delete() (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = GetServer().updateRules()
|
err = GetServer().UpdateRules()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ func (s *Server) getRules() []*Rule {
|
|||||||
return s.rules
|
return s.rules
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) updateRules() error {
|
func (s *Server) UpdateRules() error {
|
||||||
db := database.DB.Model(new(Rule))
|
db := database.DB.Model(new(Rule))
|
||||||
defer s.rulesLock.Unlock()
|
defer s.rulesLock.Unlock()
|
||||||
s.rulesLock.Lock()
|
s.rulesLock.Lock()
|
||||||
@@ -116,7 +116,7 @@ func (s *Server) Restart() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Run() {
|
func (s *Server) Run() {
|
||||||
if err := s.updateRules(); err != nil {
|
if err := s.UpdateRules(); err != nil {
|
||||||
log.Warn(err.Error())
|
log.Warn(err.Error())
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
@@ -156,8 +156,12 @@ func compileTpl(c *gin.Context, tpl string, vars map[string]string) (compiled st
|
|||||||
|
|
||||||
func (s *Server) Receive(c *gin.Context) {
|
func (s *Server) Receive(c *gin.Context) {
|
||||||
u := c.Request.URL.String()
|
u := c.Request.URL.String()
|
||||||
|
raw, err := getRawRequest(c.Request)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err.Error())
|
||||||
|
}
|
||||||
for _, _rule := range s.getRules() {
|
for _, _rule := range s.getRules() {
|
||||||
flag, flagGroup, vars := _rule.Match(u)
|
flag, flagGroup, vars := _rule.Match(string(raw))
|
||||||
if flag == "" {
|
if flag == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -172,11 +176,6 @@ func (s *Server) Receive(c *gin.Context) {
|
|||||||
delete(c.Request.Header, s.IpHeader)
|
delete(c.Request.Header, s.IpHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
raw, err := getRawRequest(c.Request)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new record
|
// create new record
|
||||||
r, err := NewRecord(_rule, flag, c.Request.Method, u, ip, area, string(raw))
|
r, err := NewRecord(_rule, flag, c.Request.Method, u, ip, area, string(raw))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ var _ record.Record = (*Record)(nil)
|
|||||||
|
|
||||||
type Record struct {
|
type Record struct {
|
||||||
Method string `gorm:"index" form:"method" json:"method"`
|
Method string `gorm:"index" form:"method" json:"method"`
|
||||||
Path string `form:"path" json:"path"`
|
URI string `form:"uri" json:"uri"`
|
||||||
record.BaseRecord
|
record.BaseRecord
|
||||||
RawRequest string `json:"raw_request" notice:"-"`
|
RawRequest string `json:"raw_request" notice:"-"`
|
||||||
Rule Rule `gorm:"foreignKey:RuleName;references:Name;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" form:"-" json:"-" notice:"-"`
|
Rule Rule `gorm:"foreignKey:RuleName;references:Name;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" form:"-" json:"-" notice:"-"`
|
||||||
@@ -37,7 +37,7 @@ func NewRecord(rule *Rule, flag, method, url, ip, area, raw string) (r *Record,
|
|||||||
RequestTime: time.Now(),
|
RequestTime: time.Now(),
|
||||||
},
|
},
|
||||||
Method: method,
|
Method: method,
|
||||||
Path: url,
|
URI: url,
|
||||||
RawRequest: raw,
|
RawRequest: raw,
|
||||||
Rule: *rule,
|
Rule: *rule,
|
||||||
}
|
}
|
||||||
@@ -78,8 +78,8 @@ func ListRecords(c *gin.Context) {
|
|||||||
if httpRecord.Method != "" {
|
if httpRecord.Method != "" {
|
||||||
db.Where("method = ?", httpRecord.Method)
|
db.Where("method = ?", httpRecord.Method)
|
||||||
}
|
}
|
||||||
if httpRecord.Path != "" {
|
if httpRecord.URI != "" {
|
||||||
db.Where("path like ?", "%"+httpRecord.Path+"%")
|
db.Where("path like ?", "%"+httpRecord.URI+"%")
|
||||||
}
|
}
|
||||||
if httpRecord.RemoteIP != "" {
|
if httpRecord.RemoteIP != "" {
|
||||||
db.Where("remote_ip = ?", httpRecord.RemoteIP)
|
db.Where("remote_ip = ?", httpRecord.RemoteIP)
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (r *Rule) CreateOrUpdate() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = GetServer().updateRules()
|
err = GetServer().UpdateRules()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ func (r *Rule) Delete() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = GetServer().updateRules()
|
err = GetServer().UpdateRules()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func (s *Server) getRules() []*Rule {
|
|||||||
return s.rules
|
return s.rules
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) updateRules() error {
|
func (s *Server) UpdateRules() error {
|
||||||
db := database.DB.Model(new(Rule))
|
db := database.DB.Model(new(Rule))
|
||||||
defer s.rulesLock.Unlock()
|
defer s.rulesLock.Unlock()
|
||||||
s.rulesLock.Lock()
|
s.rulesLock.Lock()
|
||||||
@@ -165,7 +165,7 @@ func (s *Server) Run() {
|
|||||||
s.livingLock.Unlock()
|
s.livingLock.Unlock()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := s.updateRules(); err != nil {
|
if err := s.UpdateRules(); err != nil {
|
||||||
log.Error(err.Error())
|
log.Error(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func (r *Rule) CreateOrUpdate() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = GetServer().updateRules()
|
err = GetServer().UpdateRules()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ func (r *Rule) Delete() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = GetServer().updateRules()
|
err = GetServer().UpdateRules()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/li4n0/revsuit/internal/record"
|
|
||||||
"github.com/li4n0/revsuit/internal/recycler"
|
"github.com/li4n0/revsuit/internal/recycler"
|
||||||
log "unknwon.dev/clog/v2"
|
log "unknwon.dev/clog/v2"
|
||||||
)
|
)
|
||||||
@@ -26,18 +25,15 @@ func ping(c *gin.Context) {
|
|||||||
c.String(200, "pong")
|
c.String(200, "pong")
|
||||||
}
|
}
|
||||||
|
|
||||||
func events(c *gin.Context) {
|
func (revsuit *Revsuit) events(c *gin.Context) {
|
||||||
log.Info("Receive client connection from %v", c.Request.RemoteAddr)
|
id := revsuit.addClient(c)
|
||||||
|
log.Info("Receive client[id:%d] connection from %v", id, c.Request.RemoteAddr)
|
||||||
c.Stream(func(w io.Writer) bool {
|
c.Stream(func(w io.Writer) bool {
|
||||||
select {
|
<-c.Writer.CloseNotify()
|
||||||
case <-c.Writer.CloseNotify():
|
return false
|
||||||
return false
|
|
||||||
case r := <-record.Channel():
|
|
||||||
c.SSEvent("message", r.GetFlag())
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
})
|
||||||
log.Info("Client %s disconnect", c.Request.RemoteAddr)
|
revsuit.removeClient(id)
|
||||||
|
log.Info("Client[id:%d, remote_addr:%s] disconnect", id, c.Request.RemoteAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func recovery(c *gin.Context) {
|
func recovery(c *gin.Context) {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func (revsuit *Revsuit) registerPlatformRouter() {
|
|||||||
|
|
||||||
//platform routers
|
//platform routers
|
||||||
api.GET("/auth", auth)
|
api.GET("/auth", auth)
|
||||||
api.GET("/events", events)
|
api.GET("/events", revsuit.events)
|
||||||
api.GET("/ping", ping)
|
api.GET("/ping", ping)
|
||||||
api.GET("/version", version)
|
api.GET("/version", version)
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@ func (revsuit *Revsuit) registerHttpRouter() {
|
|||||||
// init settings router group
|
// init settings router group
|
||||||
settingsGroup := revsuit.http.ApiGroup.Group("setting")
|
settingsGroup := revsuit.http.ApiGroup.Group("setting")
|
||||||
settingsGroup.GET("/exportRules", exportRules)
|
settingsGroup.GET("/exportRules", exportRules)
|
||||||
settingsGroup.POST("/importRules", importRules)
|
settingsGroup.POST("/importRules", revsuit.importRules)
|
||||||
settingsGroup.GET("/getPlatformConfig", revsuit.getPlatformConfig)
|
settingsGroup.GET("/getPlatformConfig", revsuit.getPlatformConfig)
|
||||||
settingsGroup.POST("/updatePlatformConfig", revsuit.updatePlatformConfig)
|
settingsGroup.POST("/updatePlatformConfig", revsuit.updatePlatformConfig)
|
||||||
settingsGroup.GET("/getDnsConfig", revsuit.getDnsConfig)
|
settingsGroup.GET("/getDnsConfig", revsuit.getDnsConfig)
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/li4n0/revsuit/internal/database"
|
"github.com/li4n0/revsuit/internal/database"
|
||||||
"github.com/li4n0/revsuit/internal/file"
|
"github.com/li4n0/revsuit/internal/file"
|
||||||
"github.com/li4n0/revsuit/internal/notice"
|
"github.com/li4n0/revsuit/internal/notice"
|
||||||
|
"github.com/li4n0/revsuit/internal/record"
|
||||||
"github.com/li4n0/revsuit/pkg/dns"
|
"github.com/li4n0/revsuit/pkg/dns"
|
||||||
"github.com/li4n0/revsuit/pkg/ftp"
|
"github.com/li4n0/revsuit/pkg/ftp"
|
||||||
"github.com/li4n0/revsuit/pkg/mysql"
|
"github.com/li4n0/revsuit/pkg/mysql"
|
||||||
@@ -14,7 +17,7 @@ import (
|
|||||||
log "unknwon.dev/clog/v2"
|
log "unknwon.dev/clog/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const VERSION = "0.1.2-beta"
|
const VERSION = "0.1.3-beta"
|
||||||
|
|
||||||
type Revsuit struct {
|
type Revsuit struct {
|
||||||
config *Config
|
config *Config
|
||||||
@@ -25,6 +28,29 @@ type Revsuit struct {
|
|||||||
mysql *mysql.Server
|
mysql *mysql.Server
|
||||||
rmi *rmi.Server
|
rmi *rmi.Server
|
||||||
ftp *ftp.Server
|
ftp *ftp.Server
|
||||||
|
|
||||||
|
clients map[int]*gin.Context
|
||||||
|
clientID int
|
||||||
|
clientsLock sync.RWMutex
|
||||||
|
clientsNum chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (revsuit *Revsuit) addClient(c *gin.Context) int {
|
||||||
|
revsuit.clientsLock.Lock()
|
||||||
|
defer revsuit.clientsLock.Unlock()
|
||||||
|
|
||||||
|
revsuit.clientID++
|
||||||
|
revsuit.clients[revsuit.clientID] = c
|
||||||
|
revsuit.clientsNum <- struct{}{}
|
||||||
|
return revsuit.clientID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (revsuit *Revsuit) removeClient(id int) {
|
||||||
|
revsuit.clientsLock.Lock()
|
||||||
|
defer revsuit.clientsLock.Unlock()
|
||||||
|
|
||||||
|
delete(revsuit.clients, id)
|
||||||
|
<-revsuit.clientsNum
|
||||||
}
|
}
|
||||||
|
|
||||||
func initDatabase(dsn string) {
|
func initDatabase(dsn string) {
|
||||||
@@ -188,6 +214,8 @@ func New(c *Config) *Revsuit {
|
|||||||
s.dns.SetServerIP(c.ExternalIP)
|
s.dns.SetServerIP(c.ExternalIP)
|
||||||
s.ftp.SetPasvIP(c.ExternalIP)
|
s.ftp.SetPasvIP(c.ExternalIP)
|
||||||
}
|
}
|
||||||
|
s.clients = make(map[int]*gin.Context)
|
||||||
|
s.clientsNum = make(chan struct{}, 100)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,6 +235,17 @@ func (revsuit *Revsuit) Run() {
|
|||||||
if revsuit.ftp != nil && revsuit.ftp.Enable {
|
if revsuit.ftp != nil && revsuit.ftp.Enable {
|
||||||
go revsuit.ftp.Run()
|
go revsuit.ftp.Run()
|
||||||
}
|
}
|
||||||
|
go func() {
|
||||||
|
for r := range record.Channel() {
|
||||||
|
<-revsuit.clientsNum
|
||||||
|
revsuit.clientsLock.RLock()
|
||||||
|
for _, client := range revsuit.clients {
|
||||||
|
client.SSEvent("message", r.GetFlag())
|
||||||
|
client.Writer.Flush()
|
||||||
|
}
|
||||||
|
revsuit.clientsNum <- struct{}{}
|
||||||
|
revsuit.clientsLock.RUnlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
revsuit.http.Run()
|
revsuit.http.Run()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func exportRules(c *gin.Context) {
|
|||||||
c.String(200, string(out))
|
c.String(200, string(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
func importRules(c *gin.Context) {
|
func (revsuit *Revsuit) importRules(c *gin.Context) {
|
||||||
var (
|
var (
|
||||||
db = database.DB
|
db = database.DB
|
||||||
rules Rules
|
rules Rules
|
||||||
@@ -87,48 +87,58 @@ func importRules(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, rule := range rules.Http {
|
for _, rule := range rules.Http {
|
||||||
err := db.Model(&rhttp.Rule{}).Create(&rule).Error
|
if err := db.Model(&rhttp.Rule{}).Create(&rule).Error; err != nil {
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, errors.Wrap(err, fmt.Sprintf("http rule[%s]", rule.Name)).Error())
|
errs = append(errs, errors.Wrap(err, fmt.Sprintf("http rule[%s]", rule.Name)).Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
count += 1
|
count++
|
||||||
|
}
|
||||||
|
if err := revsuit.http.UpdateRules(); err != nil {
|
||||||
|
errs = append(errs, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rule := range rules.Dns {
|
for _, rule := range rules.Dns {
|
||||||
err := db.Model(&dns.Rule{}).Create(&rule).Error
|
if err := db.Model(&dns.Rule{}).Create(&rule).Error; err != nil {
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, errors.Wrap(err, fmt.Sprintf("dns rule[%s]", rule.Name)).Error())
|
errs = append(errs, errors.Wrap(err, fmt.Sprintf("dns rule[%s]", rule.Name)).Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
count += 1
|
count++
|
||||||
|
}
|
||||||
|
if err := revsuit.dns.UpdateRules(); err != nil {
|
||||||
|
errs = append(errs, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rule := range rules.Mysql {
|
for _, rule := range rules.Mysql {
|
||||||
err := db.Model(&mysql.Rule{}).Create(&rule).Error
|
if err := db.Model(&mysql.Rule{}).Create(&rule).Error; err != nil {
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, errors.Wrap(err, fmt.Sprintf("mysql rule[%s]", rule.Name)).Error())
|
errs = append(errs, errors.Wrap(err, fmt.Sprintf("mysql rule[%s]", rule.Name)).Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
count += 1
|
count++
|
||||||
|
}
|
||||||
|
if err := revsuit.mysql.UpdateRules(); err != nil {
|
||||||
|
errs = append(errs, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rule := range rules.Rmi {
|
for _, rule := range rules.Rmi {
|
||||||
err := db.Model(&rmi.Rule{}).Create(&rule).Error
|
if err := db.Model(&rmi.Rule{}).Create(&rule).Error; err != nil {
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, errors.Wrap(err, fmt.Sprintf("rmi rule[%s]", rule.Name)).Error())
|
errs = append(errs, errors.Wrap(err, fmt.Sprintf("rmi rule[%s]", rule.Name)).Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
count += 1
|
count++
|
||||||
|
}
|
||||||
|
if err := revsuit.rmi.UpdateRules(); err != nil {
|
||||||
|
errs = append(errs, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rule := range rules.Ftp {
|
for _, rule := range rules.Ftp {
|
||||||
err := db.Model(&ftp.Rule{}).Create(&rule).Error
|
if err := db.Model(&ftp.Rule{}).Create(&rule).Error; err != nil {
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, errors.Wrap(err, fmt.Sprintf("ftp rule[%s]", rule.Name)).Error())
|
errs = append(errs, errors.Wrap(err, fmt.Sprintf("ftp rule[%s]", rule.Name)).Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
count += 1
|
count++
|
||||||
|
}
|
||||||
|
if err := revsuit.ftp.UpdateRules(); err != nil {
|
||||||
|
errs = append(errs, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
|
|||||||