Merge branch 'ngoduykhanh:master' into fix
This commit is contained in:
		
						commit
						1e589126ac
					
				| 
						 | 
					@ -0,0 +1,67 @@
 | 
				
			||||||
 | 
					# Contributing Guidelines
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Thank you for your interest in contributing to my project. Whether it's a bug report, new feature, correction, or additional
 | 
				
			||||||
 | 
					documentation, I greatly value feedback and contributions from my community.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Please read through this document before submitting any issues or pull requests to ensure I have all the necessary
 | 
				
			||||||
 | 
					information to effectively respond to your bug report or contribution.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Reporting Bugs/Feature Requests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					I welcome you to use the GitHub issue tracker to report bugs or suggest features.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already
 | 
				
			||||||
 | 
					reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- A reproducible test case or series of steps
 | 
				
			||||||
 | 
					- The version of my code being used
 | 
				
			||||||
 | 
					- Any modifications you've made relevant to the bug
 | 
				
			||||||
 | 
					- Anything unusual about your environment or deployment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Contributing via Pull Requests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Discussion of New Features
 | 
				
			||||||
 | 
					Before initiating the implementation of a new feature, I encourage contributors to open a discussion by creating a new GitHub issue. This allows me to provide feedback, share insights, and ensure alignment with the project's direction and save your time.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Process for Discussing New Features:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. **Create an Issue:**
 | 
				
			||||||
 | 
					   - Go to the "Issues" tab in the repository.
 | 
				
			||||||
 | 
					   - Click on "New Issue."
 | 
				
			||||||
 | 
					   - Clearly describe the proposed feature, its purpose, and potential benefits.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2. **Engage in Discussion:**
 | 
				
			||||||
 | 
					   - Respond promptly to comments and feedback from the community.
 | 
				
			||||||
 | 
					   - Be open to adjusting the feature based on collaborative input.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3. **Consensus Building:**
 | 
				
			||||||
 | 
					   - Strive to reach a consensus on the proposed feature.
 | 
				
			||||||
 | 
					   - Ensure alignment with the overall project vision.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Bug Fixes and Improvements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For bug fixes, documentation improvements, and general enhancements, feel free to submit a pull request directly.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Pull Request Guidelines:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. **Fork the Repository:**
 | 
				
			||||||
 | 
					   - Fork the repository to your GitHub account.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					2. **Create a Branch:**
 | 
				
			||||||
 | 
					   - Create a new branch for your changes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					3. **Make Changes:**
 | 
				
			||||||
 | 
					   - Make your changes and ensure they adhere to coding standards.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4. **Submit a Pull Request:**
 | 
				
			||||||
 | 
					   - Submit a pull request to the main repository.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					5. **Engage in Review:**
 | 
				
			||||||
 | 
					   - Be responsive to feedback and address any requested changes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					6. **Merge Process:**
 | 
				
			||||||
 | 
					   - Once approved, your changes will be merged into the main branch.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Licensing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					See the [LICENSE](LICENSE) file for my project's licensing.
 | 
				
			||||||
| 
						 | 
					@ -42,12 +42,13 @@ docker-compose up
 | 
				
			||||||
| `BIND_ADDRESS`              | The addresses that can access to the web interface and the port, use unix:///abspath/to/file.socket for unix domain socket.                                                 | 0.0.0.0:80                         |
 | 
					| `BIND_ADDRESS`              | The addresses that can access to the web interface and the port, use unix:///abspath/to/file.socket for unix domain socket.                                                 | 0.0.0.0:80                         |
 | 
				
			||||||
| `SESSION_SECRET`            | The secret key used to encrypt the session cookies. Set this to a random value                                                                                              | N/A                                |
 | 
					| `SESSION_SECRET`            | The secret key used to encrypt the session cookies. Set this to a random value                                                                                              | N/A                                |
 | 
				
			||||||
| `SESSION_SECRET_FILE`       | Optional filepath for the secret key used to encrypt the session cookies. Leave `SESSION_SECRET` blank to take effect                                                       | N/A                                |
 | 
					| `SESSION_SECRET_FILE`       | Optional filepath for the secret key used to encrypt the session cookies. Leave `SESSION_SECRET` blank to take effect                                                       | N/A                                |
 | 
				
			||||||
 | 
					| `SUBNET_RANGES`             | The list of address subdivision ranges. Format: `SR Name:10.0.1.0/24; SR2:10.0.2.0/24,10.0.3.0/24` Each CIDR must be inside one of the server interfaces.                   | N/A                                |
 | 
				
			||||||
| `WGUI_USERNAME`             | The username for the login page. Used for db initialization only                                                                                                            | `admin`                            |
 | 
					| `WGUI_USERNAME`             | The username for the login page. Used for db initialization only                                                                                                            | `admin`                            |
 | 
				
			||||||
| `WGUI_PASSWORD`             | The password for the user on the login page. Will be hashed automatically. Used for db initialization only                                                                  | `admin`                            |
 | 
					| `WGUI_PASSWORD`             | The password for the user on the login page. Will be hashed automatically. Used for db initialization only                                                                  | `admin`                            |
 | 
				
			||||||
| `WGUI_PASSWORD_FILE`        | Optional filepath for the user login password. Will be hashed automatically. Used for db initialization only. Leave `WGUI_PASSWORD` blank to take effect                    | N/A                                |
 | 
					| `WGUI_PASSWORD_FILE`        | Optional filepath for the user login password. Will be hashed automatically. Used for db initialization only. Leave `WGUI_PASSWORD` blank to take effect                    | N/A                                |
 | 
				
			||||||
| `WGUI_PASSWORD_HASH`        | The password hash for the user on the login page. (alternative to `WGUI_PASSWORD`). Used for db initialization only                                                         | N/A                                |
 | 
					| `WGUI_PASSWORD_HASH`        | The password hash for the user on the login page. (alternative to `WGUI_PASSWORD`). Used for db initialization only                                                         | N/A                                |
 | 
				
			||||||
| `WGUI_PASSWORD_HASH_FILE`   | Optional filepath for the user login password hash. (alternative to `WGUI_PASSWORD_FILE`). Used for db initialization only. Leave `WGUI_PASSWORD_HASH` blank to take effect | N/A                                |
 | 
					| `WGUI_PASSWORD_HASH_FILE`   | Optional filepath for the user login password hash. (alternative to `WGUI_PASSWORD_FILE`). Used for db initialization only. Leave `WGUI_PASSWORD_HASH` blank to take effect | N/A                                |
 | 
				
			||||||
| `WGUI_ENDPOINT_ADDRESS`     | The default endpoint address used in global settings where clients should connect to                                                                                        | Resolved to your public ip address |
 | 
					| `WGUI_ENDPOINT_ADDRESS`     | The default endpoint address used in global settings where clients should connect to. The endpoint can contain a port as well, useful when you are listening internally on the `WGUI_SERVER_LISTEN_PORT` port, but you forward on another port (ex 9000). Ex: myvpn.dyndns.com:9000                                                                                       | Resolved to your public ip address |
 | 
				
			||||||
| `WGUI_FAVICON_FILE_PATH`    | The file path used as website favicon                                                                                                                                       | Embedded WireGuard logo            |
 | 
					| `WGUI_FAVICON_FILE_PATH`    | The file path used as website favicon                                                                                                                                       | Embedded WireGuard logo            |
 | 
				
			||||||
| `WGUI_DNS`                  | The default DNS servers (comma-separated-list) used in the global settings                                                                                                  | `1.1.1.1`                          |
 | 
					| `WGUI_DNS`                  | The default DNS servers (comma-separated-list) used in the global settings                                                                                                  | `1.1.1.1`                          |
 | 
				
			||||||
| `WGUI_MTU`                  | The default MTU used in global settings                                                                                                                                     | `1450`                             |
 | 
					| `WGUI_MTU`                  | The default MTU used in global settings                                                                                                                                     | `1450`                             |
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,6 +18,11 @@ function renderClientList(data) {
 | 
				
			||||||
            allowedIpsHtml += `<small class="badge badge-secondary">${obj}</small> `;
 | 
					            allowedIpsHtml += `<small class="badge badge-secondary">${obj}</small> `;
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let subnetRangesString = "";
 | 
				
			||||||
 | 
					        if (obj.Client.subnet_ranges && obj.Client.subnet_ranges.length > 0) {
 | 
				
			||||||
 | 
					            subnetRangesString = obj.Client.subnet_ranges.join(',')
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // render client html content
 | 
					        // render client html content
 | 
				
			||||||
        let html = `<div class="col-sm-6 col-md-6 col-lg-4" id="client_${obj.Client.id}">
 | 
					        let html = `<div class="col-sm-6 col-md-6 col-lg-4" id="client_${obj.Client.id}">
 | 
				
			||||||
                        <div class="info-box">
 | 
					                        <div class="info-box">
 | 
				
			||||||
| 
						 | 
					@ -59,6 +64,7 @@ function renderClientList(data) {
 | 
				
			||||||
                                <hr>
 | 
					                                <hr>
 | 
				
			||||||
                                <span class="info-box-text"><i class="fas fa-user"></i> ${obj.Client.name}</span>
 | 
					                                <span class="info-box-text"><i class="fas fa-user"></i> ${obj.Client.name}</span>
 | 
				
			||||||
                                <span class="info-box-text" style="display: none"><i class="fas fa-key"></i> ${obj.Client.public_key}</span>
 | 
					                                <span class="info-box-text" style="display: none"><i class="fas fa-key"></i> ${obj.Client.public_key}</span>
 | 
				
			||||||
 | 
					                                <span class="info-box-text" style="display: none"><i class="fas fa-subnetrange"></i>${subnetRangesString}</span>
 | 
				
			||||||
                                <span class="info-box-text"><i class="fas fa-envelope"></i> ${obj.Client.email}</span>
 | 
					                                <span class="info-box-text"><i class="fas fa-envelope"></i> ${obj.Client.email}</span>
 | 
				
			||||||
                                <span class="info-box-text"><i class="fas fa-clock"></i>
 | 
					                                <span class="info-box-text"><i class="fas fa-clock"></i>
 | 
				
			||||||
                                    ${prettyDateTime(obj.Client.created_at)}</span>
 | 
					                                    ${prettyDateTime(obj.Client.created_at)}</span>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -366,6 +366,10 @@ func GetClients(db store.IStore) echo.HandlerFunc {
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for i, clientData := range clientDataList {
 | 
				
			||||||
 | 
								clientDataList[i] = util.FillClientSubnetRange(clientData)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return c.JSON(http.StatusOK, clientDataList)
 | 
							return c.JSON(http.StatusOK, clientDataList)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -391,7 +395,7 @@ func GetClient(db store.IStore) echo.HandlerFunc {
 | 
				
			||||||
			return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Client not found"})
 | 
								return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Client not found"})
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return c.JSON(http.StatusOK, clientData)
 | 
							return c.JSON(http.StatusOK, util.FillClientSubnetRange(clientData))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -988,6 +992,13 @@ func MachineIPAddresses() echo.HandlerFunc {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetOrderedSubnetRanges handler to get the ordered list of subnet ranges
 | 
				
			||||||
 | 
					func GetOrderedSubnetRanges() echo.HandlerFunc {
 | 
				
			||||||
 | 
						return func(c echo.Context) error {
 | 
				
			||||||
 | 
							return c.JSON(http.StatusOK, util.SubnetRangesOrder)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SuggestIPAllocation handler to get the list of ip address for client
 | 
					// SuggestIPAllocation handler to get the list of ip address for client
 | 
				
			||||||
func SuggestIPAllocation(db store.IStore) echo.HandlerFunc {
 | 
					func SuggestIPAllocation(db store.IStore) echo.HandlerFunc {
 | 
				
			||||||
	return func(c echo.Context) error {
 | 
						return func(c echo.Context) error {
 | 
				
			||||||
| 
						 | 
					@ -1009,20 +1020,46 @@ func SuggestIPAllocation(db store.IStore) echo.HandlerFunc {
 | 
				
			||||||
				false, "Cannot suggest ip allocation: failed to get list of allocated ip addresses",
 | 
									false, "Cannot suggest ip allocation: failed to get list of allocated ip addresses",
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		for _, cidr := range server.Interface.Addresses {
 | 
					
 | 
				
			||||||
			ip, err := util.GetAvailableIP(cidr, allocatedIPs)
 | 
							sr := c.QueryParam("sr")
 | 
				
			||||||
 | 
							searchCIDRList := make([]string, 0)
 | 
				
			||||||
 | 
							found := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Use subnet range or default to interface addresses
 | 
				
			||||||
 | 
							if util.SubnetRanges[sr] != nil {
 | 
				
			||||||
 | 
								for _, cidr := range util.SubnetRanges[sr] {
 | 
				
			||||||
 | 
									searchCIDRList = append(searchCIDRList, cidr.String())
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								searchCIDRList = append(searchCIDRList, server.Interface.Addresses...)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Save only unique IPs
 | 
				
			||||||
 | 
							ipSet := make(map[string]struct{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, cidr := range searchCIDRList {
 | 
				
			||||||
 | 
								ip, err := util.GetAvailableIP(cidr, allocatedIPs, server.Interface.Addresses)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Error("Failed to get available ip from a CIDR: ", err)
 | 
									log.Error("Failed to get available ip from a CIDR: ", err)
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								found = true
 | 
				
			||||||
 | 
								if strings.Contains(ip, ":") {
 | 
				
			||||||
 | 
									ipSet[fmt.Sprintf("%s/128", ip)] = struct{}{}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									ipSet[fmt.Sprintf("%s/32", ip)] = struct{}{}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if !found {
 | 
				
			||||||
			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{
 | 
								return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{
 | 
				
			||||||
				false,
 | 
									false,
 | 
				
			||||||
					fmt.Sprintf("Cannot suggest ip allocation: failed to get available ip from network %s", cidr),
 | 
									"Cannot suggest ip allocation: failed to get available ip. Try a different subnet or deallocate some ips.",
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
			if strings.Contains(ip, ":") {
 | 
					
 | 
				
			||||||
				suggestedIPs = append(suggestedIPs, fmt.Sprintf("%s/128", ip))
 | 
							for ip := range ipSet {
 | 
				
			||||||
			} else {
 | 
								suggestedIPs = append(suggestedIPs, ip)
 | 
				
			||||||
				suggestedIPs = append(suggestedIPs, fmt.Sprintf("%s/32", ip))
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return c.JSON(http.StatusOK, suggestedIPs)
 | 
							return c.JSON(http.StatusOK, suggestedIPs)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										16
									
								
								main.go
								
								
								
								
							
							
						
						
									
										16
									
								
								main.go
								
								
								
								
							| 
						 | 
					@ -45,6 +45,7 @@ var (
 | 
				
			||||||
	flagSessionSecret  string = util.RandomString(32)
 | 
						flagSessionSecret  string = util.RandomString(32)
 | 
				
			||||||
	flagWgConfTemplate string
 | 
						flagWgConfTemplate string
 | 
				
			||||||
	flagBasePath       string
 | 
						flagBasePath       string
 | 
				
			||||||
 | 
						flagSubnetRanges   string
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
| 
						 | 
					@ -81,6 +82,7 @@ func init() {
 | 
				
			||||||
	flag.StringVar(&flagEmailFromName, "email-from-name", util.LookupEnvOrString("EMAIL_FROM_NAME", flagEmailFromName), "'From' email name.")
 | 
						flag.StringVar(&flagEmailFromName, "email-from-name", util.LookupEnvOrString("EMAIL_FROM_NAME", flagEmailFromName), "'From' email name.")
 | 
				
			||||||
	flag.StringVar(&flagWgConfTemplate, "wg-conf-template", util.LookupEnvOrString("WG_CONF_TEMPLATE", flagWgConfTemplate), "Path to custom wg.conf template.")
 | 
						flag.StringVar(&flagWgConfTemplate, "wg-conf-template", util.LookupEnvOrString("WG_CONF_TEMPLATE", flagWgConfTemplate), "Path to custom wg.conf template.")
 | 
				
			||||||
	flag.StringVar(&flagBasePath, "base-path", util.LookupEnvOrString("BASE_PATH", flagBasePath), "The base path of the URL")
 | 
						flag.StringVar(&flagBasePath, "base-path", util.LookupEnvOrString("BASE_PATH", flagBasePath), "The base path of the URL")
 | 
				
			||||||
 | 
						flag.StringVar(&flagSubnetRanges, "subnet-ranges", util.LookupEnvOrString("SUBNET_RANGES", flagSubnetRanges), "IP ranges to choose from when assigning an IP for a client.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var (
 | 
						var (
 | 
				
			||||||
		smtpPasswordLookup  = util.LookupEnvOrString("SMTP_PASSWORD", flagSmtpPassword)
 | 
							smtpPasswordLookup  = util.LookupEnvOrString("SMTP_PASSWORD", flagSmtpPassword)
 | 
				
			||||||
| 
						 | 
					@ -127,6 +129,7 @@ func init() {
 | 
				
			||||||
	util.SessionSecret = []byte(flagSessionSecret)
 | 
						util.SessionSecret = []byte(flagSessionSecret)
 | 
				
			||||||
	util.WgConfTemplate = flagWgConfTemplate
 | 
						util.WgConfTemplate = flagWgConfTemplate
 | 
				
			||||||
	util.BasePath = util.ParseBasePath(flagBasePath)
 | 
						util.BasePath = util.ParseBasePath(flagBasePath)
 | 
				
			||||||
 | 
						util.SubnetRanges = util.ParseSubnetRanges(flagSubnetRanges)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// print only if log level is INFO or lower
 | 
						// print only if log level is INFO or lower
 | 
				
			||||||
	if lvl, _ := util.ParseLogLevel(util.LookupEnvOrString(util.LogLevel, "INFO")); lvl <= log.INFO {
 | 
						if lvl, _ := util.ParseLogLevel(util.LookupEnvOrString(util.LogLevel, "INFO")); lvl <= log.INFO {
 | 
				
			||||||
| 
						 | 
					@ -145,6 +148,7 @@ func init() {
 | 
				
			||||||
		//fmt.Println("Session secret\t:", util.SessionSecret)
 | 
							//fmt.Println("Session secret\t:", util.SessionSecret)
 | 
				
			||||||
		fmt.Println("Custom wg.conf\t:", util.WgConfTemplate)
 | 
							fmt.Println("Custom wg.conf\t:", util.WgConfTemplate)
 | 
				
			||||||
		fmt.Println("Base path\t:", util.BasePath+"/")
 | 
							fmt.Println("Base path\t:", util.BasePath+"/")
 | 
				
			||||||
 | 
							fmt.Println("Subnet ranges\t:", util.GetSubnetRangesString())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -170,6 +174,17 @@ func main() {
 | 
				
			||||||
	// create the wireguard config on start, if it doesn't exist
 | 
						// create the wireguard config on start, if it doesn't exist
 | 
				
			||||||
	initServerConfig(db, tmplDir)
 | 
						initServerConfig(db, tmplDir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if subnet ranges are valid for the server configuration
 | 
				
			||||||
 | 
						// Remove any non-valid CIDRs
 | 
				
			||||||
 | 
						if err := util.ValidateAndFixSubnetRanges(db); err != nil {
 | 
				
			||||||
 | 
							panic(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Print valid ranges
 | 
				
			||||||
 | 
						if lvl, _ := util.ParseLogLevel(util.LookupEnvOrString(util.LogLevel, "INFO")); lvl <= log.INFO {
 | 
				
			||||||
 | 
							fmt.Println("Valid subnet ranges:", util.GetSubnetRangesString())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// register routes
 | 
						// register routes
 | 
				
			||||||
	app := router.New(tmplDir, extraData, util.SessionSecret)
 | 
						app := router.New(tmplDir, extraData, util.SessionSecret)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -218,6 +233,7 @@ func main() {
 | 
				
			||||||
	app.GET(util.BasePath+"/api/clients", handler.GetClients(db), handler.ValidSession)
 | 
						app.GET(util.BasePath+"/api/clients", handler.GetClients(db), handler.ValidSession)
 | 
				
			||||||
	app.GET(util.BasePath+"/api/client/:id", handler.GetClient(db), handler.ValidSession)
 | 
						app.GET(util.BasePath+"/api/client/:id", handler.GetClient(db), handler.ValidSession)
 | 
				
			||||||
	app.GET(util.BasePath+"/api/machine-ips", handler.MachineIPAddresses(), handler.ValidSession)
 | 
						app.GET(util.BasePath+"/api/machine-ips", handler.MachineIPAddresses(), handler.ValidSession)
 | 
				
			||||||
 | 
						app.GET(util.BasePath+"/api/subnet-ranges", handler.GetOrderedSubnetRanges(), handler.ValidSession)
 | 
				
			||||||
	app.GET(util.BasePath+"/api/suggest-client-ips", handler.SuggestIPAllocation(db), handler.ValidSession)
 | 
						app.GET(util.BasePath+"/api/suggest-client-ips", handler.SuggestIPAllocation(db), handler.ValidSession)
 | 
				
			||||||
	app.POST(util.BasePath+"/api/apply-wg-config", handler.ApplyServerConfig(db, tmplDir), handler.ValidSession, handler.ContentTypeJson)
 | 
						app.POST(util.BasePath+"/api/apply-wg-config", handler.ApplyServerConfig(db, tmplDir), handler.ValidSession, handler.ContentTypeJson)
 | 
				
			||||||
	app.GET(util.BasePath+"/wake_on_lan_hosts", handler.GetWakeOnLanHosts(db), handler.ValidSession)
 | 
						app.GET(util.BasePath+"/wake_on_lan_hosts", handler.GetWakeOnLanHosts(db), handler.ValidSession)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@ type Client struct {
 | 
				
			||||||
	PresharedKey    string    `json:"preshared_key"`
 | 
						PresharedKey    string    `json:"preshared_key"`
 | 
				
			||||||
	Name            string    `json:"name"`
 | 
						Name            string    `json:"name"`
 | 
				
			||||||
	Email           string    `json:"email"`
 | 
						Email           string    `json:"email"`
 | 
				
			||||||
 | 
						SubnetRanges    []string  `json:"subnet_ranges,omitempty"`
 | 
				
			||||||
	AllocatedIPs    []string  `json:"allocated_ips"`
 | 
						AllocatedIPs    []string  `json:"allocated_ips"`
 | 
				
			||||||
	AllowedIPs      []string  `json:"allowed_ips"`
 | 
						AllowedIPs      []string  `json:"allowed_ips"`
 | 
				
			||||||
	ExtraAllowedIPs []string  `json:"extra_allowed_ips"`
 | 
						ExtraAllowedIPs []string  `json:"extra_allowed_ips"`
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,11 +58,13 @@
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="form-group form-group-sm">
 | 
					                <div class="form-group form-group-sm">
 | 
				
			||||||
                    <select name="status-selector" id="status-selector" class="custom-select form-control-navbar" style="margin-left: 0.5em; height: 90%; font-size: 14px;">
 | 
					                    <select name="status-selector" id="status-selector" class="custom-select form-control-navbar" style="margin-left: 0.5em; height: 90%; font-size: 14px;">
 | 
				
			||||||
 | 
					                        <!-- THIS SECTION IS OVERRIDDEN BY JS. SEE updateSearchList() function in clients.html BEFORE EDITING -->
 | 
				
			||||||
                        <option value="All">All</option>
 | 
					                        <option value="All">All</option>
 | 
				
			||||||
                        <option value="Enabled">Enabled</option>
 | 
					                        <option value="Enabled">Enabled</option>
 | 
				
			||||||
                        <option value="Disabled">Disabled</option>
 | 
					                        <option value="Disabled">Disabled</option>
 | 
				
			||||||
                        <option value="Connected">Connected</option>
 | 
					                        <option value="Connected">Connected</option>
 | 
				
			||||||
                        <option value="Disconnected">Disconnected</option>
 | 
					                        <option value="Disconnected">Disconnected</option>
 | 
				
			||||||
 | 
					                        <!-- THIS SECTION IS OVERRIDDEN BY JS. SEE updateSearchList() function in clients.html BEFORE EDITING -->
 | 
				
			||||||
                    </select>
 | 
					                    </select>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </form>
 | 
					            </form>
 | 
				
			||||||
| 
						 | 
					@ -209,6 +211,12 @@
 | 
				
			||||||
                                <label for="client_email" class="control-label">Email</label>
 | 
					                                <label for="client_email" class="control-label">Email</label>
 | 
				
			||||||
                                <input type="text" class="form-control" id="client_email" name="client_email">
 | 
					                                <input type="text" class="form-control" id="client_email" name="client_email">
 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            <div class="form-group">
 | 
				
			||||||
 | 
					                                <label for="subnet_ranges" class="control-label">Subnet range</label>
 | 
				
			||||||
 | 
					                                <select id="subnet_ranges" class="select2"
 | 
				
			||||||
 | 
					                                    data-placeholder="Select a subnet range" style="width: 100%;">
 | 
				
			||||||
 | 
					                                </select>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
                            <div class="form-group">
 | 
					                            <div class="form-group">
 | 
				
			||||||
                                <label for="client_allocated_ips" class="control-label">IP Allocation</label>
 | 
					                                <label for="client_allocated_ips" class="control-label">IP Allocation</label>
 | 
				
			||||||
                                <input type="text" data-role="tagsinput" class="form-control" id="client_allocated_ips">
 | 
					                                <input type="text" data-role="tagsinput" class="form-control" id="client_allocated_ips">
 | 
				
			||||||
| 
						 | 
					@ -368,6 +376,36 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $(document).ready(function () {
 | 
					        $(document).ready(function () {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            addGlobalStyle(`
 | 
				
			||||||
 | 
					.toast-top-right-fix {
 | 
				
			||||||
 | 
					    top: 67px;
 | 
				
			||||||
 | 
					    right: 12px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					            `, 'toastrToastStyleFix')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            toastr.options.closeDuration = 100;
 | 
				
			||||||
 | 
					            // toastr.options.timeOut = 10000;
 | 
				
			||||||
 | 
					            toastr.options.positionClass = 'toast-top-right-fix';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            updateApplyConfigVisibility()
 | 
				
			||||||
 | 
					            // from clients.html
 | 
				
			||||||
 | 
					            updateSearchList()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function addGlobalStyle(css, id) {
 | 
				
			||||||
 | 
					            if (!document.querySelector('#' + id)) {
 | 
				
			||||||
 | 
					                let head = document.head
 | 
				
			||||||
 | 
					                if (!head) { return }
 | 
				
			||||||
 | 
					                let style = document.createElement('style')
 | 
				
			||||||
 | 
					                style.type = 'text/css'
 | 
				
			||||||
 | 
					                style.id = id
 | 
				
			||||||
 | 
					                style.innerHTML = css
 | 
				
			||||||
 | 
					                head.appendChild(style)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function updateApplyConfigVisibility() {
 | 
				
			||||||
                $.ajax({
 | 
					                $.ajax({
 | 
				
			||||||
                    cache: false,
 | 
					                    cache: false,
 | 
				
			||||||
                    method: 'GET',
 | 
					                    method: 'GET',
 | 
				
			||||||
| 
						 | 
					@ -388,8 +426,7 @@
 | 
				
			||||||
                        toastr.error(responseJson['message']);
 | 
					                        toastr.error(responseJson['message']);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        // populateClient function for render new client info
 | 
					        // populateClient function for render new client info
 | 
				
			||||||
| 
						 | 
					@ -456,6 +493,7 @@
 | 
				
			||||||
                    if (window.location.pathname === "{{.basePath}}/") {
 | 
					                    if (window.location.pathname === "{{.basePath}}/") {
 | 
				
			||||||
                        populateClient(resp.id);
 | 
					                        populateClient(resp.id);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                    updateApplyConfigVisibility()
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                error: function(jqXHR, exception) {
 | 
					                error: function(jqXHR, exception) {
 | 
				
			||||||
                    const responseJson = jQuery.parseJSON(jqXHR.responseText);
 | 
					                    const responseJson = jQuery.parseJSON(jqXHR.responseText);
 | 
				
			||||||
| 
						 | 
					@ -466,19 +504,32 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // updateIPAllocationSuggestion function for automatically fill
 | 
					        // updateIPAllocationSuggestion function for automatically fill
 | 
				
			||||||
        // the IP Allocation input with suggested ip addresses
 | 
					        // the IP Allocation input with suggested ip addresses
 | 
				
			||||||
        function updateIPAllocationSuggestion() {
 | 
					        function updateIPAllocationSuggestion(forceDefault = false) {
 | 
				
			||||||
 | 
					            let subnetRange = $("#subnet_ranges").select2('val');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (forceDefault || !subnetRange || subnetRange.length === 0) {
 | 
				
			||||||
 | 
					                subnetRange = '__default_any__'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            $.ajax({
 | 
					            $.ajax({
 | 
				
			||||||
                cache: false,
 | 
					                cache: false,
 | 
				
			||||||
                method: 'GET',
 | 
					                method: 'GET',
 | 
				
			||||||
                url: '{{.basePath}}/api/suggest-client-ips',
 | 
					                url: `{{.basePath}}/api/suggest-client-ips?sr=${subnetRange}`,
 | 
				
			||||||
                dataType: 'json',
 | 
					                dataType: 'json',
 | 
				
			||||||
                contentType: "application/json",
 | 
					                contentType: "application/json",
 | 
				
			||||||
                success: function(data) {
 | 
					                success: function(data) {
 | 
				
			||||||
 | 
					                    const allocated_ips = $("#client_allocated_ips").val().split(",");
 | 
				
			||||||
 | 
					                    allocated_ips.forEach(function (item, index) {
 | 
				
			||||||
 | 
					                        $('#client_allocated_ips').removeTag(escape(item));
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
                    data.forEach(function (item, index) {
 | 
					                    data.forEach(function (item, index) {
 | 
				
			||||||
                        $('#client_allocated_ips').addTag(item);
 | 
					                        $('#client_allocated_ips').addTag(item);
 | 
				
			||||||
                    })
 | 
					                    })
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                error: function(jqXHR, exception) {
 | 
					                error: function(jqXHR, exception) {
 | 
				
			||||||
 | 
					                    const allocated_ips = $("#client_allocated_ips").val().split(",");
 | 
				
			||||||
 | 
					                    allocated_ips.forEach(function (item, index) {
 | 
				
			||||||
 | 
					                        $('#client_allocated_ips').removeTag(escape(item));
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
                    const responseJson = jQuery.parseJSON(jqXHR.responseText);
 | 
					                    const responseJson = jQuery.parseJSON(jqXHR.responseText);
 | 
				
			||||||
                    toastr.error(responseJson['message']);
 | 
					                    toastr.error(responseJson['message']);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
| 
						 | 
					@ -497,7 +548,6 @@
 | 
				
			||||||
            'defaultText': 'Add More',
 | 
					            'defaultText': 'Add More',
 | 
				
			||||||
            'removeWithBackspace': true,
 | 
					            'removeWithBackspace': true,
 | 
				
			||||||
            'minChars': 0,
 | 
					            'minChars': 0,
 | 
				
			||||||
            'minInputWidth': '100%',
 | 
					 | 
				
			||||||
            'placeholderColor': '#666666'
 | 
					            'placeholderColor': '#666666'
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -509,7 +559,6 @@
 | 
				
			||||||
            'defaultText': 'Add More',
 | 
					            'defaultText': 'Add More',
 | 
				
			||||||
            'removeWithBackspace': true,
 | 
					            'removeWithBackspace': true,
 | 
				
			||||||
            'minChars': 0,
 | 
					            'minChars': 0,
 | 
				
			||||||
            'minInputWidth': '100%',
 | 
					 | 
				
			||||||
            'placeholderColor': '#666666'
 | 
					            'placeholderColor': '#666666'
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -520,7 +569,6 @@
 | 
				
			||||||
            'defaultText': 'Add More',
 | 
					            'defaultText': 'Add More',
 | 
				
			||||||
            'removeWithBackspace': true,
 | 
					            'removeWithBackspace': true,
 | 
				
			||||||
            'minChars': 0,
 | 
					            'minChars': 0,
 | 
				
			||||||
            'minInputWidth': '100%',
 | 
					 | 
				
			||||||
            'placeholderColor': '#666666'
 | 
					            'placeholderColor': '#666666'
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -565,10 +613,17 @@
 | 
				
			||||||
                $("#client_preshared_key").val("");
 | 
					                $("#client_preshared_key").val("");
 | 
				
			||||||
                $("#client_allocated_ips").importTags('');
 | 
					                $("#client_allocated_ips").importTags('');
 | 
				
			||||||
                $("#client_extra_allowed_ips").importTags('');
 | 
					                $("#client_extra_allowed_ips").importTags('');
 | 
				
			||||||
                updateIPAllocationSuggestion();
 | 
					                updateSubnetRangesList("#subnet_ranges");
 | 
				
			||||||
 | 
					                updateIPAllocationSuggestion(true);
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // handle subnet range select
 | 
				
			||||||
 | 
					        $('#subnet_ranges').on('select2:select', function (e) {
 | 
				
			||||||
 | 
					            // console.log('Selected Option: ', $("#subnet_ranges").select2('val'));
 | 
				
			||||||
 | 
					            updateIPAllocationSuggestion();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // apply_config_confirm button event
 | 
					        // apply_config_confirm button event
 | 
				
			||||||
        $(document).ready(function () {
 | 
					        $(document).ready(function () {
 | 
				
			||||||
            $("#apply_config_confirm").click(function () {
 | 
					            $("#apply_config_confirm").click(function () {
 | 
				
			||||||
| 
						 | 
					@ -579,6 +634,7 @@
 | 
				
			||||||
                    dataType: 'json',
 | 
					                    dataType: 'json',
 | 
				
			||||||
                    contentType: "application/json",
 | 
					                    contentType: "application/json",
 | 
				
			||||||
                    success: function(data) {
 | 
					                    success: function(data) {
 | 
				
			||||||
 | 
					                        updateApplyConfigVisibility()
 | 
				
			||||||
                        $("#modal_apply_config").modal('hide');
 | 
					                        $("#modal_apply_config").modal('hide');
 | 
				
			||||||
                        toastr.success('Applied config successfully');
 | 
					                        toastr.success('Applied config successfully');
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -100,6 +100,12 @@ Wireguard Clients
 | 
				
			||||||
                        <label for="_client_email" class="control-label">Email</label>
 | 
					                        <label for="_client_email" class="control-label">Email</label>
 | 
				
			||||||
                        <input type="text" class="form-control" id="_client_email" name="client_email">
 | 
					                        <input type="text" class="form-control" id="_client_email" name="client_email">
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="form-group">
 | 
				
			||||||
 | 
					                        <label for="_subnet_ranges" class="control-label">Subnet range</label>
 | 
				
			||||||
 | 
					                        <select id="_subnet_ranges" class="select2"
 | 
				
			||||||
 | 
					                            data-placeholder="Select a subnet range" style="width: 100%;">
 | 
				
			||||||
 | 
					                        </select>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
                    <div class="form-group">
 | 
					                    <div class="form-group">
 | 
				
			||||||
                        <label for="_client_allocated_ips" class="control-label">IP Allocation</label>
 | 
					                        <label for="_client_allocated_ips" class="control-label">IP Allocation</label>
 | 
				
			||||||
                        <input type="text" data-role="tagsinput" class="form-control" id="_client_allocated_ips">
 | 
					                        <input type="text" data-role="tagsinput" class="form-control" id="_client_allocated_ips">
 | 
				
			||||||
| 
						 | 
					@ -253,12 +259,101 @@ Wireguard Clients
 | 
				
			||||||
            setClientStatus(clientID, true);
 | 
					            setClientStatus(clientID, true);
 | 
				
			||||||
            const divElement = document.getElementById("paused_" + clientID);
 | 
					            const divElement = document.getElementById("paused_" + clientID);
 | 
				
			||||||
            divElement.style.visibility = "hidden";
 | 
					            divElement.style.visibility = "hidden";
 | 
				
			||||||
 | 
					            updateApplyConfigVisibility()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        function pauseClient(clientID) {
 | 
					        function pauseClient(clientID) {
 | 
				
			||||||
            setClientStatus(clientID, false);
 | 
					            setClientStatus(clientID, false);
 | 
				
			||||||
            const divElement = document.getElementById("paused_" + clientID);
 | 
					            const divElement = document.getElementById("paused_" + clientID);
 | 
				
			||||||
            divElement.style.visibility = "visible";
 | 
					            divElement.style.visibility = "visible";
 | 
				
			||||||
 | 
					            updateApplyConfigVisibility()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // updateIPAllocationSuggestion function for automatically fill
 | 
				
			||||||
 | 
					        // the IP Allocation input with suggested ip addresses
 | 
				
			||||||
 | 
					        // FOR CHANGING A SUBNET OF AN EXISTING CLIENT
 | 
				
			||||||
 | 
					        function updateIPAllocationSuggestionExisting() {
 | 
				
			||||||
 | 
					            let subnetRange = $("#_subnet_ranges").select2('val');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!subnetRange || subnetRange.length === 0) {
 | 
				
			||||||
 | 
					                subnetRange = '__default_any__'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            $.ajax({
 | 
				
			||||||
 | 
					                cache: false,
 | 
				
			||||||
 | 
					                method: 'GET',
 | 
				
			||||||
 | 
					                url: `{{.basePath}}/api/suggest-client-ips?sr=${subnetRange}`,
 | 
				
			||||||
 | 
					                dataType: 'json',
 | 
				
			||||||
 | 
					                contentType: "application/json",
 | 
				
			||||||
 | 
					                success: function(data) {
 | 
				
			||||||
 | 
					                    const allocated_ips = $("#_client_allocated_ips").val().split(",");
 | 
				
			||||||
 | 
					                    allocated_ips.forEach(function (item, index) {
 | 
				
			||||||
 | 
					                        $('#_client_allocated_ips').removeTag(escape(item));
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    data.forEach(function (item, index) {
 | 
				
			||||||
 | 
					                        $('#_client_allocated_ips').addTag(item);
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                error: function(jqXHR, exception) {
 | 
				
			||||||
 | 
					                    const allocated_ips = $("#_client_allocated_ips").val().split(",");
 | 
				
			||||||
 | 
					                    allocated_ips.forEach(function (item, index) {
 | 
				
			||||||
 | 
					                        $('#_client_allocated_ips').removeTag(escape(item));
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    const responseJson = jQuery.parseJSON(jqXHR.responseText);
 | 
				
			||||||
 | 
					                    toastr.error(responseJson['message']);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function updateSubnetRangesList(elementID, preselectedVal) {
 | 
				
			||||||
 | 
					            $.getJSON("{{.basePath}}/api/subnet-ranges", null, function(data) {
 | 
				
			||||||
 | 
					                $(`${elementID} option`).remove();
 | 
				
			||||||
 | 
					                $(elementID).append(
 | 
				
			||||||
 | 
					                    $("<option></option>")
 | 
				
			||||||
 | 
					                        .text("Any")
 | 
				
			||||||
 | 
					                        .val("__default_any__")
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                $.each(data, function(index, item) {
 | 
				
			||||||
 | 
					                    $(elementID).append(
 | 
				
			||||||
 | 
					                        $("<option></option>")
 | 
				
			||||||
 | 
					                            .text(item)
 | 
				
			||||||
 | 
					                            .val(item)
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    if (item === preselectedVal) {
 | 
				
			||||||
 | 
					                        console.log(preselectedVal);
 | 
				
			||||||
 | 
					                        $(elementID).val(preselectedVal).trigger('change')
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function updateSearchList() {
 | 
				
			||||||
 | 
					            $.getJSON("{{.basePath}}/api/subnet-ranges", null, function(data) {
 | 
				
			||||||
 | 
					                $("#status-selector option").remove();
 | 
				
			||||||
 | 
					                $("#status-selector").append(
 | 
				
			||||||
 | 
					                    $("<option></option>")
 | 
				
			||||||
 | 
					                        .text("All")
 | 
				
			||||||
 | 
					                        .val("All"),
 | 
				
			||||||
 | 
					                    $("<option></option>")
 | 
				
			||||||
 | 
					                        .text("Enabled")
 | 
				
			||||||
 | 
					                        .val("Enabled"),
 | 
				
			||||||
 | 
					                    $("<option></option>")
 | 
				
			||||||
 | 
					                        .text("Disabled")
 | 
				
			||||||
 | 
					                        .val("Disabled"),
 | 
				
			||||||
 | 
					                    $("<option></option>")
 | 
				
			||||||
 | 
					                        .text("Connected")
 | 
				
			||||||
 | 
					                        .val("Connected"),
 | 
				
			||||||
 | 
					                    $("<option></option>")
 | 
				
			||||||
 | 
					                        .text("Disconnected")
 | 
				
			||||||
 | 
					                        .val("Disconnected")
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                $.each(data, function(index, item) {
 | 
				
			||||||
 | 
					                    $("#status-selector").append(
 | 
				
			||||||
 | 
					                        $("<option></option>")
 | 
				
			||||||
 | 
					                            .text(item)
 | 
				
			||||||
 | 
					                            .val(item)
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
    </script>
 | 
					    </script>
 | 
				
			||||||
    <script>
 | 
					    <script>
 | 
				
			||||||
| 
						 | 
					@ -349,7 +444,18 @@ Wireguard Clients
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                default:
 | 
					                default:
 | 
				
			||||||
                    $('.col-lg-4').show();
 | 
					                    $('.col-lg-4').hide();
 | 
				
			||||||
 | 
					                    const selectedSR = $("#status-selector").val()
 | 
				
			||||||
 | 
					                    $(".fa-subnetrange").each(function () {
 | 
				
			||||||
 | 
					                        const srs = $(this).parent().text().trim().split(',')
 | 
				
			||||||
 | 
					                        for (const sr of srs) {
 | 
				
			||||||
 | 
					                            if (sr === selectedSR) {
 | 
				
			||||||
 | 
					                                $(this).closest('.col-lg-4').show();
 | 
				
			||||||
 | 
					                                break
 | 
				
			||||||
 | 
					                            }                            
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                    // $('.col-lg-4').show();
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
| 
						 | 
					@ -400,6 +506,7 @@ Wireguard Clients
 | 
				
			||||||
                        toastr.success('Removed client successfully');
 | 
					                        toastr.success('Removed client successfully');
 | 
				
			||||||
                        const divElement = document.getElementById('client_' + client_id);
 | 
					                        const divElement = document.getElementById('client_' + client_id);
 | 
				
			||||||
                        divElement.style.display = "none";
 | 
					                        divElement.style.display = "none";
 | 
				
			||||||
 | 
					                        updateApplyConfigVisibility()
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    error: function(jqXHR, exception) {
 | 
					                    error: function(jqXHR, exception) {
 | 
				
			||||||
                        const responseJson = jQuery.parseJSON(jqXHR.responseText);
 | 
					                        const responseJson = jQuery.parseJSON(jqXHR.responseText);
 | 
				
			||||||
| 
						 | 
					@ -427,7 +534,6 @@ Wireguard Clients
 | 
				
			||||||
                    'defaultText': 'Add More',
 | 
					                    'defaultText': 'Add More',
 | 
				
			||||||
                    'removeWithBackspace': true,
 | 
					                    'removeWithBackspace': true,
 | 
				
			||||||
                    'minChars': 0,
 | 
					                    'minChars': 0,
 | 
				
			||||||
                    'minInputWidth': '100%',
 | 
					 | 
				
			||||||
                    'placeholderColor': '#666666'
 | 
					                    'placeholderColor': '#666666'
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -439,7 +545,6 @@ Wireguard Clients
 | 
				
			||||||
                    'defaultText': 'Add More',
 | 
					                    'defaultText': 'Add More',
 | 
				
			||||||
                    'removeWithBackspace': true,
 | 
					                    'removeWithBackspace': true,
 | 
				
			||||||
                    'minChars': 0,
 | 
					                    'minChars': 0,
 | 
				
			||||||
                    'minInputWidth': '100%',
 | 
					 | 
				
			||||||
                    'placeholderColor': '#666666'
 | 
					                    'placeholderColor': '#666666'
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -450,7 +555,6 @@ Wireguard Clients
 | 
				
			||||||
                    'defaultText': 'Add More',
 | 
					                    'defaultText': 'Add More',
 | 
				
			||||||
                    'removeWithBackspace' : true,
 | 
					                    'removeWithBackspace' : true,
 | 
				
			||||||
                    'minChars': 0,
 | 
					                    'minChars': 0,
 | 
				
			||||||
                    'minInputWidth': '100%',
 | 
					 | 
				
			||||||
                    'placeholderColor': '#666666'
 | 
					                    'placeholderColor': '#666666'
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -469,6 +573,13 @@ Wireguard Clients
 | 
				
			||||||
                        modal.find("#_client_name").val(client.name);
 | 
					                        modal.find("#_client_name").val(client.name);
 | 
				
			||||||
                        modal.find("#_client_email").val(client.email);
 | 
					                        modal.find("#_client_email").val(client.email);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        let preselectedEl
 | 
				
			||||||
 | 
					                        if (client.subnet_ranges && client.subnet_ranges.length > 0) {
 | 
				
			||||||
 | 
					                            preselectedEl = client.subnet_ranges[0]
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        updateSubnetRangesList("#_subnet_ranges", preselectedEl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        modal.find("#_client_allocated_ips").importTags('');
 | 
					                        modal.find("#_client_allocated_ips").importTags('');
 | 
				
			||||||
                        client.allocated_ips.forEach(function (obj) {
 | 
					                        client.allocated_ips.forEach(function (obj) {
 | 
				
			||||||
                            modal.find("#_client_allocated_ips").addTag(obj);
 | 
					                            modal.find("#_client_allocated_ips").addTag(obj);
 | 
				
			||||||
| 
						 | 
					@ -491,6 +602,11 @@ Wireguard Clients
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        modal.find("#_client_public_key").val(client.public_key);
 | 
					                        modal.find("#_client_public_key").val(client.public_key);
 | 
				
			||||||
                        modal.find("#_client_preshared_key").val(client.preshared_key);
 | 
					                        modal.find("#_client_preshared_key").val(client.preshared_key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // handle subnet range select
 | 
				
			||||||
 | 
					                        $('#_subnet_ranges').on('select2:select', function (e) {
 | 
				
			||||||
 | 
					                            updateIPAllocationSuggestionExisting();
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    error: function (jqXHR, exception) {
 | 
					                    error: function (jqXHR, exception) {
 | 
				
			||||||
                        const responseJson = jQuery.parseJSON(jqXHR.responseText);
 | 
					                        const responseJson = jQuery.parseJSON(jqXHR.responseText);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -203,7 +203,6 @@ Global Settings
 | 
				
			||||||
            'defaultText': 'Add More',
 | 
					            'defaultText': 'Add More',
 | 
				
			||||||
            'removeWithBackspace': true,
 | 
					            'removeWithBackspace': true,
 | 
				
			||||||
            'minChars': 0,
 | 
					            'minChars': 0,
 | 
				
			||||||
            'minInputWidth': '100%',
 | 
					 | 
				
			||||||
            'placeholderColor': '#666666'
 | 
					            'placeholderColor': '#666666'
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -167,7 +167,6 @@ Wireguard Server Settings
 | 
				
			||||||
            'defaultText': 'Add More',
 | 
					            'defaultText': 'Add More',
 | 
				
			||||||
            'removeWithBackspace': true,
 | 
					            'removeWithBackspace': true,
 | 
				
			||||||
            'minChars': 0,
 | 
					            'minChars': 0,
 | 
				
			||||||
            'minInputWidth': '100%',
 | 
					 | 
				
			||||||
            'placeholderColor': '#666666'
 | 
					            'placeholderColor': '#666666'
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					package util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var IPToSubnetRange = map[string]uint16{}
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,11 @@
 | 
				
			||||||
package util
 | 
					package util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "strings"
 | 
					import (
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/labstack/gommon/log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Runtime config
 | 
					// Runtime config
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
| 
						 | 
					@ -19,6 +24,8 @@ var (
 | 
				
			||||||
	SessionSecret     []byte
 | 
						SessionSecret     []byte
 | 
				
			||||||
	WgConfTemplate    string
 | 
						WgConfTemplate    string
 | 
				
			||||||
	BasePath          string
 | 
						BasePath          string
 | 
				
			||||||
 | 
						SubnetRanges      map[string]([]*net.IPNet)
 | 
				
			||||||
 | 
						SubnetRangesOrder []string
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
| 
						 | 
					@ -66,3 +73,45 @@ func ParseBasePath(basePath string) string {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return basePath
 | 
						return basePath
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ParseSubnetRanges(subnetRangesStr string) map[string]([]*net.IPNet) {
 | 
				
			||||||
 | 
						subnetRanges := map[string]([]*net.IPNet){}
 | 
				
			||||||
 | 
						if subnetRangesStr == "" {
 | 
				
			||||||
 | 
							return subnetRanges
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						cidrSet := map[string]bool{}
 | 
				
			||||||
 | 
						subnetRangesStr = strings.TrimSpace(subnetRangesStr)
 | 
				
			||||||
 | 
						subnetRangesStr = strings.Trim(subnetRangesStr, ";:,")
 | 
				
			||||||
 | 
						ranges := strings.Split(subnetRangesStr, ";")
 | 
				
			||||||
 | 
						for _, rng := range ranges {
 | 
				
			||||||
 | 
							rng = strings.TrimSpace(rng)
 | 
				
			||||||
 | 
							rngSpl := strings.Split(rng, ":")
 | 
				
			||||||
 | 
							if len(rngSpl) != 2 {
 | 
				
			||||||
 | 
								log.Warnf("Unable to parse subnet range: %v. Skipped.", rng)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							rngName := strings.TrimSpace(rngSpl[0])
 | 
				
			||||||
 | 
							subnetRanges[rngName] = make([]*net.IPNet, 0)
 | 
				
			||||||
 | 
							cidrs := strings.Split(rngSpl[1], ",")
 | 
				
			||||||
 | 
							for _, cidr := range cidrs {
 | 
				
			||||||
 | 
								cidr = strings.TrimSpace(cidr)
 | 
				
			||||||
 | 
								_, net, err := net.ParseCIDR(cidr)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Warnf("[%v] Unable to parse CIDR: %v. Skipped.", rngName, cidr)
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if cidrSet[net.String()] {
 | 
				
			||||||
 | 
									log.Warnf("[%v] CIDR already exists: %v. Skipped.", rngName, net.String())
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								cidrSet[net.String()] = true
 | 
				
			||||||
 | 
								subnetRanges[rngName] = append(subnetRanges[rngName], net)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(subnetRanges[rngName]) == 0 {
 | 
				
			||||||
 | 
								delete(subnetRanges, rngName)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								SubnetRangesOrder = append(SubnetRangesOrder, rngName)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return subnetRanges
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										154
									
								
								util/util.go
								
								
								
								
							
							
						
						
									
										154
									
								
								util/util.go
								
								
								
								
							| 
						 | 
					@ -95,6 +95,15 @@ func ClientDefaultsFromEnv() model.ClientDefaults {
 | 
				
			||||||
	return clientDefaults
 | 
						return clientDefaults
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ContainsCIDR to check if ipnet1 contains ipnet2
 | 
				
			||||||
 | 
					// https://stackoverflow.com/a/40406619/6111641
 | 
				
			||||||
 | 
					// https://go.dev/play/p/Q4J-JEN3sF
 | 
				
			||||||
 | 
					func ContainsCIDR(ipnet1, ipnet2 *net.IPNet) bool {
 | 
				
			||||||
 | 
						ones1, _ := ipnet1.Mask.Size()
 | 
				
			||||||
 | 
						ones2, _ := ipnet2.Mask.Size()
 | 
				
			||||||
 | 
						return ones1 <= ones2 && ipnet1.Contains(ipnet2.IP)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ValidateCIDR to validate a network CIDR
 | 
					// ValidateCIDR to validate a network CIDR
 | 
				
			||||||
func ValidateCIDR(cidr string) bool {
 | 
					func ValidateCIDR(cidr string) bool {
 | 
				
			||||||
	_, _, err := net.ParseCIDR(cidr)
 | 
						_, _, err := net.ParseCIDR(cidr)
 | 
				
			||||||
| 
						 | 
					@ -317,15 +326,32 @@ func GetBroadcastIP(n *net.IPNet) net.IP {
 | 
				
			||||||
	return broadcast
 | 
						return broadcast
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetBroadcastAndNetworkAddrsLookup get the ip address that can't be used with current server interfaces
 | 
				
			||||||
 | 
					func GetBroadcastAndNetworkAddrsLookup(interfaceAddresses []string) map[string]bool {
 | 
				
			||||||
 | 
						list := make(map[string]bool, 0)
 | 
				
			||||||
 | 
						for _, ifa := range interfaceAddresses {
 | 
				
			||||||
 | 
							_, net, err := net.ParseCIDR(ifa)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							broadcastAddr := GetBroadcastIP(net).String()
 | 
				
			||||||
 | 
							networkAddr := net.IP.String()
 | 
				
			||||||
 | 
							list[broadcastAddr] = true
 | 
				
			||||||
 | 
							list[networkAddr] = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return list
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetAvailableIP get the ip address that can be allocated from an CIDR
 | 
					// GetAvailableIP get the ip address that can be allocated from an CIDR
 | 
				
			||||||
func GetAvailableIP(cidr string, allocatedList []string) (string, error) {
 | 
					// We need interfaceAddresses to find real broadcast and network addresses
 | 
				
			||||||
 | 
					func GetAvailableIP(cidr string, allocatedList, interfaceAddresses []string) (string, error) {
 | 
				
			||||||
	ip, net, err := net.ParseCIDR(cidr)
 | 
						ip, net, err := net.ParseCIDR(cidr)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return "", err
 | 
							return "", err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	broadcastAddr := GetBroadcastIP(net).String()
 | 
						unavailableIPs := GetBroadcastAndNetworkAddrsLookup(interfaceAddresses)
 | 
				
			||||||
	networkAddr := net.IP.String()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for ip := ip.Mask(net.Mask); net.Contains(ip); inc(ip) {
 | 
						for ip := ip.Mask(net.Mask); net.Contains(ip); inc(ip) {
 | 
				
			||||||
		available := true
 | 
							available := true
 | 
				
			||||||
| 
						 | 
					@ -336,7 +362,7 @@ func GetAvailableIP(cidr string, allocatedList []string) (string, error) {
 | 
				
			||||||
				break
 | 
									break
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if available && suggestedAddr != networkAddr && suggestedAddr != broadcastAddr {
 | 
							if available && !unavailableIPs[suggestedAddr] {
 | 
				
			||||||
			return suggestedAddr, nil
 | 
								return suggestedAddr, nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -384,6 +410,126 @@ func ValidateIPAllocation(serverAddresses []string, ipAllocatedList []string, ip
 | 
				
			||||||
	return true, nil
 | 
						return true, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// findSubnetRangeForIP to find first SR for IP, and cache the match
 | 
				
			||||||
 | 
					func findSubnetRangeForIP(cidr string) (uint16, error) {
 | 
				
			||||||
 | 
						ip, _, err := net.ParseCIDR(cidr)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if srName, ok := IPToSubnetRange[ip.String()]; ok {
 | 
				
			||||||
 | 
							return srName, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for srIndex, sr := range SubnetRangesOrder {
 | 
				
			||||||
 | 
							for _, srCIDR := range SubnetRanges[sr] {
 | 
				
			||||||
 | 
								if srCIDR.Contains(ip) {
 | 
				
			||||||
 | 
									IPToSubnetRange[ip.String()] = uint16(srIndex)
 | 
				
			||||||
 | 
									return uint16(srIndex), nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return 0, fmt.Errorf("Subnet range not found for this IP")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FillClientSubnetRange to fill subnet ranges client belongs to, does nothing if SRs are not found
 | 
				
			||||||
 | 
					func FillClientSubnetRange(client model.ClientData) model.ClientData {
 | 
				
			||||||
 | 
						cl := *client.Client
 | 
				
			||||||
 | 
						for _, ip := range cl.AllocatedIPs {
 | 
				
			||||||
 | 
							sr, err := findSubnetRangeForIP(ip)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							cl.SubnetRanges = append(cl.SubnetRanges, SubnetRangesOrder[sr])
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return model.ClientData{
 | 
				
			||||||
 | 
							Client: &cl,
 | 
				
			||||||
 | 
							QRCode: client.QRCode,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ValidateAndFixSubnetRanges to check if subnet ranges are valid for the server configuration
 | 
				
			||||||
 | 
					// Removes all non-valid CIDRs
 | 
				
			||||||
 | 
					func ValidateAndFixSubnetRanges(db store.IStore) error {
 | 
				
			||||||
 | 
						if len(SubnetRangesOrder) == 0 {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						server, err := db.GetServer()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var serverSubnets []*net.IPNet
 | 
				
			||||||
 | 
						for _, addr := range server.Interface.Addresses {
 | 
				
			||||||
 | 
							addr = strings.TrimSpace(addr)
 | 
				
			||||||
 | 
							_, net, err := net.ParseCIDR(addr)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							serverSubnets = append(serverSubnets, net)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, rng := range SubnetRangesOrder {
 | 
				
			||||||
 | 
							cidrs := SubnetRanges[rng]
 | 
				
			||||||
 | 
							if len(cidrs) > 0 {
 | 
				
			||||||
 | 
								newCIDRs := make([]*net.IPNet, 0)
 | 
				
			||||||
 | 
								for _, cidr := range cidrs {
 | 
				
			||||||
 | 
									valid := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									for _, serverSubnet := range serverSubnets {
 | 
				
			||||||
 | 
										if ContainsCIDR(serverSubnet, cidr) {
 | 
				
			||||||
 | 
											valid = true
 | 
				
			||||||
 | 
											break
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if valid {
 | 
				
			||||||
 | 
										newCIDRs = append(newCIDRs, cidr)
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										log.Warnf("[%v] CIDR is outside of all server subnets: %v. Removed.", rng, cidr)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if len(newCIDRs) > 0 {
 | 
				
			||||||
 | 
									SubnetRanges[rng] = newCIDRs
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									delete(SubnetRanges, rng)
 | 
				
			||||||
 | 
									log.Warnf("[%v] No valid CIDRs in this subnet range. Removed.", rng)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetSubnetRangesString to get a formatted string, representing active subnet ranges
 | 
				
			||||||
 | 
					func GetSubnetRangesString() string {
 | 
				
			||||||
 | 
						if len(SubnetRangesOrder) == 0 {
 | 
				
			||||||
 | 
							return ""
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						strB := strings.Builder{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, rng := range SubnetRangesOrder {
 | 
				
			||||||
 | 
							cidrs := SubnetRanges[rng]
 | 
				
			||||||
 | 
							if len(cidrs) > 0 {
 | 
				
			||||||
 | 
								strB.WriteString(rng)
 | 
				
			||||||
 | 
								strB.WriteString(":[")
 | 
				
			||||||
 | 
								first := true
 | 
				
			||||||
 | 
								for _, cidr := range cidrs {
 | 
				
			||||||
 | 
									if !first {
 | 
				
			||||||
 | 
										strB.WriteString(", ")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									strB.WriteString(cidr.String())
 | 
				
			||||||
 | 
									first = false
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								strB.WriteString("]  ")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return strings.TrimSpace(strB.String())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// WriteWireGuardServerConfig to write Wireguard server config. e.g. wg0.conf
 | 
					// WriteWireGuardServerConfig to write Wireguard server config. e.g. wg0.conf
 | 
				
			||||||
func WriteWireGuardServerConfig(tmplDir fs.FS, serverConfig model.Server, clientDataList []model.ClientData, usersList []model.User, globalSettings model.GlobalSetting) error {
 | 
					func WriteWireGuardServerConfig(tmplDir fs.FS, serverConfig model.Server, clientDataList []model.ClientData, usersList []model.User, globalSettings model.GlobalSetting) error {
 | 
				
			||||||
	var tmplWireguardConf string
 | 
						var tmplWireguardConf string
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue