@ -1,61 +0,0 @@
|
||||
{
|
||||
"files": [
|
||||
"README.md"
|
||||
],
|
||||
"imageSize": 100,
|
||||
"commit": false,
|
||||
"contributors": [
|
||||
{
|
||||
"login": "antonioag95",
|
||||
"name": "antonioag95",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/30556866?v=4",
|
||||
"profile": "https://github.com/antonioag95",
|
||||
"contributions": [
|
||||
"test",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "tonjo",
|
||||
"name": "tonjo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/4726289?v=4",
|
||||
"profile": "https://github.com/tonjo",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "reafian",
|
||||
"name": "Richard Newton",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/11992416?v=4",
|
||||
"profile": "https://github.com/reafian",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "davejlong",
|
||||
"name": "David Long",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/175317?v=4",
|
||||
"profile": "http://www.davejlong.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "marneu",
|
||||
"name": "Markus Neubauer",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/5978293?v=4",
|
||||
"profile": "http://www.std-soft.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
"projectName": "WGDashboard",
|
||||
"projectOwner": "donaldzou",
|
||||
"repoType": "github",
|
||||
"repoHost": "https://github.com",
|
||||
"skipCi": true
|
||||
}
|
@ -8,7 +8,9 @@ ARG wg_port="51820"
|
||||
# Following ENV variables are changable on container runtime because /entrypoint.sh handles that. See compose.yaml for more info.
|
||||
ENV TZ="Europe/Amsterdam"
|
||||
ENV global_dns="1.1.1.1"
|
||||
|
||||
ENV enable="none"
|
||||
|
||||
ENV isolate="none"
|
||||
ENV public_ip="0.0.0.0"
|
||||
|
||||
@ -55,4 +57,5 @@ COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
# Exposing the default WireGuard Dashboard port for web access.
|
||||
EXPOSE 10086
|
||||
ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
|
||||
|
||||
ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
|
@ -93,7 +93,9 @@ To build the image yourself, you need to do a couple things:<br>
|
||||
1. Clone the Github repository containing the source code of WGDashboard including the docker directory. For example do: `git clone https://github.com/donaldzou/WGDashboard.git`
|
||||
1. Navigate into the cloned repository.
|
||||
1. (Make sure you have Docker correctly installed, if not: [Click here](https://docs.docker.com/engine/install/)) and run: `docker build . -t <Image name>:<Image tag>` as an example: `docker build . -t dselen/wgdashboard:latest`.<br>
|
||||
|
||||
This will make Docker compile the image from the resources in the directory you mention, in this case the source/root one. Let it compile, it takes only a couple seconds with a minute at most.
|
||||
|
||||
1. If all went well, see your image with `docker images`. Example below:
|
||||
|
||||
```shell
|
||||
|
@ -4,8 +4,12 @@ services:
|
||||
restart: unless-stopped
|
||||
container_name: wgdashboard
|
||||
environment:
|
||||
- tz=Europe/Amsterdam # <--- Set container timezone, default: Europe/Amsterdam.
|
||||
- global_dns=9.9.9.9 # <--- Set global DNS address, default: 1.1.1.1.
|
||||
|
||||
#- tz= # <--- Set container timezone, default: Europe/Amsterdam.
|
||||
#- global_dns= # <--- Set global DNS address, default: 1.1.1.1.
|
||||
|
||||
#- enable= # <--- Set the interfaces that will be enabled on startup, default: 'none'.
|
||||
#- isolate= # <--- Set the interfaces that will disallow peer communication, default: 'none'.
|
||||
#- public_ip= # <--- Set public IP to ensure the correct one is chosen, defaulting to the IP give by ifconfig.me.
|
||||
|
@ -1,612 +0,0 @@
|
||||
# 📖 API Document for WGDashboard
|
||||
|
||||
**Version: v4.0**
|
||||
|
||||
**Created by: Donald Zou**
|
||||
|
||||
<!-- TOC -->
|
||||
* [📖 API Document for WGDashboard](#-api-document-for-wgdashboard)
|
||||
* [🔑 How to use API Key?](#-how-to-use-api-key)
|
||||
* [Create API Key](#create-api-key)
|
||||
* [Use API Key](#use-api-key)
|
||||
* [API Endpoints](#api-endpoints)
|
||||
* [Handshake to Server](#handshake-to-server)
|
||||
* [Request](#request)
|
||||
* [Response](#response)
|
||||
* [Validate Authentication](#validate-authentication)
|
||||
* [Request](#request-1)
|
||||
* [Response](#response-1)
|
||||
* [Authenticate](#authenticate)
|
||||
* [Request](#request-2)
|
||||
* [Body Parameters](#body-parameters)
|
||||
* [Response](#response-2)
|
||||
* [Sign Out](#sign-out)
|
||||
* [Request](#request-3)
|
||||
* [Response](#response-3)
|
||||
* [Get WireGuard Configurations](#get-wireguard-configurations)
|
||||
* [Request](#request-4)
|
||||
* [Response](#response-4)
|
||||
* [Add WireGuard Configuration](#add-wireguard-configuration)
|
||||
* [Request](#request-5)
|
||||
* [Body Parameters](#body-parameters-1)
|
||||
* [Response](#response-5)
|
||||
* [Toggle WireGuard Configuration](#toggle-wireguard-configuration)
|
||||
* [Request](#request-6)
|
||||
* [Query String Parameter](#query-string-parameter)
|
||||
* [Response](#response-6)
|
||||
* [Get WGDashboard Configuration](#get-wgdashboard-configuration)
|
||||
* [Request](#request-7)
|
||||
* [Response](#response-7)
|
||||
* [Update WGDashboard Configuration Item](#update-wgdashboard-configuration-item)
|
||||
* [Request](#request-8)
|
||||
* [Body Parameters](#body-parameters-2)
|
||||
* [Response](#response-8)
|
||||
* [Get WGDashboard API Keys](#get-wgdashboard-api-keys)
|
||||
* [Request](#request-9)
|
||||
* [Response](#response-9)
|
||||
* [Add WGDashboard API Key](#add-wgdashboard-api-key)
|
||||
* [Request](#request-10)
|
||||
* [Body Parameters](#body-parameters-3)
|
||||
* [Response](#response-10)
|
||||
* [Endpoint](#endpoint)
|
||||
* [Request](#request-11)
|
||||
* [Response](#response-11)
|
||||
<!-- TOC -->
|
||||
|
||||
<hr>
|
||||
|
||||
## 🔑 How to use API Key?
|
||||
|
||||
### Create API Key
|
||||
|
||||
1. To request an API Key, simply login to your WGDashboard, go to **Settings**, scroll to the very bottom. Click the **switch** on the right to enable API Key.
|
||||
2. Click the blur **Create** button, set an **expiry date** you want or **never expire**, then click **Done**.
|
||||
|
||||
### Use API Key
|
||||
|
||||
- Simply add `wg-dashboard-apikey` with the value of your API key into the HTTP Header.
|
||||
|
||||
```javascript
|
||||
fetch('http://server:10086/api/handshake', {
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'wg-dashboard-apikey': 'insert your api key here'
|
||||
},
|
||||
method: "GET"
|
||||
})
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Handshake to Server
|
||||
|
||||
This endpoint is designed for a simple handshake when using API key to connect. If `status` is `true` that means
|
||||
|
||||
#### Request
|
||||
|
||||
`GET /api/handshake`
|
||||
|
||||
#### Response
|
||||
|
||||
`200 - OK`
|
||||
|
||||
```json
|
||||
{
|
||||
"data": null,
|
||||
"message": null,
|
||||
"status": true
|
||||
}
|
||||
```
|
||||
|
||||
`401 - UNAUTHORIZED`
|
||||
|
||||
```json
|
||||
{
|
||||
"data": null,
|
||||
"message": "Unauthorized access.",
|
||||
"status": false
|
||||
}
|
||||
```
|
||||
> Notice: this `401` response will return at all endpoint if your API Key or session is invalid.
|
||||
|
||||
<hr>
|
||||
|
||||
### Validate Authentication
|
||||
|
||||
This endpoint if needed for non-cross-server access. This will check if the cookie on the client side is still valid on the server side.
|
||||
|
||||
#### Request
|
||||
|
||||
`GET /api/validateAuthentication`
|
||||
|
||||
#### Response
|
||||
|
||||
`200 - OK`
|
||||
|
||||
Session is still valid
|
||||
|
||||
```json
|
||||
{
|
||||
"data": null,
|
||||
"message": null,
|
||||
"status": true
|
||||
}
|
||||
```
|
||||
|
||||
Session is invalid
|
||||
|
||||
```json
|
||||
{
|
||||
"data": null,
|
||||
"message": "Invalid authentication.",
|
||||
"status": false
|
||||
}
|
||||
```
|
||||
<hr>
|
||||
|
||||
### Authenticate
|
||||
|
||||
This endpoint is dedicated for non-cross-server access. It is used to authenticate user's username, password and TOTP
|
||||
|
||||
#### Request
|
||||
|
||||
`POST /api/authenticate`
|
||||
|
||||
##### Body Parameters
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "admin",
|
||||
"totp": "123456"
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Type |
|
||||
|------------|--------|
|
||||
| `username` | string |
|
||||
| `password` | string |
|
||||
| `totp` | string |
|
||||
|
||||
|
||||
#### Response
|
||||
|
||||
`200 - OK`
|
||||
|
||||
If username, password and TOTP matched
|
||||
|
||||
```json
|
||||
{
|
||||
"data": null,
|
||||
"message": null,
|
||||
"status": true
|
||||
}
|
||||
```
|
||||
|
||||
If username, password or TOTP is not match
|
||||
|
||||
```json
|
||||
{
|
||||
"data": null,
|
||||
"message": "Sorry, your username, password or OTP is incorrect.",
|
||||
"status": false
|
||||
}
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
### Sign Out
|
||||
|
||||
To remove the current session on server side
|
||||
|
||||
#### Request
|
||||
|
||||
`GET /api/signout`
|
||||
|
||||
#### Response
|
||||
|
||||
`200 - OK`
|
||||
|
||||
```json
|
||||
{
|
||||
"data": null,
|
||||
"message": null,
|
||||
"status": true
|
||||
}
|
||||
```
|
||||
<hr>
|
||||
|
||||
### Get WireGuard Configurations
|
||||
|
||||
To get all WireGuard configurations in `/etc/wireguard`
|
||||
|
||||
#### Request
|
||||
|
||||
`GET /api/getWireguardConfigurations`
|
||||
|
||||
#### Response
|
||||
|
||||
`200 - OK`
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"Address": "10.200.200.1/24",
|
||||
"ConnectedPeers": 0,
|
||||
"DataUsage": {
|
||||
"Receive": 0.1582,
|
||||
"Sent": 2.1012999999999997,
|
||||
"Total": 2.2595
|
||||
},
|
||||
"ListenPort": "51820",
|
||||
"Name": "wg0",
|
||||
"PostDown": "iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o enp0s1 -j MASQUERADE;",
|
||||
"PostUp": "iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o enp0s1 -j MASQUERADE;",
|
||||
"PreDown": "",
|
||||
"PreUp": "",
|
||||
"PrivateKey": "8DsSMli3okgUx5frKbFQ0fMW5ZMyqyxOdOW7+g21L18=",
|
||||
"PublicKey": "GQlGi8QJ93hWY7L2xlJyh+7S6+ekER9xP11T92T0O0Q=",
|
||||
"SaveConfig": true,
|
||||
"Status": false
|
||||
}
|
||||
],
|
||||
"message": null,
|
||||
"status": true
|
||||
}
|
||||
```
|
||||
<hr>
|
||||
|
||||
### Add WireGuard Configuration
|
||||
|
||||
Add a new WireGuard Configuration
|
||||
|
||||
#### Request
|
||||
|
||||
`POST /api/addWireguardConfiguration`
|
||||
|
||||
##### Body Parameters
|
||||
|
||||
```json
|
||||
{
|
||||
"ConfigurationName": "wg0",
|
||||
"Address": "10.0.0.1/24",
|
||||
"ListenPort": 51820,
|
||||
"PrivateKey": "eJuuamCgakVs2xUZGHh/g7C6Oy89JGh7eE2jjEGbbFc=",
|
||||
"PublicKey": "3Ruirgw9qNRwNpBepkiVjjSe82tY+lDZr6WaFC4wO2g=",
|
||||
"PresharedKey": "GMMLKWdJlgsKVoR26BJPsNbDXyfILL+x1Nd6Ecmn4lg=",
|
||||
"PreUp": "",
|
||||
"PreDown": "iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o enp0s1 -j MASQUERADE;",
|
||||
"PostUp": "iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o enp0s1 -j MASQUERADE;",
|
||||
"PostDown": ""
|
||||
}
|
||||
```
|
||||
|
||||
| Parameter | Type |
|
||||
|---------------------|--------|
|
||||
| `ConfigurationName` | string |
|
||||
| `Address` | string |
|
||||
| `ListenPort` | int |
|
||||
| `PrivateKey` | string |
|
||||
| `PublicKey` | string |
|
||||
| `PresharedKey` | string |
|
||||
| `PreUp` | string |
|
||||
| `PreDown` | string |
|
||||
| `PostUp` | string |
|
||||
| `PostDown` | string |
|
||||
|
||||
#### Response
|
||||
|
||||
`200 - OK`
|
||||
|
||||
If everything is good
|
||||
|
||||
```json
|
||||
{
|
||||
"data": null,
|
||||
"message": null,
|
||||
"status": true
|
||||
}
|
||||
```
|
||||
|
||||
If the new configuration's `ConfigurationName` is already existed
|
||||
|
||||
```json
|
||||
{
|
||||
"data": null,
|
||||
"message": "Already have a configuration with the name \"wg0\"",
|
||||
"status": false
|
||||
}
|
||||
```
|
||||
|
||||
If the new configuration's `ListenPort` is used by another configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"data": null,
|
||||
"message": "Already have a configuration with the port \"51820\"",
|
||||
"status": false
|
||||
}
|
||||
```
|
||||
|
||||
If the new configuration's `Address` is used by another configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"data": null,
|
||||
"message": "Already have a configuration with the address \"10.0.0.1/24\"",
|
||||
"status": false
|
||||
}
|
||||
```
|
||||
<hr>
|
||||
|
||||
### Toggle WireGuard Configuration
|
||||
|
||||
To turn on/off of a WireGuard Configuration
|
||||
|
||||
#### Request
|
||||
|
||||
`GET /api/toggleWireguardConfiguration/?configurationName=`
|
||||
|
||||
##### Query String Parameter
|
||||
|
||||
| Parameter | Type |
|
||||
|---------------------|--------|
|
||||
| `configurationName` | string |
|
||||
|
||||
#### Response
|
||||
|
||||
`200 - OK`
|
||||
|
||||
If toggle is successful, server will return the current status in `status`: `true` or `false` indicating if the configuration is up or not.
|
||||
|
||||
```json
|
||||
{
|
||||
"data": true,
|
||||
"message": null,
|
||||
"status": true
|
||||
}
|
||||
```
|
||||
|
||||
If the `configurationName` provided does not exist
|
||||
|
||||
```json
|
||||
{
|
||||
"data": null,
|
||||
"message": "Please provide a valid configuration name",
|
||||
"status": false
|
||||
}
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
### Get WGDashboard Configuration
|
||||
|
||||
Get the WGDashboard Configuration, such as `dashboard_theme`...
|
||||
|
||||
#### Request
|
||||
|
||||
`GET /api/getDashboardConfiguration`
|
||||
|
||||
#### Response
|
||||
|
||||
`200 - OK`
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"Account": {
|
||||
"enable_totp": false,
|
||||
"password": "some hashed value :(",
|
||||
"totp_verified": false,
|
||||
"username": "admin"
|
||||
},
|
||||
"Database": {
|
||||
"type": "sqlite"
|
||||
},
|
||||
"Other": {
|
||||
"welcome_session": false
|
||||
},
|
||||
"Peers": {
|
||||
"peer_display_mode": "grid",
|
||||
"peer_endpoint_allowed_ip": "0.0.0.0/0",
|
||||
"peer_global_dns": "1.1.1.1",
|
||||
"peer_keep_alive": "21",
|
||||
"peer_mtu": "1420",
|
||||
"remote_endpoint": "192.168.2.38"
|
||||
},
|
||||
"Server": {
|
||||
"app_ip": "0.0.0.0",
|
||||
"app_port": "10086",
|
||||
"app_prefix": "",
|
||||
"auth_req": true,
|
||||
"dashboard_api_key": true,
|
||||
"dashboard_refresh_interval": "5000",
|
||||
"dashboard_sort": "status",
|
||||
"dashboard_theme": "dark",
|
||||
"version": "v4.0",
|
||||
"wg_conf_path": "/etc/wireguard"
|
||||
}
|
||||
},
|
||||
"message": null,
|
||||
"status": true
|
||||
}
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
### Update WGDashboard Configuration Item
|
||||
|
||||
Update the WGDashboard Configuration one at a time
|
||||
|
||||
#### Request
|
||||
|
||||
`POST /api/updateDashboardConfigurationItem`
|
||||
|
||||
##### Body Parameters
|
||||
|
||||
```json
|
||||
{
|
||||
"section": "Server",
|
||||
"key": "dashboard_theme",
|
||||
"value": "dark"
|
||||
}
|
||||
```
|
||||
| Parameter | Type | |
|
||||
|-----------|--------|----------------------------------------------------------|
|
||||
| `section` | string | Each section in the `wg-dashboard.ini` |
|
||||
| `key` | string | Each key/value pair under each in the `wg-dashboard.ini` |
|
||||
| `value` | string | Value for this key/value pair |
|
||||
|
||||
|
||||
#### Response
|
||||
|
||||
`200 - OK`
|
||||
|
||||
If update is success
|
||||
|
||||
```json
|
||||
{
|
||||
"data": true,
|
||||
"message": null,
|
||||
"status": true
|
||||
}
|
||||
```
|
||||
|
||||
If update failed
|
||||
|
||||
```json
|
||||
{
|
||||
"data": true,
|
||||
"message": "Message related to the error will appear here",
|
||||
"status": false
|
||||
}
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
### Get WGDashboard API Keys
|
||||
|
||||
Get a list of active API key in WGDashboard
|
||||
|
||||
#### Request
|
||||
|
||||
`GET /api/getDashboardAPIKeys`
|
||||
|
||||
#### Response
|
||||
|
||||
`200 - OK`
|
||||
|
||||
If API Key function is enabled and there are active API keys
|
||||
|
||||
> If `ExpiredAt` is `null`, that means this API key will never expire
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"CreatedAt": "2024-08-15 00:42:31",
|
||||
"ExpiredAt": null,
|
||||
"Key": "AXt1x3TZMukmA-eSnAyESy08I14n20boppSsknHOB-Y"
|
||||
},
|
||||
{
|
||||
"CreatedAt": "2024-08-14 22:50:44",
|
||||
"ExpiredAt": "2024-08-21 22:50:43",
|
||||
"Key": "ry0Suo0BrypSMzbq0C_TjkEcgrFHHj6UBZGmC2-KI2o"
|
||||
}
|
||||
],
|
||||
"message": null,
|
||||
"status": true
|
||||
}
|
||||
```
|
||||
|
||||
If API key function is disabled
|
||||
|
||||
```json
|
||||
{
|
||||
"data": null,
|
||||
"message": "Dashboard API Keys function is disabled",
|
||||
"status": false
|
||||
}
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
### Add WGDashboard API Key
|
||||
|
||||
Add a new API Key in WGDashboard
|
||||
|
||||
#### Request
|
||||
|
||||
`POST /api/newDashboardAPIKey`
|
||||
|
||||
##### Body Parameters
|
||||
|
||||
```json
|
||||
{
|
||||
"neverExpire": false,
|
||||
"ExpiredAt": "2024-12-31 16:00:00"
|
||||
}
|
||||
```
|
||||
| Parameter | Type | |
|
||||
|---------------|--------|-----------------------------------------------------------------------------------|
|
||||
| `neverExpire` | bool | If this is `false`, please specify a date in `ExpiredAt` |
|
||||
| `ExpiredAt` | string | If `neverExpire` is `true`, this can be omitted. Format is `YYYY-MM-DD hh:mm:ss`. |
|
||||
|
||||
#### Response
|
||||
|
||||
`200 - OK`
|
||||
|
||||
If success, it will return the latest list of API Keys
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"CreatedAt": "2024-08-15 00:42:31",
|
||||
"ExpiredAt": null,
|
||||
"Key": "AXt1x3TZMukmA-eSnAyESy08I14n20boppSsknHOB-Y"
|
||||
},
|
||||
{
|
||||
"CreatedAt": "2024-08-14 22:50:44",
|
||||
"ExpiredAt": "2024-12-31 16:50:43",
|
||||
"Key": "ry0Suo0BrypSMzbq0C_TjkEcgrFHHj6UBZGmC2-KI2o"
|
||||
}
|
||||
],
|
||||
"message": null,
|
||||
"status": true
|
||||
}
|
||||
```
|
||||
|
||||
If API key function is disabled
|
||||
|
||||
```json
|
||||
{
|
||||
"data": null,
|
||||
"message": "Dashboard API Keys function is disabled",
|
||||
"status": false
|
||||
}
|
||||
```
|
||||
|
||||
<hr>
|
||||
|
||||
### Endpoint
|
||||
|
||||
Description
|
||||
|
||||
#### Request
|
||||
|
||||
`GET`
|
||||
|
||||
#### Response
|
||||
|
||||
`200 - OK`
|
||||
|
||||
```json
|
||||
{
|
||||
"data": true,
|
||||
"message": null,
|
||||
"status": true
|
||||
}
|
||||
```
|
||||
|
@ -1,105 +0,0 @@
|
||||
# ⏰ Changelogs of WGDashboard
|
||||
|
||||
#### v3.0.0 - v3.0.6.2 - Jan 18, 2022
|
||||
|
||||
- 🎉 **New Features**
|
||||
- **Moved from TinyDB to SQLite**: SQLite provide a better performance and loading speed when getting peers! Also avoided crashing the database due to **race condition**.
|
||||
- **Added Gunicorn WSGI Server**: This could provide more stable on handling HTTP request, and more flexibility in the future (such as HTTPS support). **BIG THANKS to @pgalonza :heart:**
|
||||
- **Add Peers by Bulk:** User can add peers by bulk, just simply set the amount and click add.
|
||||
- **Delete Peers by Bulk**: User can delete peers by bulk, without deleting peers one by one.
|
||||
- **Download Peers in Zip**: User can download all *downloadable* peers in a zip.
|
||||
- **Added Pre-shared Key to peers:** Now each peer can add with a pre-shared key to enhance security. Previously added peers can add the pre-shared key through the peer setting button.
|
||||
- **Redirect Back to Previous Page:** The dashboard will now redirect you back to your previous page if the current session got timed out and you need to sign in again.
|
||||
- **Added Some [🥘 Experimental Functions](#-experimental-functions)**
|
||||
|
||||
- 🪚 **Bug Fixed**
|
||||
- [IP Sorting range issues #99](https://github.com/donaldzou/WGDashboard/issues/99) [❤️ @barryboom]
|
||||
- [INvalid character written to tunnel json file #108](https://github.com/donaldzou/WGDashboard/issues/108) [❤️ @ikidd]
|
||||
- [Add IPv6 #91](https://github.com/donaldzou/WGDashboard/pull/91) [❤️ @pgalonza]
|
||||
- [Added MTU and PersistentKeepalive to QR code and download files #112](https://github.com/donaldzou/WGDashboard/pull/112) [:heart: @reafian]
|
||||
- **And many other bugs provided by our beloved users** :heart:
|
||||
- **🧐 Other Changes**
|
||||
- **Key generating moved to front-end**: No longer need to use the server's WireGuard to generate keys, thanks to the `wireguard.js` from the [official repository](https://git.zx2c4.com/wireguard-tools/tree/contrib/keygen-html/wireguard.js)!
|
||||
- **Peer transfer calculation**: each peer will now show all transfer amount (previously was only showing transfer amount from the last configuration start-up).
|
||||
- **UI adjustment on running peers**: peers will have a new style indicating that it is running.
|
||||
- **`wgd.sh` finally can update itself**: So now user could update the whole dashboard from `wgd.sh`, with the `update` command.
|
||||
- **Minified JS and CSS files**: Although only a small changes on the file size, but I think is still a good practice to save a bit of bandwidth ;)
|
||||
|
||||
*And many other small changes for performance and bug fixes! :laughing:*
|
||||
|
||||
#### v2.3.1 - Sep 8, 2021
|
||||
|
||||
- Updated dashboard's name to **WGDashboard**!!
|
||||
|
||||
#### v2.3 - Sep 8, 2021
|
||||
|
||||
- 🎉 **New Features**
|
||||
- **Update directly from `wgd.sh`:** Now you can update WGDashboard directly from the bash script.
|
||||
- **Displaying Peers:** You can switch the display mode between list and table in the configuration page.
|
||||
- 🪚 **Bug Fixed**
|
||||
- [Peer DNS Validation Fails #67](issues/67): Added DNS format check. [❤️ @realfian]
|
||||
- [configparser.NoSectionError: No section: 'Interface' #66](issues/66): Changed permission requirement for `etc/wireguard` from `744` to `755`. [❤️ @ramalmaty]
|
||||
- [Feature request: Interface not loading when information missing #73](issues/73): Fixed when Configuration Address and Listen Port is missing will crash the dashboard. [❤️ @js32]
|
||||
- [Remote Peer, MTU and PersistentKeepalives added #70](pull/70): Added MTU, remote peer and Persistent Keepalive. [❤️ @realfian]
|
||||
- [Fixes DNS check to support search domain #65](pull/65): Added allow input domain into DNS. [❤️@davejlong]
|
||||
- **🧐 Other Changes**
|
||||
- Moved Add Peer Button into the right bottom corner.
|
||||
|
||||
#### v2.2.1 - Aug 16, 2021
|
||||
|
||||
Bug Fixed:
|
||||
- Added support for full subnet on Allowed IP
|
||||
- Peer setting Save button
|
||||
|
||||
#### v2.2 - Aug 14, 2021
|
||||
|
||||
- 🎉 **New Features**
|
||||
- **Add new peers**: Now you can add peers directly on dashboard, it will generate a pair of private key and public key. You can also set its DNS, endpoint allowed IPs. Both can set a default value in the setting page. [❤️ in [#44](https://github.com/donaldzou/wireguard-dashboard/issues/44)]
|
||||
- **QR Code:** You can add the private key in peer setting of your existed peer to create a QR code. Or just create a new one, dashboard will now be able to auto generate a private key and public key ;) Don't worry, all keys will be generated on your machine, and **will delete all key files after they got generated**. [❤️ in [#29](https://github.com/donaldzou/wireguard-dashboard/issues/29)]
|
||||
- **Peer configuration file download:** Same as QR code, you now can download the peer configuration file, so you don't need to manually input all the details on the peer machine! [❤️ in [#40](https://github.com/donaldzou/wireguard-dashboard/issues/40)]
|
||||
- **Search peers**: You can now search peers by their name.
|
||||
- **Autostart on boot:** Added a tutorial on how to start the dashboard to on boot! Please read the [tutorial below](#autostart-wireguard-dashboard-on-boot). [❤️ in [#29](https://github.com/donaldzou/wireguard-dashboard/issues/29)]
|
||||
- **Click to copy**: You can now click and copy all peer's public key and configuration's public key.
|
||||
- ....
|
||||
- 🪚 **Bug Fixed**
|
||||
- When there are comments in the wireguard config file, will cause the dashboard to crash.
|
||||
- Used regex to search for config files.
|
||||
- **🧐 Other Changes**
|
||||
- Moved all external CSS and JavaScript file to local hosting (Except Bootstrap Icon, due to large amount of SVG files).
|
||||
- Updated Python dependencies
|
||||
- Flask: `v1.1.2 => v2.0.1`
|
||||
- Jinja: `v2.10.1 => v3.0.1`
|
||||
- icmplib: `v2.1.1 => v3.0.1`
|
||||
- Updated CSS/JS dependencies
|
||||
- Bootstrap: `v4.5.3 => v4.6.0`
|
||||
- UI adjustment
|
||||
- Adjusted how peers will display in larger screens, used to be 1 row per peer, now is 3 peers in 1 row.
|
||||
|
||||
#### v2.1 - Jul 2, 2021
|
||||
|
||||
- Added **Ping** and **Traceroute** tools!
|
||||
- Adjusted the calculation of data usage on each peers
|
||||
- Added refresh interval of the dashboard
|
||||
- Bug fixed when no configuration on fresh install ([#23](https://github.com/donaldzou/wireguard-dashboard/issues/23))
|
||||
- Fixed crash when too many peers ([#22](https://github.com/donaldzou/wireguard-dashboard/issues/22))
|
||||
|
||||
#### v2.0 - May 5, 2021
|
||||
|
||||
- Added login function to dashboard
|
||||
- ***I'm not using the most ideal way to store the username and password, feel free to provide a better way to do this if you any good idea!***
|
||||
- Added a config file to the dashboard
|
||||
- Dashboard config can be change within the **Setting** tab on the side bar
|
||||
- Adjusted UI
|
||||
- And much more!
|
||||
|
||||
#### v1.1.2 - Apr 3, 2021
|
||||
|
||||
- Resolved issue [#3](https://github.com/donaldzou/wireguard-dashboard/issues/3).
|
||||
|
||||
#### v1.1.1 - Apr 2, 2021
|
||||
|
||||
- Able to add a friendly name to each peer. Thanks [#2](https://github.com/donaldzou/wireguard-dashboard/issues/2) !
|
||||
|
||||
#### v1.0 - Dec 27, 2020
|
||||
|
||||
- Added the function to remove peers
|
@ -16,8 +16,11 @@ ensure_installation() {
|
||||
python3 -m venv "${WGDASH}"/src/venv
|
||||
. "${WGDASH}/src/venv/bin/activate"
|
||||
|
||||
mv /usr/lib/python3.12/site-packages/psutil* "${WGDASH}"/src/venv/lib/python3.12/site-packages
|
||||
mv /usr/lib/python3.12/site-packages/bcrypt* "${WGDASH}"/src/venv/lib/python3.12/site-packages
|
||||
|
||||
|
||||
[ ! -d "${WGDASH}/src/venv/lib/python3.12/site-packages/psutil" ] && echo "Moving PIP dependency: psutil" && mv /usr/lib/python3.12/site-packages/psutil* "${WGDASH}"/src/venv/lib/python3.12/site-packages
|
||||
[ ! -d "${WGDASH}/src/venv/lib/python3.12/site-packages/bcrypt" ] && echo "Moving PIP dependency: bcrypt" && mv /usr/lib/python3.12/site-packages/bcrypt* "${WGDASH}"/src/venv/lib/python3.12/site-packages
|
||||
|
||||
|
||||
chmod +x "${WGDASH}"/src/wgd.sh
|
||||
cd "${WGDASH}"/src || exit
|
||||
@ -74,6 +77,8 @@ set_envvars() {
|
||||
fi
|
||||
|
||||
# Determine the public IP and update if necessary
|
||||
echo "{$public_ip}"
|
||||
|
||||
if [ "${public_ip}" = "0.0.0.0" ]; then
|
||||
default_ip=$(curl -s ifconfig.me)
|
||||
|
||||
@ -141,7 +146,14 @@ start_core() {
|
||||
echo "Found: $interface, stopping isolation checking."
|
||||
break
|
||||
else
|
||||
if [ -f "/etc/wireguard/${interface}.conf" ]; then
|
||||
|
||||
|
||||
if [ ! -f "/etc/wireguard/${interface}.conf" ]; then
|
||||
echo "Ignoring ${interface}"
|
||||
|
||||
elif [ -f "/etc/wireguard/${interface}.conf" ]; then
|
||||
|
||||
|
||||
echo "Isolating interface:" "$interface"
|
||||
|
||||
upblocking=$(grep -c "PostUp = iptables -I FORWARD -i ${interface} -o ${interface} -j DROP" /etc/wireguard/"${interface}".conf)
|
||||
@ -161,9 +173,15 @@ start_core() {
|
||||
done
|
||||
|
||||
# Removing isolation for the configurations that did not match.
|
||||
for interface in "${non_isolate[@]}"; do
|
||||
|
||||
if [ -f "/etc/wireguard/${interface}.conf" ]; then
|
||||
|
||||
for interface in "${non_isolate[@]}"; do
|
||||
if [ ! -f "/etc/wireguard/${interface}.conf" ]; then
|
||||
echo "Ignoring ${interface}"
|
||||
|
||||
elif [ -f "/etc/wireguard/${interface}.conf" ]; then
|
||||
|
||||
|
||||
echo "Removing isolation, if isolation is present for:" "$interface"
|
||||
|
||||
sed -i "/PostUp = iptables -I FORWARD -i ${interface} -o ${interface} -j DROP/d" /etc/wireguard/"${interface}".conf
|
||||
@ -174,37 +192,6 @@ start_core() {
|
||||
|
||||
done
|
||||
|
||||
# The following section takes care of enabling wireguard interfaces on startup. Using arrays and given arguments.
|
||||
#
|
||||
# WILL BE REMOVED IN FUTURE WHEN WGDASHBOARD ITSELF SUPPORTS THIS!!
|
||||
#
|
||||
|
||||
IFS=',' read -r -a enable_array <<< "${enable}"
|
||||
|
||||
for interface in "${enable_array[@]}"; do
|
||||
|
||||
if [ "$interface" = "none" ]; then
|
||||
echo "Found: $interface, stopping enabling checking."
|
||||
break
|
||||
else
|
||||
echo "Enabling interface:" "$interface"
|
||||
|
||||
local fileperms
|
||||
fileperms=$(stat -c "%a" /etc/wireguard/"${interface}".conf)
|
||||
if [ "$fileperms" -eq 644 ]; then
|
||||
echo "Configuration is world accessible, adjusting."
|
||||
chmod 600 "/etc/wireguard/${interface}.conf"
|
||||
fi
|
||||
|
||||
if [ -f "/etc/wireguard/${interface}.conf" ]; then
|
||||
wg-quick up "$interface"
|
||||
else
|
||||
echo "No corresponding configuration file found for $interface doing nothing."
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
done
|
||||
}
|
||||
|
||||
ensure_blocking() {
|
||||
|
1081
package-lock.json
generated
@ -1,5 +1,8 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@volar/language-server": "2.4.0-alpha.18",
|
||||
"@vue/language-server": "2.0.28",
|
||||
"ag-charts-vue3": "^10.3.1",
|
||||
"dayjs": "^1.11.12"
|
||||
}
|
||||
}
|
||||
|
244
src/api.py
@ -1,244 +0,0 @@
|
||||
import ipaddress, subprocess, datetime, os, util
|
||||
from datetime import datetime, timedelta
|
||||
from flask import jsonify
|
||||
from util import *
|
||||
import configparser
|
||||
|
||||
notEnoughParameter = {"status": False, "reason": "Please provide all required parameters."}
|
||||
good = {"status": True, "reason": ""}
|
||||
|
||||
def ret(status=True, reason="", data=""):
|
||||
return {"status": status, "reason": reason, "data": data}
|
||||
|
||||
|
||||
|
||||
def togglePeerAccess(data, g):
|
||||
checkUnlock = g.cur.execute(f"SELECT * FROM {data['config']} WHERE id='{data['peerID']}'").fetchone()
|
||||
if checkUnlock:
|
||||
moveUnlockToLock = g.cur.execute(
|
||||
f"INSERT INTO {data['config']}_restrict_access SELECT * FROM {data['config']} WHERE id = '{data['peerID']}'")
|
||||
if g.cur.rowcount == 1:
|
||||
print(g.cur.rowcount)
|
||||
print(util.deletePeers(data['config'], [data['peerID']], g.cur, g.db))
|
||||
else:
|
||||
moveLockToUnlock = g.cur.execute(
|
||||
f"SELECT * FROM {data['config']}_restrict_access WHERE id = '{data['peerID']}'").fetchone()
|
||||
try:
|
||||
if len(moveLockToUnlock[-1]) == 0:
|
||||
status = subprocess.check_output(
|
||||
f"wg set {data['config']} peer {moveLockToUnlock[0]} allowed-ips {moveLockToUnlock[11]}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
else:
|
||||
now = str(datetime.datetime.now().strftime("%m%d%Y%H%M%S"))
|
||||
f_name = now + "_tmp_psk.txt"
|
||||
f = open(f_name, "w+")
|
||||
f.write(moveLockToUnlock[-1])
|
||||
f.close()
|
||||
subprocess.check_output(
|
||||
f"wg set {data['config']} peer {moveLockToUnlock[0]} allowed-ips {moveLockToUnlock[11]} preshared-key {f_name}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
os.remove(f_name)
|
||||
status = subprocess.check_output(f"wg-quick save {data['config']}", shell=True, stderr=subprocess.STDOUT)
|
||||
g.cur.execute(
|
||||
f"INSERT INTO {data['config']} SELECT * FROM {data['config']}_restrict_access WHERE id = '{data['peerID']}'")
|
||||
if g.cur.rowcount == 1:
|
||||
g.cur.execute(f"DELETE FROM {data['config']}_restrict_access WHERE id = '{data['peerID']}'")
|
||||
|
||||
except subprocess.CalledProcessError as exc:
|
||||
return {"status": False, "reason": str(exc.output.strip())}
|
||||
return good
|
||||
|
||||
class managePeer:
|
||||
def getPeerDataUsage(self, data, cur):
|
||||
now = datetime.now()
|
||||
now_string = now.strftime("%d/%m/%Y %H:%M:%S")
|
||||
interval = {
|
||||
"30min": now - timedelta(hours=0, minutes=30),
|
||||
"1h": now - timedelta(hours=1, minutes=0),
|
||||
"6h": now - timedelta(hours=6, minutes=0),
|
||||
"24h": now - timedelta(hours=24, minutes=0),
|
||||
"all": ""
|
||||
}
|
||||
if data['interval'] not in interval.keys():
|
||||
return {"status": False, "reason": "Invalid interval."}
|
||||
intv = ""
|
||||
if data['interval'] != "all":
|
||||
t = interval[data['interval']].strftime("%d/%m/%Y %H:%M:%S")
|
||||
intv = f" AND time >= '{t}'"
|
||||
timeData = cur.execute(f"SELECT total_receive, total_sent, time FROM wg0_transfer WHERE id='{data['peerID']}' {intv} ORDER BY time DESC;")
|
||||
chartData = []
|
||||
for i in timeData:
|
||||
chartData.append({
|
||||
"total_receive": i[0],
|
||||
"total_sent": i[1],
|
||||
"time": i[2]
|
||||
})
|
||||
return {"status": True, "reason": "", "data": chartData}
|
||||
|
||||
class manageConfiguration:
|
||||
def AddressCheck(self, data):
|
||||
address = data['address']
|
||||
address = address.replace(" ", "")
|
||||
address = address.split(',')
|
||||
amount = 0
|
||||
for i in address:
|
||||
try:
|
||||
ips = ipaddress.ip_network(i, False)
|
||||
amount += ips.num_addresses
|
||||
except ValueError as e:
|
||||
return {"status": False, "reason": str(e)}
|
||||
if amount >= 1:
|
||||
return {"status": True, "reason": "", "data": f"Total of {amount} IPs"}
|
||||
else:
|
||||
return {"status": True, "reason": "", "data": f"0 available IPs"}
|
||||
|
||||
def PortCheck(self, data, configs):
|
||||
port = data['port']
|
||||
if (not port.isdigit()) or int(port) < 1 or int(port) > 65535:
|
||||
return {"status": False, "reason": f"Invalid port."}
|
||||
for i in configs:
|
||||
if i['port'] == port:
|
||||
return {"status": False, "reason": f"{port} used by {i['conf']}."}
|
||||
checkSystem = subprocess.run(f'ss -tulpn | grep :{port} > /dev/null', shell=True)
|
||||
if checkSystem.returncode != 1:
|
||||
return {"status": False, "reason": f"Port {port} used by other process in your system."}
|
||||
return good
|
||||
|
||||
def NameCheck(self, data, configs):
|
||||
name = data['name']
|
||||
name = name.replace(" ", "")
|
||||
for i in configs:
|
||||
if name == i['conf']:
|
||||
return {"status": False, "reason": f"{name} already existed."}
|
||||
illegal_filename = ["(Space)", " ", ".", ",", "/", "?", "<", ">", "\\", ":", "*", '|' '\"', "com1", "com2",
|
||||
"com3",
|
||||
"com4", "com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4",
|
||||
"lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "con", "nul", "prn"]
|
||||
for i in illegal_filename:
|
||||
name = name.replace(i, "")
|
||||
if len(name) == 0:
|
||||
return {"status": False, "reason": "Invalid name."}
|
||||
return good
|
||||
|
||||
def addConfiguration(self, data, configs, WG_CONF_PATH):
|
||||
output = ["[Interface]", "SaveConfig = true"]
|
||||
required = ['addConfigurationPrivateKey', 'addConfigurationListenPort',
|
||||
'addConfigurationAddress', 'addConfigurationPreUp', 'addConfigurationPreDown',
|
||||
'addConfigurationPostUp', 'addConfigurationPostDown']
|
||||
for i in required:
|
||||
e = data[i]
|
||||
if len(e) != 0:
|
||||
key = i.replace("addConfiguration", "")
|
||||
o = f"{key} = {e}"
|
||||
output.append(o)
|
||||
name = data['addConfigurationName']
|
||||
illegal_filename = ["(Space)", " ", ".", ",", "/", "?", "<", ">", "\\", ":", "*", '|' '\"', "com1", "com2",
|
||||
"com3",
|
||||
"com4", "com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4",
|
||||
"lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "con", "nul", "prn"]
|
||||
for i in illegal_filename:
|
||||
name = name.replace(i, "")
|
||||
|
||||
try:
|
||||
newFile = open(f"{WG_CONF_PATH}/{name}.conf", "w+")
|
||||
newFile.write("\n".join(output))
|
||||
except Exception as e:
|
||||
return {"status": False, "reason": str(e)}
|
||||
return {"status": True, "reason": "", "data": name}
|
||||
|
||||
def deleteConfiguration(self, data, config, g, WG_CONF_PATH):
|
||||
confs = []
|
||||
for i in config:
|
||||
confs.append(i['conf'])
|
||||
print(confs)
|
||||
if data['name'] not in confs:
|
||||
return {"status": False, "reason": "Configuration does not exist", "data": ""}
|
||||
for i in config:
|
||||
if i['conf'] == data['name']:
|
||||
if i['status'] == "running":
|
||||
try:
|
||||
subprocess.check_output("wg-quick down " + data['name'], shell=True, stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
return {"status": False, "reason": "Can't stop peer", "data": str(exc.output.strip().decode("utf-8"))}
|
||||
|
||||
g.cur.execute(f'DROP TABLE {data["name"]}')
|
||||
g.cur.execute(f'DROP TABLE {data["name"]}_restrict_access')
|
||||
g.db.commit()
|
||||
|
||||
try:
|
||||
os.remove(f'{WG_CONF_PATH}/{data["name"]}.conf')
|
||||
except Exception as e:
|
||||
return {"status": False, "reason": "Can't delete peer", "data": str(e)}
|
||||
|
||||
return good
|
||||
|
||||
def getConfigurationInfo(self, configName, WG_CONF_PATH):
|
||||
conf = configparser.RawConfigParser(strict=False)
|
||||
conf.optionxform = str
|
||||
try:
|
||||
with open(f'{WG_CONF_PATH}/{configName}.conf', 'r'):
|
||||
conf.read(f'{WG_CONF_PATH}/{configName}.conf')
|
||||
if not conf.has_section("Interface"):
|
||||
return ret(status=False, reason="No [Interface] in configuration file")
|
||||
return ret(data=dict(conf['Interface']))
|
||||
except FileNotFoundError as err:
|
||||
return ret(status=False, reason=str(err))
|
||||
|
||||
def saveConfiguration(self, data, WG_CONF_PATH, configs):
|
||||
conf = configparser.RawConfigParser(strict=False)
|
||||
conf.optionxform = str
|
||||
configName = data['configurationName']
|
||||
pc = manageConfiguration.PortCheck(self, {'port': data['ListenPort']}, configs)
|
||||
if pc['status']:
|
||||
try:
|
||||
newData = []
|
||||
with open(f'{WG_CONF_PATH}/{configName}.conf', 'r') as f:
|
||||
conf.read(f'{WG_CONF_PATH}/{configName}.conf')
|
||||
if not conf.has_section("Interface"):
|
||||
return ret(status=False, reason="No [Interface] in configuration file")
|
||||
l = ['ListenPort', 'PostUp', 'PostDown', 'PreUp', 'PreDown']
|
||||
for i in l:
|
||||
conf.set("Interface", i, data[i])
|
||||
conf.remove_section("Peer")
|
||||
newData = list(map(lambda x : f"{x[0]} = {x[1]}\n", list(conf.items("Interface"))))
|
||||
originalData = f.readlines()
|
||||
for i in range(len(originalData)):
|
||||
if originalData[i] == "[Peer]\n":
|
||||
originalData = originalData[i:]
|
||||
break
|
||||
newData.insert(0, "[Interface]\n")
|
||||
newData.append("\n")
|
||||
newData = newData + originalData
|
||||
conf.clear()
|
||||
|
||||
|
||||
try:
|
||||
check = subprocess.check_output("wg-quick down " + configName,
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
pass
|
||||
with open(f'{WG_CONF_PATH}/{configName}.conf', 'w') as f:
|
||||
for i in newData:
|
||||
f.write(i)
|
||||
try:
|
||||
check = subprocess.check_output("wg-quick up " + configName,
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
pass
|
||||
return ret()
|
||||
except FileNotFoundError as err:
|
||||
return ret(status=False, reason=str(err))
|
||||
else:
|
||||
return pc
|
||||
|
||||
|
||||
|
||||
|
||||
class settings:
|
||||
def setTheme(self, theme, config, setConfig):
|
||||
themes = ['light', 'dark']
|
||||
if theme not in themes:
|
||||
return ret(status=False, reason="Theme does not exist")
|
||||
config['Server']['dashboard_theme'] = theme
|
||||
setConfig(config)
|
||||
return ret()
|
@ -1,7 +0,0 @@
|
||||
for ((i = 0 ; i<$1 ; i++ ))
|
||||
do
|
||||
privateKey=$(wg genkey)
|
||||
presharedKey=$(wg genkey)
|
||||
publicKey=$(wg pubkey <<< "$privateKey")
|
||||
echo "$privateKey,$publicKey,$presharedKey"
|
||||
done
|
1100
src/dashboard.py
@ -5,4 +5,5 @@ pyotp
|
||||
Flask
|
||||
flask-cors
|
||||
icmplib
|
||||
gunicorn
|
||||
gunicorn
|
||||
requests
|
27
src/static/app/build.sh
Executable file
@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
echo "Running vite build..."
|
||||
if vite build; then
|
||||
echo "Vite build successful."
|
||||
else
|
||||
echo "Vite build failed. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
echo "Checking for changes to commit..."
|
||||
if git diff-index --quiet HEAD --; then
|
||||
|
||||
if git commit -a; then
|
||||
echo "Git commit successful."
|
||||
else
|
||||
echo "Git commit failed. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "No changes to commit. Skipping commit."
|
||||
fi
|
||||
echo "Pushing changes to remote..."
|
||||
if git push; then
|
||||
echo "Git push successful."
|
||||
else
|
||||
echo "Git push failed. Exiting."
|
||||
exit 1
|
||||
fi
|
8
src/static/app/dist/assets/browser-CjSdxGTc.js
vendored
Normal file
1
src/static/app/dist/assets/configuration-CwSXWo0f.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{_ as r,c as i,d as o,w as e,j as l,a as t,T as _,i as a,l as d,S as u}from"./index-CP9pHThw.js";const m={name:"configuration"},p={class:"mt-md-5 mt-3 text-body"};function f(x,h,k,w,$,v){const n=l("RouterView");return t(),i("div",p,[o(n,null,{default:e(({Component:s,route:c})=>[o(_,{name:"fade2",mode:"out-in"},{default:e(()=>[(t(),a(u,null,{default:e(()=>[(t(),a(d(s),{key:c.path}))]),_:2},1024))]),_:2},1024)]),_:1})])}const B=r(m,[["render",f]]);export{B as default};
|
1
src/static/app/dist/assets/configurationBackupRestore-B8kdEi-3.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.confirmationContainer[data-v-a575be12]{background-color:#00000087;z-index:9999;backdrop-filter:blur(1px);-webkit-backdrop-filter:blur(1px)}.list1-enter-active[data-v-a575be12]{transition-delay:var(--6919ade8)!important}.card[data-v-0b159317],.title[data-v-0b159317]{width:100%}@media screen and (min-width: 700px){.card[data-v-0b159317],.title[data-v-0b159317]{width:700px}}.animate__fadeInUp[data-v-0b159317]{animation-timing-function:cubic-bezier(.42,0,.22,1)}.list1-move[data-v-0b159317],.list1-enter-active[data-v-0b159317],.list1-leave-active[data-v-0b159317]{transition:all .5s cubic-bezier(.42,0,.22,1)}.list1-enter-from[data-v-0b159317],.list1-leave-to[data-v-0b159317]{opacity:0;transform:translateY(30px)}.list1-leave-active[data-v-0b159317]{width:100%;position:absolute}
|
1
src/static/app/dist/assets/configurationBackupRestore-DH0fkbZ8.js
vendored
Normal file
1
src/static/app/dist/assets/configurationList-CWcGXYWr.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.fade-enter-active[data-v-a85a04a5]{transition-delay:var(--1d5189b2)!important}.configurationListTitle{.btn[data-v-16b5ab33]{border-radius:50%!important}}
|
1
src/static/app/dist/assets/configurationList-Dp28fKus.js
vendored
Normal file
1
src/static/app/dist/assets/dayjs.min-BfBaVRGN.js
vendored
Normal file
1
src/static/app/dist/assets/deleteConfiguration-C8C_aX-i.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{$ as C,r as u,H as w,D as x,o as _,a as d,c,b as t,d as s,n as B,m as $,s as D,B as m,q as N,g as M}from"./index-CP9pHThw.js";import{L as o}from"./localeText-DgEvbt12.js";const T={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll"},I={class:"container d-flex h-100 w-100"},R={class:"m-auto modal-dialog-centered dashboardModal",style:{width:"700px"}},S={class:"card rounded-3 shadow flex-grow-1 bg-danger-subtle border-danger-subtle",id:"deleteConfigurationContainer"},V={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0"},A={class:"mb-0"},L={class:"card-body px-4 text-muted"},P={class:"mb-0"},W={key:0},q={key:1},z={key:2,class:"d-flex align-items-center gap-2"},E=["placeholder"],G=["disabled"],F={__name:"deleteConfiguration",emits:["backup"],setup(H,{emit:v}){const a=C().params.id,b=u(""),h=w(),g=x(),p=u(!1),k=()=>{clearInterval(g.Peers.RefreshInterval),p.value=!0,N("/api/deleteWireguardConfiguration",{Name:a},l=>{l.status?(h.push("/"),g.newMessage("Server","Configuration deleted","success")):p.value=!1})},i=u(!0),r=u([]),f=()=>{i.value=!0,M("/api/getWireguardConfigurationBackup",{configurationName:a},l=>{r.value=l.data,i.value=!1})};_(()=>{f()});const y=v;return(l,e)=>(d(),c("div",T,[t("div",I,[t("div",R,[t("div",S,[t("div",V,[t("h5",A,[s(o,{t:"Are you sure to delete this configuration?"})]),t("button",{type:"button",class:"btn-close ms-auto",onClick:e[0]||(e[0]=n=>l.$emit("close"))})]),t("div",L,[t("p",P,[s(o,{t:"Once you deleted this configuration:"})]),t("ul",null,[t("li",null,[s(o,{t:"All connected peers will get disconnected"})]),t("li",null,[s(o,{t:"Both configuration file (.conf) and database table related to this configuration will get deleted"})])]),t("div",{class:B(["alert",[i.value?"alert-secondary":r.value.length>0?"alert-success":"alert-danger"]])},[i.value?(d(),c("div",W,[e[5]||(e[5]=t("i",{class:"bi bi-search me-2"},null,-1)),s(o,{t:"Checking backups..."})])):r.value.length>0?(d(),c("div",q,[e[6]||(e[6]=t("i",{class:"bi bi-check-circle-fill me-2"},null,-1)),s(o,{t:"This configuration have "+r.value.length+" backups"},null,8,["t"])])):(d(),c("div",z,[e[9]||(e[9]=t("i",{class:"bi bi-x-circle-fill me-2"},null,-1)),s(o,{t:"This configuration have no backup"}),t("a",{role:"button",onClick:e[1]||(e[1]=n=>y("backup")),class:"ms-auto btn btn-sm btn-primary rounded-3"},[e[7]||(e[7]=t("i",{class:"bi bi-clock-history me-2"},null,-1)),s(o,{t:"Backup"})]),t("a",{role:"button",onClick:e[2]||(e[2]=n=>f()),class:"btn btn-sm btn-primary rounded-3"},e[8]||(e[8]=[t("i",{class:"bi bi-arrow-clockwise"},null,-1)]))]))],2),e[11]||(e[11]=t("hr",null,null,-1)),t("p",null,[s(o,{t:"If you're sure, please type in the configuration name below and click Delete"})]),$(t("input",{class:"form-control rounded-3 mb-3",placeholder:m(a),"onUpdate:modelValue":e[3]||(e[3]=n=>b.value=n),type:"text"},null,8,E),[[D,b.value]]),t("button",{class:"btn btn-danger w-100",onClick:e[4]||(e[4]=n=>k()),disabled:b.value!==m(a)||p.value},[e[10]||(e[10]=t("i",{class:"bi bi-trash-fill me-2 rounded-3"},null,-1)),s(o,{t:"Delete"})],8,G)])])])])]))}};export{F as default};
|
1
src/static/app/dist/assets/editConfiguration-DrLB14XU.js
vendored
Normal file
1
src/static/app/dist/assets/editConfiguration-Oqrarw_V.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
@media screen and (max-width: 567px){.inputGroup{&[data-v-4be4f48a]{flex-direction:column}h3[data-v-4be4f48a]{transform:rotate(90deg)}}}
|
1
src/static/app/dist/assets/index-BKNwSldE.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
@media screen and (max-width: 768px){.navbar-container[data-v-83a7789f]{position:absolute;z-index:1000;animation-duration:.4s;animation-fill-mode:both;display:none;animation-timing-function:cubic-bezier(.82,.58,.17,.9)}.navbar-container.active[data-v-83a7789f]{animation-direction:normal;display:block!important;animation-name:zoomInFade-83a7789f}}.navbar-container[data-v-83a7789f]{height:100vh}@supports (height: 100dvh){@media screen and (max-width: 768px){.navbar-container[data-v-83a7789f]{height:calc(100dvh - 50px)}}}@keyframes zoomInFade-83a7789f{0%{opacity:0;transform:translateY(60px);filter:blur(3px)}to{opacity:1;transform:translateY(0);filter:blur(0px)}}.messageCentre[data-v-ce114a8b]{top:1rem;right:1rem;width:calc(100% - 2rem)}main[data-v-ce114a8b]{height:100vh}@supports (height: 100dvh){@media screen and (max-width: 768px){main[data-v-ce114a8b]{height:calc(100dvh - 50px)}}}
|
15
src/static/app/dist/assets/index-Bqf0w701.css
vendored
Normal file
44
src/static/app/dist/assets/index-CP9pHThw.js
vendored
Normal file
1
src/static/app/dist/assets/index-CS7uR-yN.js
vendored
Normal file
1
src/static/app/dist/assets/index-L60y6kc9.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
function f(e){return e.includes(":")?6:e.includes(".")?4:0}function b(e){const i=f(e);if(!i)throw new Error(`Invalid IP address: ${e}`);let n=0n,o=0n;const r=Object.create(null);if(i===4)for(const s of e.split(".").map(BigInt).reverse())n+=s*2n**o,o+=8n;else{if(e.includes(".")&&(r.ipv4mapped=!0,e=e.split(":").map(t=>{if(t.includes(".")){const[c,l,d,a]=t.split(".").map($=>Number($).toString(16).padStart(2,"0"));return`${c}${l}:${d}${a}`}else return t}).join(":")),e.includes("%")){let t;[,e,t]=/(.+)%(.+)/.exec(e)||[],r.scopeid=t}const s=e.split(":"),u=s.indexOf("");if(u!==-1)for(;s.length<8;)s.splice(u,0,"");for(const t of s.map(c=>BigInt(parseInt(c||"0",16))).reverse())n+=t*2n**o,o+=16n}return r.number=n,r.version=i,r}const p={4:32,6:128},I=e=>e.includes("/")?f(e):0;function m(e){const i=I(e),n=Object.create(null);if(i)n.cidr=e,n.version=i;else{const a=f(e);if(a)n.cidr=`${e}/${p[a]}`,n.version=a;else throw new Error(`Network is not a CIDR or IP: ${e}`)}const[o,r]=n.cidr.split("/");if(!/^[0-9]+$/.test(r))throw new Error(`Network is not a CIDR or IP: ${e}`);n.prefix=r,n.single=r===String(p[n.version]);const{number:s,version:u}=b(o),t=p[u],c=s.toString(2).padStart(t,"0"),l=Number(t-r),d=c.substring(0,t-l);return n.start=BigInt(`0b${d}${"0".repeat(l)}`),n.end=BigInt(`0b${d}${"1".repeat(l)}`),n}export{m as p};
|
15
src/static/app/dist/assets/index.css
vendored
68
src/static/app/dist/assets/index.js
vendored
1
src/static/app/dist/assets/localeText-DgEvbt12.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{_ as t,G as e,t as o}from"./index-CP9pHThw.js";const s={name:"localeText",props:{t:""},computed:{getLocaleText(){return e(this.t)}}};function a(c,r,n,p,_,i){return o(this.getLocaleText)}const x=t(s,[["render",a]]);export{x as L};
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
1
src/static/app/dist/assets/message-CmGi0ZIz.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.message[data-v-f50b8f0c]{width:100%}@media screen and (min-width: 576px){.message[data-v-f50b8f0c]{width:400px}}
|
1
src/static/app/dist/assets/message-Yb5Da6zq.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{L as c}from"./localeText-DgEvbt12.js";import{d as n}from"./dayjs.min-BfBaVRGN.js";import{_ as d,a as r,c as m,b as s,d as i,f as t,t as e,n as l,j as _}from"./index-CP9pHThw.js";const p={name:"message",methods:{dayjs:n},components:{LocaleText:c},props:{message:Object},mounted(){setTimeout(()=>{this.message.show=!1},5e3)}},g=["id"],h={class:"card-body"},f={class:"d-flex"},x={class:"fw-bold d-block",style:{"text-transform":"uppercase"}},u={class:"ms-auto"};function b(y,v,w,T,j,a){const o=_("LocaleText");return r(),m("div",{class:l(["card shadow rounded-3 position-relative message ms-auto",{"text-bg-danger":this.message.type==="danger","text-bg-success":this.message.type==="success","text-bg-warning":this.message.type==="warning"}]),id:this.message.id},[s("div",h,[s("div",f,[s("small",x,[i(o,{t:"FROM "}),t(" "+e(this.message.from),1)]),s("small",u,e(a.dayjs().format("hh:mm A")),1)]),t(" "+e(this.message.content),1)])],10,g)}const M=d(p,[["render",b],["__scopeId","data-v-f50b8f0c"]]);export{M};
|
1
src/static/app/dist/assets/newConfiguration-BpiLZhj1.js
vendored
Normal file
10
src/static/app/dist/assets/osmap-BDScGXtl.js
vendored
Normal file
1
src/static/app/dist/assets/osmap-CoctJCk_.css
vendored
Normal file
1
src/static/app/dist/assets/peerConfigurationFile-B4JDnWh6.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{_ as f,D as m,r as _,a as o,c as a,b as e,d as l,w as g,T as h}from"./index-CP9pHThw.js";import{L as v}from"./localeText-DgEvbt12.js";const y={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0"},x={class:"container d-flex h-100 w-100"},w={class:"m-auto modal-dialog-centered dashboardModal justify-content-center"},C={class:"card rounded-3 shadow w-100"},k={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0"},F={class:"mb-0"},T={class:"card-body p-4"},D={class:"d-flex"},S=["disabled"],B={key:0,class:"d-block"},M={key:1,class:"d-block",id:"check"},G=["value"],L={__name:"peerConfigurationFile",props:{configurationFile:String},emits:["close"],setup(i,{emit:r}){const c=r,d=i,n=m(),s=_(!1),u=async()=>{navigator.clipboard&&navigator.clipboard.writeText?navigator.clipboard.writeText(d.configurationFile).then(()=>{s.value=!0,setTimeout(()=>{s.value=!1},3e3)}).catch(()=>{n.newMessage("WGDashboard","Failed to copy","danger")}):(document.querySelector("#peerConfigurationFile").select(),document.execCommand("copy")?(s.value=!0,setTimeout(()=>{s.value=!1},3e3)):n.newMessage("WGDashboard","Failed to copy","danger"))};return(p,t)=>(o(),a("div",y,[e("div",x,[e("div",w,[e("div",C,[e("div",k,[e("h4",F,[l(v,{t:"Peer Configuration File"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:t[0]||(t[0]=b=>c("close"))})]),e("div",T,[e("div",D,[e("button",{onClick:t[1]||(t[1]=b=>u()),disabled:s.value,class:"ms-auto btn bg-primary-subtle border-primary-subtle text-primary-emphasis rounded-3 position-relative"},[l(h,{name:"slide-up",mode:"out-in"},{default:g(()=>[s.value?(o(),a("span",M,t[3]||(t[3]=[e("i",{class:"bi bi-check-circle-fill"},null,-1)]))):(o(),a("span",B,t[2]||(t[2]=[e("i",{class:"bi bi-clipboard-fill"},null,-1)])))]),_:1})],8,S)]),e("textarea",{style:{height:"300px"},class:"form-control w-100 rounded-3 mt-2",disabled:"",id:"peerConfigurationFile",value:i.configurationFile},null,8,G)])])])])]))}},W=f(L,[["__scopeId","data-v-e8a2c914"]]);export{W as default};
|
1
src/static/app/dist/assets/peerConfigurationFile-fak3wop9.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.slide-up-enter-active[data-v-e8a2c914],.slide-up-leave-active[data-v-e8a2c914]{transition:all .2s cubic-bezier(.42,0,.22,1)}.slide-up-enter-from[data-v-e8a2c914],.slide-up-leave-to[data-v-e8a2c914]{opacity:0;transform:scale(.9)}@keyframes spin-e8a2c914{0%{transform:rotate(0)}to{transform:rotate(360deg)}}#check[data-v-e8a2c914]{animation:cubic-bezier(.42,0,.22,1.3) .7s spin-e8a2c914}
|
1
src/static/app/dist/assets/peerCreate-BKh8DeBv.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.list-move[data-v-6d5fc831],.list-enter-active[data-v-6d5fc831],.list-leave-active[data-v-6d5fc831]{transition:all .3s ease}.list-enter-from[data-v-6d5fc831],.list-leave-to[data-v-6d5fc831]{opacity:0;transform:translateY(10px)}.list-leave-active[data-v-6d5fc831]{position:absolute}.peerSettingContainer[data-v-17eb547c]{background-color:#00000060;z-index:9998}div[data-v-17eb547c]{transition:.2s ease-in-out}.inactiveField[data-v-17eb547c]{opacity:.4}.card[data-v-17eb547c]{max-height:100%}
|
1
src/static/app/dist/assets/peerCreate-DN_BpUiR.js
vendored
Normal file
1
src/static/app/dist/assets/peerJobs-3iA_tL3T.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{S as p,a as b}from"./schedulePeerJob-BSUNj4bD.js";import{_ as h,W as u,p as m,j as i,a as o,c as a,b as e,d as r,w as _,F as v,h as f,i as J,e as x,k as g}from"./index-CP9pHThw.js";import{L as w}from"./localeText-DgEvbt12.js";import"./vue-datepicker-CXBjFMKF.js";import"./dayjs.min-BfBaVRGN.js";const P={name:"peerJobs",setup(){return{store:u()}},props:{selectedPeer:Object},components:{LocaleText:w,SchedulePeerJob:p,ScheduleDropdown:b},data(){return{}},methods:{deleteJob(d){this.selectedPeer.jobs=this.selectedPeer.jobs.filter(t=>t.JobID!==d.JobID)},addJob(){this.selectedPeer.jobs.unshift(JSON.parse(JSON.stringify({JobID:m().toString(),Configuration:this.selectedPeer.configuration.Name,Peer:this.selectedPeer.id,Field:this.store.PeerScheduleJobs.dropdowns.Field[0].value,Operator:this.store.PeerScheduleJobs.dropdowns.Operator[0].value,Value:"",CreationDate:"",ExpireDate:"",Action:this.store.PeerScheduleJobs.dropdowns.Action[0].value})))}}},S={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll"},y={class:"container d-flex h-100 w-100"},$={class:"m-auto modal-dialog-centered dashboardModal"},C={class:"card rounded-3 shadow",style:{width:"700px"}},D={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2"},j={class:"mb-0 fw-normal"},k={class:"card-body px-4 pb-4 pt-2 position-relative"},N={class:"d-flex align-items-center mb-3"},T={class:"card shadow-sm",key:"none",style:{height:"153px"}},I={class:"card-body text-muted text-center d-flex"},L={class:"m-auto"};function O(d,t,B,F,V,A){const n=i("LocaleText"),l=i("SchedulePeerJob");return o(),a("div",S,[e("div",y,[e("div",$,[e("div",C,[e("div",D,[e("h4",j,[r(n,{t:"Schedule Jobs"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:t[0]||(t[0]=s=>this.$emit("close"))})]),e("div",k,[e("div",N,[e("button",{class:"btn bg-primary-subtle border-1 border-primary-subtle text-primary-emphasis rounded-3 shadow",onClick:t[1]||(t[1]=s=>this.addJob())},[t[3]||(t[3]=e("i",{class:"bi bi-plus-lg me-2"},null,-1)),r(n,{t:"Job"})])]),r(g,{name:"schedulePeerJobTransition",tag:"div",class:"position-relative"},{default:_(()=>[(o(!0),a(v,null,f(this.selectedPeer.jobs,(s,E)=>(o(),J(l,{onRefresh:t[2]||(t[2]=c=>this.$emit("refresh")),onDelete:c=>this.deleteJob(s),dropdowns:this.store.PeerScheduleJobs.dropdowns,key:s.JobID,pjob:s},null,8,["onDelete","dropdowns","pjob"]))),128)),this.selectedPeer.jobs.length===0?(o(),a("div",T,[e("div",I,[e("h6",L,[r(n,{t:"This peer does not have any job yet."})])])])):x("",!0)]),_:1})])])])])])}const z=h(P,[["render",O],["__scopeId","data-v-5bbdd42b"]]);export{z as default};
|
1
src/static/app/dist/assets/peerJobs-voXURBEt.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.schedulePeerJobTransition-move[data-v-5bbdd42b],.schedulePeerJobTransition-enter-active[data-v-5bbdd42b],.schedulePeerJobTransition-leave-active[data-v-5bbdd42b]{transition:all .4s cubic-bezier(.82,.58,.17,.9)}.schedulePeerJobTransition-enter-from[data-v-5bbdd42b],.schedulePeerJobTransition-leave-to[data-v-5bbdd42b]{opacity:0;transform:scale(.9)}.schedulePeerJobTransition-leave-active[data-v-5bbdd42b]{position:absolute;width:100%}
|
1
src/static/app/dist/assets/peerJobsAllModal-CRX5N8Ta.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{S as b}from"./schedulePeerJob-BSUNj4bD.js";import{_ as g,W as v,p as f,j as l,a as o,c as t,b as e,d as i,F as p,h,t as _,e as y,i as x}from"./index-CP9pHThw.js";import{L as J}from"./localeText-DgEvbt12.js";import"./vue-datepicker-CXBjFMKF.js";import"./dayjs.min-BfBaVRGN.js";const w={name:"peerJobsAllModal",setup(){return{store:v()}},components:{LocaleText:J,SchedulePeerJob:b},props:{configurationPeers:Array[Object]},methods:{getuuid(){return f()}},computed:{getAllJobs(){return this.configurationPeers.filter(r=>r.jobs.length>0)}}},A={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll"},$={class:"container d-flex h-100 w-100"},k={class:"m-auto modal-dialog-centered dashboardModal"},S={class:"card rounded-3 shadow",style:{width:"700px"}},L={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2"},j={class:"mb-0 fw-normal"},P={class:"card-body px-4 pb-4 pt-2"},C={key:0,class:"accordion",id:"peerJobsLogsModalAccordion"},M={class:"accordion-header"},B=["data-bs-target"],N={key:0},D={class:"text-muted"},T=["id"],V={class:"accordion-body"},F={key:1,class:"card shadow-sm",style:{height:"153px"}},O={class:"card-body text-muted text-center d-flex"},W={class:"m-auto"};function E(r,s,I,R,q,z){const n=l("LocaleText"),u=l("SchedulePeerJob");return o(),t("div",A,[e("div",$,[e("div",k,[e("div",S,[e("div",L,[e("h4",j,[i(n,{t:"All Active Jobs"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:s[0]||(s[0]=a=>this.$emit("close"))})]),e("div",P,[this.getAllJobs.length>0?(o(),t("div",C,[(o(!0),t(p,null,h(this.getAllJobs,(a,d)=>(o(),t("div",{class:"accordion-item",key:a.id},[e("h2",M,[e("button",{class:"accordion-button collapsed",type:"button","data-bs-toggle":"collapse","data-bs-target":"#collapse_"+d},[e("small",null,[e("strong",null,[a.name?(o(),t("span",N,_(a.name)+" • ",1)):y("",!0),e("samp",D,_(a.id),1)])])],8,B)]),e("div",{id:"collapse_"+d,class:"accordion-collapse collapse","data-bs-parent":"#peerJobsLogsModalAccordion"},[e("div",V,[(o(!0),t(p,null,h(a.jobs,c=>(o(),x(u,{onDelete:s[1]||(s[1]=m=>this.$emit("refresh")),onRefresh:s[2]||(s[2]=m=>this.$emit("refresh")),dropdowns:this.store.PeerScheduleJobs.dropdowns,viewOnly:!0,key:c.JobID,pjob:c},null,8,["dropdowns","pjob"]))),128))])],8,T)]))),128))])):(o(),t("div",F,[e("div",O,[e("span",W,[i(n,{t:"No active job at the moment."})])])]))])])])])])}const X=g(w,[["render",E]]);export{X as default};
|
1
src/static/app/dist/assets/peerJobsLogsModal-CBZxmvLB.js
vendored
Normal file
19
src/static/app/dist/assets/peerList-C4zXbUmw.js
vendored
Normal file
7
src/static/app/dist/assets/peerList-D1fpqVhD.css
vendored
Normal file
1
src/static/app/dist/assets/peerQRCode-C2Qs7b8y.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{b as a}from"./browser-CjSdxGTc.js";import{L as n}from"./localeText-DgEvbt12.js";import{_ as c,j as r,a as d,c as i,b as e,d as l}from"./index-CP9pHThw.js";const p={name:"peerQRCode",components:{LocaleText:n},props:{peerConfigData:String},mounted(){a.toCanvas(document.querySelector("#qrcode"),this.peerConfigData,o=>{o&&console.error(o)})}},_={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0"},m={class:"container d-flex h-100 w-100"},h={class:"m-auto modal-dialog-centered dashboardModal justify-content-center"},u={class:"card rounded-3 shadow"},f={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0"},b={class:"mb-0"},v={class:"card-body"},C={id:"qrcode",class:"rounded-3 shadow",ref:"qrcode"};function g(o,t,x,$,w,q){const s=r("LocaleText");return d(),i("div",_,[e("div",m,[e("div",h,[e("div",u,[e("div",f,[e("h4",b,[l(s,{t:"QR Code"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:t[0]||(t[0]=y=>this.$emit("close"))})]),e("div",v,[e("canvas",C,null,512)])])])])])}const Q=c(p,[["render",g]]);export{Q as default};
|
1
src/static/app/dist/assets/peerSettings-C0i7Fwmi.js
vendored
Normal file
1
src/static/app/dist/assets/peerSettings-Cm-ugvIi.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.toggleShowKey[data-v-a63ae8cb]{position:absolute;top:35px;right:12px}
|
1
src/static/app/dist/assets/peerShareLinkModal-KjBtYPtu.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{_ as g,D as f,q as h,j as p,a as s,c as r,b as t,d as o,n as m,i as n,t as _,e as b}from"./index-CP9pHThw.js";import{d}from"./dayjs.min-BfBaVRGN.js";import{V as y}from"./vue-datepicker-CXBjFMKF.js";import{L as S}from"./localeText-DgEvbt12.js";const k={name:"peerShareLinkModal",props:{peer:Object},components:{LocaleText:S,VueDatePicker:y},data(){return{dataCopy:void 0,loading:!1}},setup(){return{store:f()}},mounted(){this.dataCopy=JSON.parse(JSON.stringify(this.peer.ShareLink)).at(0)},watch:{"peer.ShareLink":{deep:!0,handler(e,a){a.length!==e.length&&(this.dataCopy=JSON.parse(JSON.stringify(this.peer.ShareLink)).at(0))}}},methods:{startSharing(){this.loading=!0,h("/api/sharePeer/create",{Configuration:this.peer.configuration.Name,Peer:this.peer.id,ExpireDate:d().add(7,"d").format("YYYY-MM-DD HH:mm:ss")},e=>{e.status?(this.peer.ShareLink=e.data,this.dataCopy=e.data.at(0)):this.store.newMessage("Server","Share link failed to create. Reason: "+e.message,"danger"),this.loading=!1})},updateLinkExpireDate(){h("/api/sharePeer/update",this.dataCopy,e=>{e.status?(this.dataCopy=e.data.at(0),this.peer.ShareLink=e.data,this.store.newMessage("Server","Link expire date updated","success")):this.store.newMessage("Server","Link expire date failed to update. Reason: "+e.message,"danger"),this.loading=!1})},stopSharing(){this.loading=!0,this.dataCopy.ExpireDate=d().format("YYYY-MM-DD HH:mm:ss"),this.updateLinkExpireDate()},parseTime(e){e?this.dataCopy.ExpireDate=d(e).format("YYYY-MM-DD HH:mm:ss"):this.dataCopy.ExpireDate=void 0,this.updateLinkExpireDate()}},computed:{getUrl(){const e=this.store.getActiveCrossServer();return e?`${e.host}/${this.$router.resolve({path:"/share",query:{ShareID:this.dataCopy.ShareID}}).href}`:window.location.origin+window.location.pathname+this.$router.resolve({path:"/share",query:{ShareID:this.dataCopy.ShareID}}).href}}},x={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll"},v={class:"container d-flex h-100 w-100"},C={class:"m-auto modal-dialog-centered dashboardModal",style:{width:"500px"}},D={class:"card rounded-3 shadow flex-grow-1"},w={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4"},L={class:"mb-0"},M={key:0,class:"card-body px-4 pb-4"},Y={key:0},$={class:"mb-3 text-muted"},E=["disabled"],H={key:1},V={class:"d-flex gap-2 mb-4"},N=["href"],P={class:"d-flex flex-column gap-2 mb-3"},O=["disabled"];function T(e,a,U,B,I,c){const i=p("LocaleText"),u=p("VueDatePicker");return s(),r("div",x,[t("div",v,[t("div",C,[t("div",D,[t("div",w,[t("h4",L,[o(i,{t:"Share Peer"})]),t("button",{type:"button",class:"btn-close ms-auto",onClick:a[0]||(a[0]=l=>this.$emit("close"))})]),this.peer.ShareLink?(s(),r("div",M,[this.dataCopy?(s(),r("div",H,[t("div",V,[a[4]||(a[4]=t("i",{class:"bi bi-link-45deg"},null,-1)),t("a",{href:this.getUrl,class:"text-decoration-none",target:"_blank"},_(c.getUrl),9,N)]),t("div",P,[t("small",null,[a[5]||(a[5]=t("i",{class:"bi bi-calendar me-2"},null,-1)),o(i,{t:"Expire At"})]),o(u,{is24:!0,"min-date":new Date,"model-value":this.dataCopy.ExpireDate,"onUpdate:modelValue":this.parseTime,"time-picker-inline":"",format:"yyyy-MM-dd HH:mm:ss","preview-format":"yyyy-MM-dd HH:mm:ss",dark:this.store.Configuration.Server.dashboard_theme==="dark"},null,8,["min-date","model-value","onUpdate:modelValue","dark"])]),t("button",{onClick:a[2]||(a[2]=l=>this.stopSharing()),disabled:this.loading,class:"w-100 btn bg-danger-subtle text-danger-emphasis border-1 border-danger-subtle rounded-3 shadow-sm"},[t("span",{class:m({"animate__animated animate__flash animate__infinite animate__slower":this.loading})},a[6]||(a[6]=[t("i",{class:"bi bi-send-slash-fill me-2"},null,-1)]),2),this.loading?(s(),n(i,{key:0,t:"Stop Sharing..."})):(s(),n(i,{key:1,t:"Stop Sharing"}))],8,O)])):(s(),r("div",Y,[t("h6",$,[o(i,{t:"Currently the peer is not sharing"})]),t("button",{onClick:a[1]||(a[1]=l=>this.startSharing()),disabled:this.loading,class:"w-100 btn bg-success-subtle text-success-emphasis border-1 border-success-subtle rounded-3 shadow-sm"},[t("span",{class:m({"animate__animated animate__flash animate__infinite animate__slower":this.loading})},a[3]||(a[3]=[t("i",{class:"bi bi-send-fill me-2"},null,-1)]),2),this.loading?(s(),n(i,{key:0,t:"Sharing..."})):(s(),n(i,{key:1,t:"Start Sharing"}))],8,E)]))])):b("",!0)])])])])}const R=g(k,[["render",T]]);export{R as default};
|
1
src/static/app/dist/assets/ping-DojRH9NX.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.pingPlaceholder[data-v-a08ce97e]{width:100%;height:79.98px}.ping-move[data-v-a08ce97e],.ping-enter-active[data-v-a08ce97e],.ping-leave-active[data-v-a08ce97e]{transition:all .4s cubic-bezier(.82,.58,.17,.9)}.ping-leave-active[data-v-a08ce97e]{position:absolute;width:100%}.ping-enter-from[data-v-a08ce97e],.ping-leave-to[data-v-a08ce97e]{opacity:0;filter:blur(3px)}
|
1
src/static/app/dist/assets/ping-JKqA0a7D.js
vendored
Normal file
1
src/static/app/dist/assets/restoreConfiguration-C77qni2g.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.dropdownIcon[data-v-626f1988]{transition:all .2s ease-in-out}.dropdownIcon.active[data-v-626f1988]{transform:rotate(180deg)}.steps{&[data-v-f0245d51]{transition:all .3s ease-in-out;opacity:.3}&.active[data-v-f0245d51]{opacity:1}}
|
4
src/static/app/dist/assets/restoreConfiguration-CICjh3u4.js
vendored
Normal file
1
src/static/app/dist/assets/schedulePeerJob--V7cpve7.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.btn.disabled[data-v-6a5aba2a]{opacity:1;background-color:#0d6efd17;border-color:transparent}[data-v-8f3f1b93]{font-size:.875rem}input[data-v-8f3f1b93]{padding:.1rem .4rem}input[data-v-8f3f1b93]:disabled{border-color:transparent;background-color:#0d6efd17;color:#0d6efd}.dp__main[data-v-8f3f1b93]{width:auto;flex-grow:1;--dp-input-padding: 2.5px 30px 2.5px 12px;--dp-border-radius: .5rem}
|
1
src/static/app/dist/assets/schedulePeerJob-BSUNj4bD.js
vendored
Normal file
1
src/static/app/dist/assets/selectPeers-Eb21QZ5T.js
vendored
Normal file
1
src/static/app/dist/assets/selectPeers-Wjnh8YUZ.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.card[data-v-177407c1]{height:100%}.dashboardModal[data-v-177407c1]{height:calc(100% - 1rem)!important}@media screen and (min-height: 700px){.card[data-v-177407c1]{height:700px}}.peerBtn[data-v-177407c1]{border:var(--bs-border-width) solid var(--bs-border-color)}.peerBtn.active[data-v-177407c1]{border:var(--bs-border-width) solid var(--bs-body-color)}
|
1
src/static/app/dist/assets/settings-DR10zZKy.js
vendored
Normal file
1
src/static/app/dist/assets/settings-H5PFVa1m.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
@media screen and (max-width: 992px){.apiKey-card-body{&[data-v-a76253c8]{flex-direction:column!important;align-items:start!important}div.ms-auto[data-v-a76253c8]{margin-left:0!important}div[data-v-a76253c8]{width:100%;align-items:start!important}small[data-v-a76253c8]{margin-right:auto}}}.apiKey-move[data-v-100ee9f9],.apiKey-enter-active[data-v-100ee9f9],.apiKey-leave-active[data-v-100ee9f9]{transition:all .5s ease}.apiKey-enter-from[data-v-100ee9f9],.apiKey-leave-to[data-v-100ee9f9]{opacity:0;transform:translateY(30px) scale(.9)}.apiKey-leave-active[data-v-100ee9f9]{position:absolute;width:100%}.dropdown-menu[data-v-0f26916d]{width:100%}.list-group{&[data-v-4aa2aed9]:first-child{border-top-left-radius:var(--bs-border-radius-lg);border-top-right-radius:var(--bs-border-radius-lg)}&[data-v-4aa2aed9]:last-child{border-bottom-left-radius:var(--bs-border-radius-lg);border-bottom-right-radius:var(--bs-border-radius-lg)}}
|
1
src/static/app/dist/assets/setup-DMTs4XPM.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{_ as u,D as m,q as p,c as r,b as e,d as o,f as c,t as h,e as f,m as l,s as d,a as i,j as w}from"./index-CP9pHThw.js";import{L as g}from"./localeText-DgEvbt12.js";const b={name:"setup",components:{LocaleText:g},setup(){return{store:m()}},data(){return{setup:{username:"",newPassword:"",repeatNewPassword:"",enable_totp:!0},loading:!1,errorMessage:"",done:!1}},computed:{goodToSubmit(){return this.setup.username&&this.setup.newPassword.length>=8&&this.setup.repeatNewPassword.length>=8&&this.setup.newPassword===this.setup.repeatNewPassword}},methods:{submit(){this.loading=!0,p("/api/Welcome_Finish",this.setup,n=>{n.status?(this.done=!0,this.$router.push("/2FASetup")):(document.querySelectorAll("#createAccount input").forEach(s=>s.classList.add("is-invalid")),this.errorMessage=n.message,document.querySelector(".login-container-fluid").scrollTo({top:0,left:0,behavior:"smooth"})),this.loading=!1})}}},_=["data-bs-theme"],x={class:"m-auto text-body",style:{width:"500px"}},v={class:"dashboardLogo display-4"},y={class:"mb-5"},P={key:0,class:"alert alert-danger"},N={class:"d-flex flex-column gap-3"},k={id:"createAccount",class:"d-flex flex-column gap-2"},S={class:"form-group text-body"},T={for:"username",class:"mb-1 text-muted"},C={class:"form-group text-body"},L={for:"password",class:"mb-1 text-muted"},V={class:"form-group text-body"},q={for:"confirmPassword",class:"mb-1 text-muted"},$=["disabled"],A={key:0,class:"d-flex align-items-center w-100"},M={key:1,class:"d-flex align-items-center w-100"};function B(n,s,D,E,U,F){const t=w("LocaleText");return i(),r("div",{class:"container-fluid login-container-fluid d-flex main pt-5 overflow-scroll","data-bs-theme":this.store.Configuration.Server.dashboard_theme},[e("div",x,[e("span",v,[o(t,{t:"Nice to meet you!"})]),e("p",y,[o(t,{t:"Please fill in the following fields to finish setup"}),s[4]||(s[4]=c(" 😊"))]),e("div",null,[e("h3",null,[o(t,{t:"Create an account"})]),this.errorMessage?(i(),r("div",P,h(this.errorMessage),1)):f("",!0),e("div",N,[e("form",k,[e("div",S,[e("label",T,[e("small",null,[o(t,{t:"Enter an username you like"})])]),l(e("input",{type:"text",autocomplete:"username","onUpdate:modelValue":s[0]||(s[0]=a=>this.setup.username=a),class:"form-control",id:"username",name:"username",required:""},null,512),[[d,this.setup.username]])]),e("div",C,[e("label",L,[e("small",null,[o(t,{t:"Enter a password"}),e("code",null,[o(t,{t:"(At least 8 characters and make sure is strong enough!)"})])])]),l(e("input",{type:"password",autocomplete:"new-password","onUpdate:modelValue":s[1]||(s[1]=a=>this.setup.newPassword=a),class:"form-control",id:"password",name:"password",required:""},null,512),[[d,this.setup.newPassword]])]),e("div",V,[e("label",q,[e("small",null,[o(t,{t:"Confirm password"})])]),l(e("input",{type:"password",autocomplete:"confirm-new-password","onUpdate:modelValue":s[2]||(s[2]=a=>this.setup.repeatNewPassword=a),class:"form-control",id:"confirmPassword",name:"confirmPassword",required:""},null,512),[[d,this.setup.repeatNewPassword]])])]),e("button",{class:"btn btn-dark btn-lg mb-5 d-flex btn-brand shadow align-items-center",ref:"signInBtn",disabled:!this.goodToSubmit||this.loading||this.done,onClick:s[3]||(s[3]=a=>this.submit())},[!this.loading&&!this.done?(i(),r("span",A,[o(t,{t:"Next"}),s[5]||(s[5]=e("i",{class:"bi bi-chevron-right ms-auto"},null,-1))])):(i(),r("span",M,[o(t,{t:"Saving..."}),s[6]||(s[6]=e("span",{class:"spinner-border ms-auto spinner-border-sm",role:"status"},[e("span",{class:"visually-hidden"},"Loading...")],-1))]))],8,$)])])])],8,_)}const W=u(b,[["render",B]]);export{W as default};
|
1
src/static/app/dist/assets/share-B4McccvP.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.animate__fadeInUp[data-v-1b44aacd]{animation-timing-function:cubic-bezier(.42,0,.22,1)}
|
1
src/static/app/dist/assets/share-zC8fY8Lp.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{_,r,D as p,g as u,c as m,b as t,d as c,$ as h,a as f,j as b}from"./index-CP9pHThw.js";import{b as v}from"./browser-CjSdxGTc.js";import{L as y}from"./localeText-DgEvbt12.js";const g={name:"share",components:{LocaleText:y},async setup(){const o=h(),e=r(!1),i=p(),n=r(""),s=r(void 0),l=r(new Blob);await u("/api/getDashboardTheme",{},d=>{n.value=d.data});const a=o.query.ShareID;return a===void 0||a.length===0?(s.value=void 0,e.value=!0):await u("/api/sharePeer/get",{ShareID:a},d=>{d.status?(s.value=d.data,l.value=new Blob([s.value.file],{type:"text/plain"})):s.value=void 0,e.value=!0}),{store:i,theme:n,peerConfiguration:s,blob:l}},mounted(){this.peerConfiguration&&v.toCanvas(document.querySelector("#qrcode"),this.peerConfiguration.file,o=>{o&&console.error(o)})},methods:{download(){const o=new Blob([this.peerConfiguration.file],{type:"text/plain"}),e=URL.createObjectURL(o),i=`${this.peerConfiguration.fileName}.conf`,n=document.createElement("a");n.href=e,n.download=i,n.click()}},computed:{getBlob(){return URL.createObjectURL(this.blob)}}},w=["data-bs-theme"],x={class:"m-auto text-body",style:{width:"500px"}},C={key:0,class:"text-center position-relative",style:{}},U={class:"position-absolute w-100 h-100 top-0 start-0 d-flex animate__animated animate__fadeInUp",style:{"animation-delay":"0.1s"}},I={class:"m-auto"},L={key:1,class:"d-flex align-items-center flex-column gap-3"},B={class:"h1 dashboardLogo text-center animate__animated animate__fadeInUp"},k={id:"qrcode",class:"rounded-3 shadow animate__animated animate__fadeInUp mb-3",ref:"qrcode"},D={class:"text-muted animate__animated animate__fadeInUp mb-1",style:{"animation-delay":"0.2s"}},R=["download","href"];function j(o,e,i,n,s,l){const a=b("LocaleText");return f(),m("div",{class:"container-fluid login-container-fluid d-flex main pt-5 overflow-scroll","data-bs-theme":this.theme},[t("div",x,[this.peerConfiguration?(f(),m("div",L,[t("div",B,[e[1]||(e[1]=t("h6",null,"WGDashboard",-1)),c(a,{t:"Scan QR Code with the WireGuard App to add peer"})]),t("canvas",k,null,512),t("p",D,[c(a,{t:"or click the button below to download the "}),e[2]||(e[2]=t("samp",null,".conf",-1)),c(a,{t:" file"})]),t("a",{download:this.peerConfiguration.fileName+".conf",href:l.getBlob,class:"btn btn-lg bg-primary-subtle text-primary-emphasis border-1 border-primary-subtle animate__animated animate__fadeInUp shadow-sm",style:{"animation-delay":"0.25s"}},e[3]||(e[3]=[t("i",{class:"bi bi-download"},null,-1)]),8,R)])):(f(),m("div",C,[e[0]||(e[0]=t("div",{class:"animate__animated animate__fadeInUp"},[t("h1",{style:{"font-size":"20rem",filter:"blur(1rem)","animation-duration":"7s"},class:"animate__animated animate__flash animate__infinite"},[t("i",{class:"bi bi-file-binary"})])],-1)),t("div",U,[t("h3",I,[c(a,{t:"Oh no... This link is either expired or invalid."})])])]))])],8,w)}const $=_(g,[["render",j],["__scopeId","data-v-1b44aacd"]]);export{$ as default};
|
1
src/static/app/dist/assets/signin-BSkygxAc.js
vendored
Normal file
1
src/static/app/dist/assets/signin-DaFiOgh_.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.dot.inactive[data-v-ed7817c7]{background-color:#dc3545;box-shadow:0 0 0 .2rem #dc354545}.spin[data-v-ed7817c7]{animation:spin-ed7817c7 1s infinite cubic-bezier(.82,.58,.17,.9)}@keyframes spin-ed7817c7{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@media screen and (max-width: 768px){.remoteServerContainer[data-v-ed7817c7]{flex-direction:column}.remoteServerContainer .button-group button[data-v-ed7817c7]{width:100%}}@media screen and (max-width: 768px){.login-box[data-v-95530d22]{width:100%!important}.login-box div[data-v-95530d22]{width:auto!important}}
|
1
src/static/app/dist/assets/totp-B8iqxVYn.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{_ as h,D as m,g as p,q as f,c as b,b as t,d as i,t as _,m as v,s as g,i as d,w as r,j as c,a as n}from"./index-CP9pHThw.js";import{b as x}from"./browser-CjSdxGTc.js";import{L as y}from"./localeText-DgEvbt12.js";const T={name:"totp",components:{LocaleText:y},async setup(){const s=m();let e="";return await p("/api/Welcome_GetTotpLink",{},a=>{a.status&&(e=a.data)}),{l:e,store:s}},mounted(){this.l&&x.toCanvas(document.getElementById("qrcode"),this.l,function(s){})},data(){return{totp:"",totpInvalidMessage:"",verified:!1}},methods:{validateTotp(){}},watch:{totp(s){const e=document.querySelector("#totp");e.classList.remove("is-invalid","is-valid"),s.length===6&&(console.log(s),/[0-9]{6}/.test(s)?f("/api/Welcome_VerifyTotpLink",{totp:s},a=>{a.status?(this.verified=!0,e.classList.add("is-valid"),this.$emit("verified")):(e.classList.add("is-invalid"),this.totpInvalidMessage="TOTP does not match.")}):(e.classList.add("is-invalid"),this.totpInvalidMessage="TOTP can only contain numbers"))}}},k=["data-bs-theme"],w={class:"m-auto text-body",style:{width:"500px"}},L={class:"d-flex flex-column"},M={class:"dashboardLogo display-4"},C={class:"mb-2"},P={class:"text-muted"},I={class:"p-3 bg-body-secondary rounded-3 border mb-3"},O={class:"text-muted mb-0"},B=["href"],$={style:{"line-break":"anywhere"}},q={for:"totp",class:"mb-2"},D={class:"text-muted"},S={class:"form-group mb-2"},A=["disabled"],E={class:"invalid-feedback"},F={class:"valid-feedback"},R={class:"d-flex gap-3 mt-5 flex-column"};function G(s,e,a,N,W,j){const o=c("LocaleText"),l=c("RouterLink");return n(),b("div",{class:"container-fluid login-container-fluid d-flex main pt-5 overflow-scroll","data-bs-theme":this.store.Configuration.Server.dashboard_theme},[t("div",w,[t("div",L,[t("div",null,[t("h1",M,[i(o,{t:"Multi-Factor Authentication (MFA)"})]),t("p",C,[t("small",P,[i(o,{t:"1. Please scan the following QR Code to generate TOTP with your choice of authenticator"})])]),e[1]||(e[1]=t("canvas",{id:"qrcode",class:"rounded-3 mb-2"},null,-1)),t("div",I,[t("p",O,[t("small",null,[i(o,{t:"Or you can click the link below:"})])]),t("a",{href:this.l},[t("code",$,_(this.l),1)],8,B)]),t("label",q,[t("small",D,[i(o,{t:"2. Enter the TOTP generated by your authenticator to verify"})])]),t("div",S,[v(t("input",{class:"form-control text-center totp",id:"totp",maxlength:"6",type:"text",inputmode:"numeric",autocomplete:"one-time-code","onUpdate:modelValue":e[0]||(e[0]=u=>this.totp=u),disabled:this.verified},null,8,A),[[g,this.totp]]),t("div",E,[i(o,{t:this.totpInvalidMessage},null,8,["t"])]),t("div",F,[i(o,{t:"TOTP verified!"})])])]),e[4]||(e[4]=t("hr",null,null,-1)),t("div",R,[this.verified?(n(),d(l,{key:1,to:"/",class:"btn btn-dark btn-lg d-flex btn-brand shadow align-items-center flex-grow-1 rounded-3"},{default:r(()=>[i(o,{t:"Complete"}),e[3]||(e[3]=t("i",{class:"bi bi-chevron-right ms-auto"},null,-1))]),_:1})):(n(),d(l,{key:0,to:"/",class:"btn bg-secondary-subtle text-secondary-emphasis rounded-3 flex-grow-1 btn-lg border-1 border-secondary-subtle shadow d-flex"},{default:r(()=>[i(o,{t:"I don't need MFA"}),e[2]||(e[2]=t("i",{class:"bi bi-chevron-right ms-auto"},null,-1))]),_:1}))])])])],8,k)}const z=h(T,[["render",G]]);export{z as default};
|
1
src/static/app/dist/assets/traceroute-C5JbTTY4.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.pingPlaceholder[data-v-549eb223]{width:100%;height:40px}.ping-leave-active[data-v-549eb223]{position:absolute}table th[data-v-549eb223],table td[data-v-549eb223]{padding:.5rem}.table[data-v-549eb223]>:not(caption)>*>*{background-color:transparent!important}.ping-move[data-v-549eb223],.ping-enter-active[data-v-549eb223],.ping-leave-active[data-v-549eb223]{transition:all .4s cubic-bezier(.82,.58,.17,.9)}.ping-leave-active[data-v-549eb223]{position:absolute;width:100%}.ping-enter-from[data-v-549eb223],.ping-leave-to[data-v-549eb223]{opacity:0;filter:blur(3px)}
|
1
src/static/app/dist/assets/traceroute-DtD7BkdA.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
import{_ as h,W as b,g,c as o,b as t,d as n,m as y,s as f,A as v,w as r,T as c,a,f as x,F as u,h as m,n as T,z as k,t as i,e as A,j as _}from"./index-CP9pHThw.js";import{O as w}from"./osmap-BDScGXtl.js";import{L as R}from"./localeText-DgEvbt12.js";const M={name:"traceroute",components:{LocaleText:R,OSMap:w},data(){return{tracing:!1,ipAddress:void 0,tracerouteResult:void 0}},setup(){return{store:b()}},methods:{execute(){this.ipAddress&&(this.tracing=!0,this.tracerouteResult=void 0,g("/api/traceroute/execute",{ipAddress:this.ipAddress},d=>{d.status?this.tracerouteResult=d.data:this.store.newMessage("Server",d.message,"danger"),this.tracing=!1}))}}},S={class:"mt-md-5 mt-3 text-body"},$={class:"container-md"},C={class:"mb-3 text-body"},L={class:"d-flex gap-2 flex-column mb-5"},P={class:"mb-1 text-muted",for:"ipAddress"},V=["disabled"],N=["disabled"],O={key:0,class:"d-block"},z={key:1,class:"d-block"},B={class:"position-relative"},I={key:"pingPlaceholder"},D={key:1},E={key:"table",class:"w-100 mt-2"},F={class:"table table-sm rounded-3 w-100"},G={scope:"col"},H={scope:"col"},K={scope:"col"},W={scope:"col"},j={scope:"col"},U={scope:"col"},q={key:0};function J(d,s,Q,X,Y,Z){const l=_("LocaleText"),p=_("OSMap");return a(),o("div",S,[t("div",$,[t("h3",C,[n(l,{t:"Traceroute"})]),t("div",L,[t("div",null,[t("label",P,[t("small",null,[n(l,{t:"Enter IP Address / Hostname"})])]),y(t("input",{disabled:this.tracing,id:"ipAddress",class:"form-control","onUpdate:modelValue":s[0]||(s[0]=e=>this.ipAddress=e),onKeyup:s[1]||(s[1]=v(e=>this.execute(),["enter"])),type:"text"},null,40,V),[[f,this.ipAddress]])]),t("button",{class:"btn btn-primary rounded-3 mt-3 position-relative",disabled:this.tracing||!this.ipAddress,onClick:s[2]||(s[2]=e=>this.execute())},[n(c,{name:"slide"},{default:r(()=>[this.tracing?(a(),o("span",z,s[4]||(s[4]=[t("span",{class:"spinner-border spinner-border-sm","aria-hidden":"true"},null,-1),t("span",{class:"visually-hidden",role:"status"},"Loading...",-1)]))):(a(),o("span",O,s[3]||(s[3]=[t("i",{class:"bi bi-person-walking me-2"},null,-1),x("Trace! ")])))]),_:1})],8,N)]),t("div",B,[n(c,{name:"ping"},{default:r(()=>[this.tracerouteResult?(a(),o("div",D,[n(p,{d:this.tracerouteResult,type:"traceroute"},null,8,["d"]),t("div",E,[t("table",F,[t("thead",null,[t("tr",null,[t("th",G,[n(l,{t:"Hop"})]),t("th",H,[n(l,{t:"IP Address"})]),t("th",K,[n(l,{t:"Average RTT (ms)"})]),t("th",W,[n(l,{t:"Min RTT (ms)"})]),t("th",j,[n(l,{t:"Max RTT (ms)"})]),t("th",U,[n(l,{t:"Geolocation"})])])]),t("tbody",null,[(a(!0),o(u,null,m(this.tracerouteResult,(e,tt)=>(a(),o("tr",null,[t("td",null,[t("small",null,i(e.hop),1)]),t("td",null,[t("small",null,i(e.ip),1)]),t("td",null,[t("small",null,i(e.avg_rtt),1)]),t("td",null,[t("small",null,i(e.min_rtt),1)]),t("td",null,[t("small",null,i(e.max_rtt),1)]),t("td",null,[e.geo.city&&e.geo.country?(a(),o("span",q,[t("small",null,i(e.geo.city)+", "+i(e.geo.country),1)])):A("",!0)])]))),256))])])])])):(a(),o("div",I,[s[5]||(s[5]=t("div",{class:"pingPlaceholder bg-body-secondary rounded-3 mb-3",style:{height:"300px !important"}},null,-1)),(a(),o(u,null,m(5,e=>t("div",{class:T(["pingPlaceholder bg-body-secondary rounded-3 mb-3",{"animate__animated animate__flash animate__slower animate__infinite":this.tracing}]),style:k({"animation-delay":`${e*.05}s`})},null,6)),64))]))]),_:1})])])])}const ot=h(M,[["render",J],["__scopeId","data-v-549eb223"]]);export{ot as default};
|
1
src/static/app/dist/assets/vue-datepicker-CXBjFMKF.js
vendored
Normal file
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
BIN
src/static/app/dist/img/logo.png
vendored
Normal file
After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
27
src/static/app/dist/index.html
vendored
@ -1,14 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/static/app/dist/favicon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WGDashboard</title>
|
||||
<script type="module" crossorigin src="/static/app/dist/assets/index.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/static/app/dist/assets/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="w-100 vh-100"></div>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="application-name" content="WGDashboard">
|
||||
<meta name="apple-mobile-web-app-title" content="WGDashboard">
|
||||
<link rel="manifest" href="/static/app/dist/json/manifest.json">
|
||||
<link rel="icon" href="/static/app/dist/favicon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WGDashboard</title>
|
||||
<script type="module" crossorigin src="/static/app/dist/assets/index-CP9pHThw.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/static/app/dist/assets/index-Bqf0w701.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
44
src/static/app/dist/json/manifest.json
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"theme_color": "#343a40",
|
||||
"background_color": "#343a40",
|
||||
"display": "fullscreen",
|
||||
"scope": "/",
|
||||
"start_url": "/",
|
||||
"name": "WGDashboard",
|
||||
"short_name": "WGDashboard",
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "https://donaldzou.github.io/WGDashboard-Documentation/images/sign-in.png",
|
||||
"sizes": "2880x1826",
|
||||
"type": "image/png",
|
||||
"form_factor": "wide"
|
||||
},
|
||||
{
|
||||
"src": "https://donaldzou.github.io/WGDashboard-Documentation/images/sign-in.png",
|
||||
"sizes": "2880x1826",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"icons": [
|
||||
{
|
||||
"src": "../img/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "../img/icon-256x256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "../img/icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "../img/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,13 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WGDashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="w-100 vh-100"></div>
|
||||
<script type="module" src="./src/main.js"></script>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="application-name" content="WGDashboard">
|
||||
<meta name="apple-mobile-web-app-title" content="WGDashboard">
|
||||
<link rel="manifest" href="/json/manifest.json">
|
||||
<link rel="icon" href="/favicon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WGDashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="./src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
3364
src/static/app/package-lock.json
generated
@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "app",
|
||||
"version": "4.0.4",
|
||||
"version": "4.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"buildcommitpush": "./build.sh",
|
||||
"build electron": "vite build && vite build --mode electron && cd ../../../../WGDashboard-Desktop && electron-builder --mac --win",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
@ -15,7 +16,7 @@
|
||||
"@vueuse/shared": "^10.9.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"bootstrap": "^5.3.2",
|
||||
"bootstrap-icons": "^1.11.2",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"cidr-tools": "^7.0.4",
|
||||
"dayjs": "^1.11.12",
|
||||
"electron-builder": "^24.13.3",
|
||||
@ -23,6 +24,7 @@
|
||||
"i": "^0.3.7",
|
||||
"is-cidr": "^5.0.3",
|
||||
"npm": "^10.5.0",
|
||||
"ol": "^10.2.1",
|
||||
"pinia": "^2.1.7",
|
||||
"qrcode": "^1.5.3",
|
||||
"qrcodejs": "^1.0.0",
|
||||
|
BIN
src/static/app/public/img/192x192ios.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
src/static/app/public/img/icon-192x192.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
src/static/app/public/img/icon-256x256.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
src/static/app/public/img/icon-384x384.png
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
src/static/app/public/img/icon-512x512.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
src/static/app/public/img/logo.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
src/static/app/public/img/wireguard_logo.png
Normal file
After Width: | Height: | Size: 80 KiB |
44
src/static/app/public/json/manifest.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"theme_color": "#343a40",
|
||||
"background_color": "#343a40",
|
||||
"display": "fullscreen",
|
||||
"scope": "/",
|
||||
"start_url": "/",
|
||||
"name": "WGDashboard",
|
||||
"short_name": "WGDashboard",
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "https://donaldzou.github.io/WGDashboard-Documentation/images/sign-in.png",
|
||||
"sizes": "2880x1826",
|
||||
"type": "image/png",
|
||||
"form_factor": "wide"
|
||||
},
|
||||
{
|
||||
"src": "https://donaldzou.github.io/WGDashboard-Documentation/images/sign-in.png",
|
||||
"sizes": "2880x1826",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"icons": [
|
||||
{
|
||||
"src": "../img/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "../img/icon-256x256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "../img/icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "../img/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,45 +1,43 @@
|
||||
<script setup>
|
||||
import { RouterView } from 'vue-router'
|
||||
<script setup async>
|
||||
import {RouterView, useRoute} from 'vue-router'
|
||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||
import {computed, watch} from "vue";
|
||||
const store = DashboardConfigurationStore();
|
||||
import "@/utilities/wireguard.js"
|
||||
store.initCrossServerConfiguration();
|
||||
if (window.IS_WGDASHBOARD_DESKTOP){
|
||||
store.IsElectronApp = true;
|
||||
store.CrossServerConfiguration.Enable = true;
|
||||
}
|
||||
|
||||
watch(store.CrossServerConfiguration, () => {
|
||||
store.syncCrossServerConfiguration()
|
||||
}, {
|
||||
deep: true
|
||||
});
|
||||
|
||||
const getActiveCrossServer = computed(() => {
|
||||
if (store.ActiveServerConfiguration){
|
||||
return store.CrossServerConfiguration.ServerList[store.ActiveServerConfiguration]
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
const route = useRoute()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="z-index: 9999; height: 5px" class="position-absolute loadingBar top-0 start-0"></div>
|
||||
<nav class="navbar bg-dark sticky-top" data-bs-theme="dark">
|
||||
<div class="container-fluid d-flex text-body align-items-center">
|
||||
<span class="navbar-brand mb-0 h1">WGDashboard</span>
|
||||
<small class="ms-auto text-muted" v-if="getActiveCrossServer !== undefined">
|
||||
<i class="bi bi-server me-2"></i>{{getActiveCrossServer.host}}
|
||||
</small>
|
||||
<RouterLink to="/" class="navbar-brand mb-0 h1">
|
||||
<img src="../public/img/logo.png" alt="WGDashboard Logo" style="width: 32px">
|
||||
</RouterLink>
|
||||
<a role="button" class="navbarBtn text-body"
|
||||
@click="store.ShowNavBar = !store.ShowNavBar"
|
||||
style="line-height: 0; font-size: 2rem">
|
||||
<i class="bi bi-list"></i></a>
|
||||
<Transition name="fade2" mode="out-in">
|
||||
<i class="bi bi-list" v-if="!store.ShowNavBar"></i>
|
||||
<i class="bi bi-x-lg" v-else></i>
|
||||
</Transition>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<Suspense>
|
||||
<RouterView v-slot="{ Component }">
|
||||
<Transition name="app" mode="out-in">
|
||||
<Transition name="app" mode="out-in" type="transition" appear>
|
||||
<Component :is="Component"></Component>
|
||||
</Transition>
|
||||
</RouterView>
|
||||
@ -49,24 +47,16 @@ const getActiveCrossServer = computed(() => {
|
||||
<style scoped>
|
||||
.app-enter-active,
|
||||
.app-leave-active {
|
||||
transition: all 0.3s cubic-bezier(0.82, 0.58, 0.17, 0.9);
|
||||
transition: all 0.7s cubic-bezier(0.82, 0.58, 0.17, 1);
|
||||
}
|
||||
|
||||
.app-enter-from{
|
||||
transform: translateY(20px);
|
||||
.app-enter-from,
|
||||
.app-leave-to{
|
||||
opacity: 0;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.app-leave-to {
|
||||
transform: translateY(-20px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.navbarBtn{
|
||||
.navbar{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
@ -0,0 +1,175 @@
|
||||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
import {computed, ref} from "vue";
|
||||
import {fetchPost} from "@/utilities/fetch.js";
|
||||
import {useRoute} from "vue-router";
|
||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||
import LocaleText from "@/components/text/localeText.vue";
|
||||
const props = defineProps(["b", "delay"])
|
||||
const deleteConfirmation = ref(false)
|
||||
const restoreConfirmation = ref(false)
|
||||
const route = useRoute()
|
||||
const emit = defineEmits(["refresh", "refreshPeersList"])
|
||||
const store = DashboardConfigurationStore()
|
||||
const loading = ref(false);
|
||||
const deleteBackup = () => {
|
||||
loading.value = true;
|
||||
fetchPost("/api/deleteWireguardConfigurationBackup", {
|
||||
configurationName: route.params.id,
|
||||
backupFileName: props.b.filename
|
||||
}, (res) => {
|
||||
loading.value = false;
|
||||
if (res.status){
|
||||
emit("refresh")
|
||||
store.newMessage("Server", "Backup deleted", "success")
|
||||
}else{
|
||||
store.newMessage("Server", "Backup failed to delete", "danger")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const restoreBackup = () => {
|
||||
loading.value = true;
|
||||
fetchPost("/api/restoreWireguardConfigurationBackup", {
|
||||
configurationName: route.params.id,
|
||||
backupFileName: props.b.filename
|
||||
}, (res) => {
|
||||
loading.value = false;
|
||||
restoreConfirmation.value = false;
|
||||
if (res.status){
|
||||
emit("refresh")
|
||||
store.newMessage("Server", "Backup restored with " + props.b.filename, "success")
|
||||
}else{
|
||||
store.newMessage("Server", "Backup failed to restore", "danger")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const delaySeconds = computed(() => {
|
||||
return props.delay + 's'
|
||||
})
|
||||
|
||||
const showContent = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card my-0 rounded-3">
|
||||
<div class="card-body position-relative">
|
||||
<Transition name="zoomReversed">
|
||||
<div
|
||||
v-if="deleteConfirmation"
|
||||
class="position-absolute w-100 h-100 confirmationContainer start-0 top-0 rounded-3 d-flex p-2">
|
||||
<div class="m-auto">
|
||||
<h5>
|
||||
<LocaleText t="Are you sure to delete this backup?"></LocaleText>
|
||||
</h5>
|
||||
<div class="d-flex gap-2 align-items-center justify-content-center">
|
||||
<button class="btn btn-danger rounded-3"
|
||||
:disabled="loading"
|
||||
@click='deleteBackup()'>
|
||||
<LocaleText t="Yes"></LocaleText>
|
||||
</button>
|
||||
<button
|
||||
@click="deleteConfirmation = false"
|
||||
:disabled="loading"
|
||||
class="btn bg-secondary-subtle text-secondary-emphasis border-secondary-subtle rounded-3">
|
||||
<LocaleText t="No"></LocaleText>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
<Transition name="zoomReversed">
|
||||
<div
|
||||
v-if="restoreConfirmation"
|
||||
class="position-absolute w-100 h-100 confirmationContainer start-0 top-0 rounded-3 d-flex p-2">
|
||||
<div class="m-auto">
|
||||
<h5>
|
||||
<LocaleText t="Are you sure to restore this backup?"></LocaleText>
|
||||
</h5>
|
||||
<div class="d-flex gap-2 align-items-center justify-content-center">
|
||||
<button
|
||||
:disabled="loading"
|
||||
@click="restoreBackup()"
|
||||
class="btn btn-success rounded-3">
|
||||
<LocaleText t="Yes"></LocaleText>
|
||||
</button>
|
||||
<button
|
||||
@click="restoreConfirmation = false"
|
||||
:disabled="loading"
|
||||
class="btn bg-secondary-subtle text-secondary-emphasis border-secondary-subtle rounded-3">
|
||||
<LocaleText t="No"></LocaleText>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
<div class="d-flex gap-3">
|
||||
<div class="d-flex flex-column">
|
||||
<small class="text-muted">
|
||||
<LocaleText t="Backup"></LocaleText>
|
||||
</small>
|
||||
<samp>{{b.filename}}</samp>
|
||||
</div>
|
||||
<div class="d-flex flex-column">
|
||||
<small class="text-muted">
|
||||
<LocaleText t="Backup Date"></LocaleText>
|
||||
</small>
|
||||
{{dayjs(b.backupDate, "YYYYMMDDHHmmss").format("YYYY-MM-DD HH:mm:ss")}}
|
||||
</div>
|
||||
<div class="d-flex gap-2 align-items-center ms-auto">
|
||||
<button
|
||||
@click="restoreConfirmation = true"
|
||||
class="btn bg-warning-subtle text-warning-emphasis border-warning-subtle rounded-3 btn-sm">
|
||||
<i class="bi bi-clock-history"></i>
|
||||
</button>
|
||||
<button
|
||||
@click="deleteConfirmation = true"
|
||||
class="btn bg-danger-subtle text-danger-emphasis border-danger-subtle rounded-3 btn-sm">
|
||||
<i class="bi bi-trash-fill"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="card rounded-3">
|
||||
<a role="button" class="card-header d-flex text-decoration-none align-items-center"
|
||||
:class="{'border-bottom-0': !showContent}"
|
||||
style="cursor: pointer" @click="showContent = !showContent">
|
||||
<small>.conf <LocaleText t="File"></LocaleText>
|
||||
</small>
|
||||
<i class="bi bi-chevron-down ms-auto"></i>
|
||||
</a>
|
||||
<div class="card-body" v-if="showContent">
|
||||
<textarea class="form-control rounded-3" :value="b.content"
|
||||
disabled
|
||||
style="height: 300px; font-family: var(--bs-font-monospace),sans-serif !important;"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex">
|
||||
<span>
|
||||
<i class="bi bi-database me-1"></i>
|
||||
<LocaleText t="Database File"></LocaleText>
|
||||
</span>
|
||||
<i class="bi ms-auto"
|
||||
:class="[b.database ? 'text-success bi-check-circle-fill' : 'text-danger bi-x-circle-fill']"
|
||||
></i>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.confirmationContainer{
|
||||
background-color: rgba(0, 0, 0, 0.53);
|
||||
z-index: 9999;
|
||||
backdrop-filter: blur(1px);
|
||||
-webkit-backdrop-filter: blur(1px);
|
||||
}
|
||||
|
||||
.list1-enter-active{
|
||||
transition-delay: v-bind(delaySeconds) !important;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,124 @@
|
||||
<script setup>
|
||||
import {onBeforeUnmount, onMounted, reactive, ref} from "vue";
|
||||
import {fetchGet} from "@/utilities/fetch.js";
|
||||
import {useRoute} from "vue-router";
|
||||
import dayjs from "dayjs";
|
||||
import LocaleText from "@/components/text/localeText.vue";
|
||||
import Backup from "@/components/configurationComponents/backupRestoreComponents/backup.vue";
|
||||
|
||||
const route = useRoute()
|
||||
const backups = ref([])
|
||||
const loading = ref(true)
|
||||
const emit = defineEmits(["close", "refreshPeersList"])
|
||||
|
||||
onMounted(() => {
|
||||
loadBackup();
|
||||
})
|
||||
|
||||
const loadBackup = () => {
|
||||
loading.value = true
|
||||
fetchGet("/api/getWireguardConfigurationBackup", {
|
||||
configurationName: route.params.id
|
||||
}, (res) => {
|
||||
backups.value = res.data;
|
||||
loading.value = false;
|
||||
})
|
||||
}
|
||||
|
||||
const createBackup = () => {
|
||||
fetchGet("/api/createWireguardConfigurationBackup", {
|
||||
configurationName: route.params.id
|
||||
}, (res) => {
|
||||
backups.value = res.data;
|
||||
loading.value = false;
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll" ref="editConfigurationContainer">
|
||||
<div class="d-flex h-100 w-100">
|
||||
<div class="modal-dialog-centered dashboardModal w-100 h-100 overflow-x-scroll flex-column gap-3 mx-3">
|
||||
<div class="my-5 d-flex gap-3 flex-column position-relative">
|
||||
<div class="title">
|
||||
<div class="d-flex mb-3">
|
||||
<h4 class="mb-0">
|
||||
<LocaleText t="Backup & Restore"></LocaleText>
|
||||
</h4>
|
||||
<button type="button" class="btn-close ms-auto" @click="$emit('close')"></button>
|
||||
</div>
|
||||
<button
|
||||
@click="createBackup()"
|
||||
class="btn bg-primary-subtle text-primary-emphasis border-primary-subtle rounded-3 w-100">
|
||||
<i class="bi bi-plus-circle-fill me-2"></i>
|
||||
<LocaleText t="Create Backup"></LocaleText>
|
||||
</button>
|
||||
</div>
|
||||
<div class="position-relative d-flex flex-column gap-3">
|
||||
<TransitionGroup name="list1" >
|
||||
<div class="text-center title"
|
||||
key="spinner"
|
||||
v-if="loading && backups.length === 0">
|
||||
<div class="spinner-border"></div>
|
||||
</div>
|
||||
<div class="card my-0 rounded-3"
|
||||
v-else-if="!loading && backups.length === 0"
|
||||
key="noBackups"
|
||||
>
|
||||
<div class="card-body text-center text-muted">
|
||||
<i class="bi bi-x-circle-fill me-2"></i>
|
||||
<LocaleText t="No backup yet, click the button above to create backup."></LocaleText>
|
||||
</div>
|
||||
</div>
|
||||
<Backup
|
||||
@refresh="loadBackup()"
|
||||
@refreshPeersList="emit('refreshPeersList')"
|
||||
:b="b" v-for="(b, index) in backups"
|
||||
:delay="index*0.05"
|
||||
:key="b.filename"
|
||||
></Backup>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.card, .title{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 700px) {
|
||||
.card, .title{
|
||||
width: 700px;
|
||||
}
|
||||
}
|
||||
|
||||
.animate__fadeInUp{
|
||||
animation-timing-function: cubic-bezier(0.42, 0, 0.22, 1.0)
|
||||
}
|
||||
|
||||
.list1-move, /* apply transition to moving elements */
|
||||
.list1-enter-active,
|
||||
.list1-leave-active {
|
||||
transition: all 0.5s cubic-bezier(0.42, 0, 0.22, 1.0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.list1-enter-from,
|
||||
.list1-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
|
||||
/* ensure leaving items are taken out of layout flow so that moving
|
||||
animations can be calculated correctly. */
|
||||
.list1-leave-active {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,128 @@
|
||||
<script setup>
|
||||
import LocaleText from "@/components/text/localeText.vue";
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import {onMounted, ref, useTemplateRef} from "vue";
|
||||
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
|
||||
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
|
||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||
const route = useRoute()
|
||||
const configurationName = route.params.id;
|
||||
const input = ref("")
|
||||
const router = useRouter()
|
||||
const store = DashboardConfigurationStore()
|
||||
const deleting = ref(false)
|
||||
|
||||
const deleteConfiguration = () => {
|
||||
clearInterval(store.Peers.RefreshInterval)
|
||||
deleting.value = true;
|
||||
fetchPost("/api/deleteWireguardConfiguration", {
|
||||
Name: configurationName
|
||||
}, (res) => {
|
||||
if (res.status){
|
||||
router.push('/')
|
||||
store.newMessage("Server", "Configuration deleted", "success")
|
||||
}else{
|
||||
deleting.value = false;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const loading = ref(true)
|
||||
const backups = ref([])
|
||||
let timeout = undefined;
|
||||
const getBackup = () => {
|
||||
loading.value = true;
|
||||
fetchGet("/api/getWireguardConfigurationBackup", {
|
||||
configurationName: configurationName
|
||||
}, (res) => {
|
||||
backups.value = res.data;
|
||||
loading.value = false;
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getBackup()
|
||||
})
|
||||
|
||||
const emits = defineEmits(["backup"])
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll">
|
||||
<div class="container d-flex h-100 w-100">
|
||||
<div class="m-auto modal-dialog-centered dashboardModal" style="width: 700px">
|
||||
<div class="card rounded-3 shadow flex-grow-1 bg-danger-subtle border-danger-subtle" id="deleteConfigurationContainer">
|
||||
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0">
|
||||
<h5 class="mb-0">
|
||||
<LocaleText t="Are you sure to delete this configuration?"></LocaleText>
|
||||
</h5>
|
||||
<button type="button" class="btn-close ms-auto" @click="$emit('close')"></button>
|
||||
</div>
|
||||
<div class="card-body px-4 text-muted">
|
||||
<p class="mb-0">
|
||||
<LocaleText t="Once you deleted this configuration:"></LocaleText>
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<LocaleText t="All connected peers will get disconnected"></LocaleText>
|
||||
</li>
|
||||
<li>
|
||||
<LocaleText t="Both configuration file (.conf) and database table related to this configuration will get deleted"></LocaleText>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="alert"
|
||||
:class="[loading ? 'alert-secondary' : (backups.length > 0 ? 'alert-success' : 'alert-danger')]">
|
||||
<div v-if="loading">
|
||||
<i class="bi bi-search me-2"></i>
|
||||
<LocaleText t="Checking backups..."></LocaleText>
|
||||
</div>
|
||||
<div v-else-if="backups.length > 0">
|
||||
<i class="bi bi-check-circle-fill me-2"></i>
|
||||
<LocaleText :t="'This configuration have ' + backups.length + ' backups'"></LocaleText>
|
||||
</div>
|
||||
<div v-else class="d-flex align-items-center gap-2">
|
||||
<i class="bi bi-x-circle-fill me-2"></i>
|
||||
<LocaleText t="This configuration have no backup"></LocaleText>
|
||||
<a role="button"
|
||||
@click="emits('backup')"
|
||||
class="ms-auto btn btn-sm btn-primary rounded-3">
|
||||
<i class="bi bi-clock-history me-2"></i>
|
||||
<LocaleText t="Backup"></LocaleText>
|
||||
</a>
|
||||
<a role="button"
|
||||
@click="getBackup()"
|
||||
class="btn btn-sm btn-primary rounded-3">
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<p>
|
||||
<LocaleText t="If you're sure, please type in the configuration name below and click Delete"></LocaleText>
|
||||
</p>
|
||||
<input class="form-control rounded-3 mb-3"
|
||||
:placeholder="configurationName"
|
||||
v-model="input"
|
||||
type="text">
|
||||
<button class="btn btn-danger w-100"
|
||||
@click="deleteConfiguration()"
|
||||
:disabled="input !== configurationName || deleting">
|
||||
<i class="bi bi-trash-fill me-2 rounded-3"></i>
|
||||
<LocaleText t="Delete"></LocaleText>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,215 @@
|
||||
<script setup>
|
||||
import LocaleText from "@/components/text/localeText.vue";
|
||||
import {onMounted, reactive, ref, useTemplateRef, watch} from "vue";
|
||||
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
|
||||
import {fetchPost} from "@/utilities/fetch.js";
|
||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||
import UpdateConfigurationName
|
||||
from "@/components/configurationComponents/editConfigurationComponents/updateConfigurationName.vue";
|
||||
const props = defineProps({
|
||||
configurationInfo: Object
|
||||
})
|
||||
const wgStore = WireguardConfigurationsStore()
|
||||
const store = DashboardConfigurationStore()
|
||||
const saving = ref(false)
|
||||
const data = reactive(JSON.parse(JSON.stringify(props.configurationInfo)))
|
||||
const editPrivateKey = ref(false)
|
||||
const dataChanged = ref(false)
|
||||
const confirmChanges = ref(false)
|
||||
const reqField = reactive({
|
||||
PrivateKey: true,
|
||||
IPAddress: true,
|
||||
ListenPort: true
|
||||
})
|
||||
const editConfigurationContainer = useTemplateRef("editConfigurationContainer")
|
||||
const genKey = () => {
|
||||
if (wgStore.checkWGKeyLength(data.PrivateKey)){
|
||||
reqField.PrivateKey = true;
|
||||
data.PublicKey = window.wireguard.generatePublicKey(data.PrivateKey)
|
||||
}else{
|
||||
reqField.PrivateKey = false;
|
||||
}
|
||||
}
|
||||
const resetForm = () => {
|
||||
dataChanged.value = false;
|
||||
Object.assign(data, JSON.parse(JSON.stringify(props.configurationInfo)))
|
||||
}
|
||||
const emit = defineEmits(["changed", "close"])
|
||||
const saveForm = () => {
|
||||
saving.value = true
|
||||
fetchPost("/api/updateWireguardConfiguration", data, (res) => {
|
||||
saving.value = false
|
||||
if (res.status){
|
||||
store.newMessage("Server", "Configuration saved", "success")
|
||||
dataChanged.value = false
|
||||
emit("dataChanged", res.data)
|
||||
|
||||
}else{
|
||||
store.newMessage("Server", res.message, "danger")
|
||||
}
|
||||
})
|
||||
}
|
||||
const updateConfigurationName = ref(false)
|
||||
|
||||
watch(data, () => {
|
||||
dataChanged.value = JSON.stringify(data) !== JSON.stringify(props.configurationInfo);
|
||||
}, {
|
||||
deep: true
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll" ref="editConfigurationContainer">
|
||||
<div class="container d-flex h-100 w-100">
|
||||
<div class="m-auto modal-dialog-centered dashboardModal" style="width: 700px">
|
||||
<div class="card rounded-3 shadow flex-grow-1">
|
||||
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4">
|
||||
<h4 class="mb-0">
|
||||
<LocaleText t="Configuration Settings"></LocaleText>
|
||||
</h4>
|
||||
<button type="button" class="btn-close ms-auto" @click="$emit('close')"></button>
|
||||
</div>
|
||||
<div class="card-body px-4 pb-4">
|
||||
<div class="d-flex gap-2 flex-column">
|
||||
<div class="d-flex align-items-center gap-3" v-if="!updateConfigurationName">
|
||||
<small class="text-muted">
|
||||
<LocaleText t="Name"></LocaleText>
|
||||
</small>
|
||||
<small>{{data.Name}}</small>
|
||||
<button
|
||||
@click="updateConfigurationName = true"
|
||||
class="btn btn-sm bg-danger-subtle border-danger-subtle text-danger-emphasis rounded-3 ms-auto">
|
||||
Update Name
|
||||
</button>
|
||||
</div>
|
||||
<UpdateConfigurationName
|
||||
@close="updateConfigurationName = false"
|
||||
:configuration-name="data.Name"
|
||||
v-if="updateConfigurationName"></UpdateConfigurationName>
|
||||
|
||||
<template v-else>
|
||||
<hr>
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<small class="text-muted" style="word-break: keep-all">
|
||||
<LocaleText t="Public Key"></LocaleText>
|
||||
</small>
|
||||
<small class="ms-auto" style="word-break: break-all">
|
||||
{{data.PublicKey}}
|
||||
</small>
|
||||
</div>
|
||||
<hr>
|
||||
<div>
|
||||
<div class="d-flex">
|
||||
<label for="configuration_private_key" class="form-label">
|
||||
<small class="text-muted d-block">
|
||||
<LocaleText t="Private Key"></LocaleText>
|
||||
</small>
|
||||
</label>
|
||||
<div class="form-check form-switch ms-auto">
|
||||
<input class="form-check-input"
|
||||
type="checkbox" role="switch" id="editPrivateKeySwitch"
|
||||
v-model="editPrivateKey"
|
||||
>
|
||||
<label class="form-check-label" for="editPrivateKeySwitch">
|
||||
<small>Edit</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" class="form-control form-control-sm rounded-3"
|
||||
:disabled="saving || !editPrivateKey"
|
||||
:class="{'is-invalid': !reqField.PrivateKey}"
|
||||
@keyup="genKey()"
|
||||
v-model="data.PrivateKey"
|
||||
id="configuration_private_key">
|
||||
</div>
|
||||
<div>
|
||||
<label for="configuration_ipaddress_cidr" class="form-label">
|
||||
<small class="text-muted">
|
||||
<LocaleText t="IP Address/CIDR"></LocaleText>
|
||||
</small>
|
||||
</label>
|
||||
<input type="text" class="form-control form-control-sm rounded-3"
|
||||
:disabled="saving"
|
||||
v-model="data.Address"
|
||||
id="configuration_ipaddress_cidr">
|
||||
</div>
|
||||
<div>
|
||||
<label for="configuration_listen_port" class="form-label">
|
||||
<small class="text-muted">
|
||||
<LocaleText t="Listen Port"></LocaleText>
|
||||
</small>
|
||||
</label>
|
||||
<input type="number" class="form-control form-control-sm rounded-3"
|
||||
:disabled="saving"
|
||||
v-model="data.ListenPort"
|
||||
id="configuration_listen_port">
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<label for="configuration_preup" class="form-label">
|
||||
<small class="text-muted">
|
||||
<LocaleText t="PreUp"></LocaleText>
|
||||
</small>
|
||||
</label>
|
||||
<input type="text" class="form-control form-control-sm rounded-3"
|
||||
:disabled="saving"
|
||||
v-model="data.PreUp"
|
||||
id="configuration_preup">
|
||||
</div>
|
||||
<div>
|
||||
<label for="configuration_predown" class="form-label">
|
||||
<small class="text-muted">
|
||||
<LocaleText t="PreDown"></LocaleText>
|
||||
</small>
|
||||
</label>
|
||||
<input type="text" class="form-control form-control-sm rounded-3"
|
||||
:disabled="saving"
|
||||
v-model="data.PreDown"
|
||||
id="configuration_predown">
|
||||
</div>
|
||||
<div>
|
||||
<label for="configuration_postup" class="form-label">
|
||||
<small class="text-muted">
|
||||
<LocaleText t="PostUp"></LocaleText>
|
||||
</small>
|
||||
</label>
|
||||
<input type="text" class="form-control form-control-sm rounded-3"
|
||||
:disabled="saving"
|
||||
v-model="data.PostUp"
|
||||
id="configuration_postup">
|
||||
</div>
|
||||
<div>
|
||||
<label for="configuration_postdown" class="form-label">
|
||||
<small class="text-muted">
|
||||
<LocaleText t="PostDown"></LocaleText>
|
||||
</small>
|
||||
</label>
|
||||
<input type="text" class="form-control form-control-sm rounded-3"
|
||||
:disabled="saving"
|
||||
v-model="data.PostDown"
|
||||
id="configuration_postdown">
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 mt-4">
|
||||
<button class="btn bg-secondary-subtle border-secondary-subtle text-secondary-emphasis rounded-3 shadow ms-auto"
|
||||
@click="resetForm()"
|
||||
:disabled="!dataChanged || saving">
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
</button>
|
||||
<button class="btn bg-primary-subtle border-primary-subtle text-primary-emphasis rounded-3 shadow"
|
||||
:disabled="!dataChanged || saving"
|
||||
@click="saveForm()"
|
||||
>
|
||||
<i class="bi bi-save-fill"></i></button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,110 @@
|
||||
<script setup>
|
||||
import {onMounted, reactive, ref, watch} from "vue";
|
||||
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
|
||||
import LocaleText from "@/components/text/localeText.vue";
|
||||
import {fetchPost} from "@/utilities/fetch.js";
|
||||
import {useRouter} from "vue-router";
|
||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||
const props = defineProps({
|
||||
configurationName: String
|
||||
})
|
||||
const emit = defineEmits(['close'])
|
||||
const newConfigurationName = reactive({
|
||||
data: "",
|
||||
valid: false
|
||||
});
|
||||
const store = WireguardConfigurationsStore()
|
||||
|
||||
onMounted(() => {
|
||||
watch(() => newConfigurationName.data, (newVal) => {
|
||||
newConfigurationName.valid = /^[a-zA-Z0-9_=+.-]{1,15}$/.test(newVal) && newVal.length > 0 && !store.Configurations.find(x => x.Name === newVal);
|
||||
})
|
||||
})
|
||||
const dashboardConfigurationStore = DashboardConfigurationStore()
|
||||
const loading = ref(false)
|
||||
const router = useRouter()
|
||||
const rename = async () => {
|
||||
if (newConfigurationName.data){
|
||||
loading.value = true
|
||||
clearInterval(dashboardConfigurationStore.Peers.RefreshInterval)
|
||||
await fetchPost("/api/renameWireguardConfiguration", {
|
||||
Name: props.configurationName,
|
||||
NewConfigurationName: newConfigurationName.data
|
||||
}, async (res) => {
|
||||
if (res.status){
|
||||
await store.getConfigurations()
|
||||
dashboardConfigurationStore.newMessage("Server", "Configuration renamed", "success")
|
||||
router.push(`/configuration/${newConfigurationName.data}/peers`)
|
||||
}else{
|
||||
dashboardConfigurationStore.newMessage("Server", res.message, "danger")
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card rounded-3 flex-grow-1 bg-danger-subtle border-danger-subtle border shadow">
|
||||
<div class="card-body">
|
||||
<p>
|
||||
<LocaleText t="To update this configuration's name, WGDashboard will execute the following operations:"></LocaleText>
|
||||
</p>
|
||||
<ol>
|
||||
<li>
|
||||
<LocaleText t="Duplicate current configuration's database table and .conf file with the new name"></LocaleText>
|
||||
</li>
|
||||
<li>
|
||||
<LocaleText t="Delete current configuration's database table and .conf file"></LocaleText>
|
||||
</li>
|
||||
</ol>
|
||||
<div class="d-flex align-items-center gap-3 inputGroup">
|
||||
<input class="form-control form-control-sm rounded-3" :value="configurationName" disabled>
|
||||
<h3 class="mb-0">
|
||||
<i class="bi bi-arrow-right"></i>
|
||||
</h3>
|
||||
<input class="form-control form-control-sm rounded-3"
|
||||
id="newConfigurationName"
|
||||
:class="[newConfigurationName.data ? (newConfigurationName.valid ? 'is-valid' : 'is-invalid') : '']"
|
||||
v-model="newConfigurationName.data">
|
||||
</div>
|
||||
<div class="invalid-feedback" :class="{'d-block': !newConfigurationName.valid && newConfigurationName.data}">
|
||||
<LocaleText t="Configuration name is invalid. Possible reasons:"></LocaleText>
|
||||
<ul class="mb-0">
|
||||
<li>
|
||||
<LocaleText t="Configuration name already exist."></LocaleText>
|
||||
</li>
|
||||
<li>
|
||||
<LocaleText t="Configuration name can only contain 15 lower/uppercase alphabet, numbers, underscore, equal sign, plus sign, period and hyphen."></LocaleText>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="d-flex mt-3">
|
||||
<button
|
||||
@click="emit('close')"
|
||||
class="btn btn-sm bg-secondary-subtle border-secondary-subtle text-secondary-emphasis rounded-3">
|
||||
<LocaleText t="Cancel"></LocaleText>
|
||||
</button>
|
||||
<button
|
||||
@click="rename()"
|
||||
:disabled="!newConfigurationName.data || loading"
|
||||
class="btn btn-sm btn-danger rounded-3 ms-auto">
|
||||
<LocaleText t="Save"></LocaleText>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@media screen and (max-width: 567px) {
|
||||
.inputGroup{
|
||||
flex-direction: column;
|
||||
|
||||
h3{
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -2,9 +2,12 @@
|
||||
import {fetchGet} from "@/utilities/fetch.js";
|
||||
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
|
||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||
import LocaleText from "@/components/text/localeText.vue";
|
||||
import {GetLocale} from "@/utilities/locale.js";
|
||||
|
||||
export default {
|
||||
name: "allowedIPsInput",
|
||||
components: {LocaleText},
|
||||
props: {
|
||||
data: Object,
|
||||
saving: Boolean,
|
||||
@ -30,6 +33,9 @@ export default {
|
||||
this.availableIp.filter(x =>
|
||||
x.includes(this.availableIpSearchString) && !this.data.allowed_ips.includes(x)) :
|
||||
this.availableIp.filter(x => !this.data.allowed_ips.includes(x))
|
||||
},
|
||||
inputGetLocale(){
|
||||
return GetLocale("Enter IP Address/CIDR")
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -40,7 +46,7 @@ export default {
|
||||
return true;
|
||||
}
|
||||
this.allowedIpFormatError = true;
|
||||
this.dashboardStore.newMessage('WGDashboard', 'Allowed IP is invalid', 'danger')
|
||||
this.dashboardStore.newMessage('WGDashboard', 'Allowed IPs is invalid', 'danger')
|
||||
return false;
|
||||
}
|
||||
},
|
||||
@ -63,7 +69,11 @@ export default {
|
||||
<template>
|
||||
<div :class="{inactiveField: this.bulk}">
|
||||
<label for="peer_allowed_ip_textbox" class="form-label">
|
||||
<small class="text-muted">Allowed IPs <code>(Required)</code></small>
|
||||
<small class="text-muted">
|
||||
<LocaleText t="Allowed IPs"></LocaleText>
|
||||
<code>
|
||||
<LocaleText t="(Required)"></LocaleText>
|
||||
</code></small>
|
||||
</label>
|
||||
<div class="d-flex gap-2 flex-wrap" :class="{'mb-2': this.data.allowed_ips.length > 0}">
|
||||
<TransitionGroup name="list">
|
||||
@ -77,7 +87,7 @@ export default {
|
||||
<div class="d-flex gap-2 align-items-center">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control form-control-sm rounded-start-3"
|
||||
placeholder="Enter IP Address/CIDR"
|
||||
:placeholder="this.inputGetLocale"
|
||||
:class="{'is-invalid': this.allowedIpFormatError}"
|
||||
v-model="customAvailableIp"
|
||||
:disabled="bulk">
|
||||
@ -88,23 +98,29 @@ export default {
|
||||
<i class="bi bi-plus-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-muted">or</small>
|
||||
<small class="text-muted">
|
||||
<LocaleText t="or"></LocaleText>
|
||||
</small>
|
||||
<div class="dropdown flex-grow-1">
|
||||
<button class="btn btn-outline-secondary btn-sm dropdown-toggle rounded-3 w-100"
|
||||
:disabled="!availableIp || bulk"
|
||||
data-bs-auto-close="outside"
|
||||
type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-filter-circle me-2"></i>
|
||||
Pick Available IP
|
||||
<LocaleText t="Pick Available IP"></LocaleText>
|
||||
</button>
|
||||
<ul class="dropdown-menu mt-2 shadow w-100 dropdown-menu-end rounded-3"
|
||||
v-if="this.availableIp"
|
||||
style="overflow-y: scroll; max-height: 270px; width: 300px !important;">
|
||||
<li>
|
||||
<div class="px-3 pb-2 pt-1">
|
||||
<input class="form-control form-control-sm rounded-3"
|
||||
v-model="this.availableIpSearchString"
|
||||
placeholder="Search...">
|
||||
<div class="px-3 pb-2 pt-1 d-flex gap-3 align-items-center">
|
||||
<label for="availableIpSearchString" class="text-muted">
|
||||
<i class="bi bi-search"></i>
|
||||
</label>
|
||||
<input
|
||||
id="availableIpSearchString"
|
||||
class="form-control form-control-sm rounded-3"
|
||||
v-model="this.availableIpSearchString">
|
||||
</div>
|
||||
</li>
|
||||
<li v-for="ip in this.searchAvailableIps" >
|
||||
@ -113,7 +129,9 @@ export default {
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="this.searchAvailableIps.length === 0">
|
||||
<small class="px-3 text-muted">No available IP containing "{{this.availableIpSearchString}}"</small>
|
||||
<small class="px-3 text-muted">
|
||||
<LocaleText t="No available IP containing"></LocaleText>
|
||||
"{{this.availableIpSearchString}}"</small>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|