Edit wireguard client (#19)
* Add the ability to modify an existing client * Update client page using Ajax
This commit is contained in:
		
							parent
							
								
									9169e75e88
								
							
						
					
					
						commit
						cd7f6e500a
					
				|  | @ -35,6 +35,9 @@ RUN mkdir -p assets/plugins && \ | |||
|     /build/node_modules/jquery-tags-input/ \ | ||||
|     assets/plugins/ | ||||
| 
 | ||||
| # Move custom assets | ||||
| RUN cp -r /build/custom/ assets/ | ||||
| 
 | ||||
| # Get go modules and build tool | ||||
| RUN go mod download && \ | ||||
|     go get github.com/GeertJohan/go.rice/rice | ||||
|  |  | |||
|  | @ -0,0 +1,60 @@ | |||
| function renderClientList(data) { | ||||
|     $.each(data, function(index, obj) { | ||||
|         // render client status css tag style
 | ||||
|         let clientStatusHtml = '>' | ||||
|         if (obj.Client.enabled) { | ||||
|             clientStatusHtml = `style="visibility: hidden;">` | ||||
|         } | ||||
| 
 | ||||
|         // render client allocated ip addresses
 | ||||
|         let allocatedIpsHtml = ""; | ||||
|         $.each(obj.Client.allocated_ips, function(index, obj) { | ||||
|             allocatedIpsHtml += `<small class="badge badge-secondary">${obj}</small> `; | ||||
|         }) | ||||
| 
 | ||||
|         // render client allowed ip addresses
 | ||||
|         let allowedIpsHtml = ""; | ||||
|         $.each(obj.Client.allowed_ips, function(index, obj) { | ||||
|             allowedIpsHtml += `<small class="badge badge-secondary">${obj}</small> `; | ||||
|         }) | ||||
| 
 | ||||
|         // render client html content
 | ||||
|         let html = `<div class="col-sm-6" id="client_${obj.Client.id}">
 | ||||
|                         <div class="info-box"> | ||||
|                             <div class="overlay" id="paused_${obj.Client.id}"` + clientStatusHtml
 | ||||
|                                 + `<i class="paused-client fas fa-3x fa-play" onclick="resumeClient('${obj.Client.id}')"></i>
 | ||||
|                             </div> | ||||
|                             <img src="${obj.QRCode}" /> | ||||
|                             <div class="info-box-content"> | ||||
|                                 <div class="btn-group"> | ||||
|                                     <button onclick="location.href='/download?clientid=${obj.Client.id}'" type="button" | ||||
|                                         class="btn btn-outline-success btn-sm">Download</button> | ||||
|                                     <button type="button" class="btn btn-outline-primary btn-sm" data-toggle="modal" | ||||
|                                         data-target="#modal_edit_client" data-clientid="${obj.Client.id}" | ||||
|                                         data-clientname="${obj.Client.name}">Edit</button> | ||||
|                                     <button type="button" class="btn btn-outline-warning btn-sm" data-toggle="modal" | ||||
|                                         data-target="#modal_pause_client" data-clientid="${obj.Client.id}" | ||||
|                                         data-clientname="${obj.Client.name}">Disable</button> | ||||
|                                     <button type="button" class="btn btn-outline-danger btn-sm" data-toggle="modal" | ||||
|                                         data-target="#modal_remove_client" data-clientid="${obj.Client.id}" | ||||
|                                         data-clientname="${obj.Client.name}">Remove</button> | ||||
|                                 </div> | ||||
|                                 <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-envelope"></i> ${obj.Client.email}</span> | ||||
|                                 <span class="info-box-text"><i class="fas fa-clock"></i> | ||||
|                                     ${obj.Client.created_at}</span> | ||||
|                                 <span class="info-box-text"><i class="fas fa-history"></i> | ||||
|                                     ${obj.Client.updated_at}</span> | ||||
|                                 <span class="info-box-text"><strong>IP Allocation</strong></span>` | ||||
|                                 + allocatedIpsHtml | ||||
|                                 + `<span class="info-box-text"><strong>Allowed IPs</strong></span>` | ||||
|                                 + allowedIpsHtml | ||||
|                             +`</div>
 | ||||
|                         </div> | ||||
|                     </div>` | ||||
| 
 | ||||
|         // add the client html elements to the list
 | ||||
|         $('#client-list').append(html); | ||||
|     }); | ||||
| } | ||||
|  | @ -92,6 +92,37 @@ func WireGuardClients() echo.HandlerFunc { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| // GetClients handler return a list of Wireguard client data
 | ||||
| func GetClients() echo.HandlerFunc { | ||||
| 	return func(c echo.Context) error { | ||||
| 		// access validation
 | ||||
| 		validSession(c) | ||||
| 
 | ||||
| 		clientDataList, err := util.GetClients(true) | ||||
| 		if err != nil { | ||||
| 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, fmt.Sprintf("Cannot get client list: %v", err)}) | ||||
| 		} | ||||
| 
 | ||||
| 		return c.JSON(http.StatusOK, clientDataList) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // GetClient handler return a of Wireguard client data
 | ||||
| func GetClient() echo.HandlerFunc { | ||||
| 	return func(c echo.Context) error { | ||||
| 		// access validation
 | ||||
| 		validSession(c) | ||||
| 
 | ||||
| 		clientID := c.Param("id") | ||||
| 		clientData, err := util.GetClientByID(clientID, true) | ||||
| 		if err != nil { | ||||
| 			return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Client not found"}) | ||||
| 		} | ||||
| 
 | ||||
| 		return c.JSON(http.StatusOK, clientData) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // NewClient handler
 | ||||
| func NewClient() echo.HandlerFunc { | ||||
| 	return func(c echo.Context) error { | ||||
|  | @ -114,7 +145,7 @@ func NewClient() echo.HandlerFunc { | |||
| 		} | ||||
| 
 | ||||
| 		// validate the input Allocation IPs
 | ||||
| 		allocatedIPs, err := util.GetAllocatedIPs() | ||||
| 		allocatedIPs, err := util.GetAllocatedIPs("") | ||||
| 		check, err := util.ValidateIPAllocation(serverInterface.Addresses, allocatedIPs, client.AllocatedIPs) | ||||
| 		if !check { | ||||
| 			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, fmt.Sprintf("%s", err)}) | ||||
|  | @ -157,6 +188,63 @@ func NewClient() echo.HandlerFunc { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| // UpdateClient handler to update client information
 | ||||
| func UpdateClient() echo.HandlerFunc { | ||||
| 	return func(c echo.Context) error { | ||||
| 		// access validation
 | ||||
| 		validSession(c) | ||||
| 
 | ||||
| 		_client := new(model.Client) | ||||
| 		c.Bind(_client) | ||||
| 
 | ||||
| 		db, err := util.DBConn() | ||||
| 		if err != nil { | ||||
| 			log.Error("Cannot initialize database: ", err) | ||||
| 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot access database"}) | ||||
| 		} | ||||
| 
 | ||||
| 		// validate client existence
 | ||||
| 		client := model.Client{} | ||||
| 		if err := db.Read("clients", _client.ID, &client); err != nil { | ||||
| 			return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Client not found"}) | ||||
| 		} | ||||
| 
 | ||||
| 		// read server information
 | ||||
| 		serverInterface := model.ServerInterface{} | ||||
| 		if err := db.Read("server", "interfaces", &serverInterface); err != nil { | ||||
| 			log.Error("Cannot fetch server interface config from database: ", err) | ||||
| 			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, fmt.Sprintf("Cannot fetch server config: %s", err)}) | ||||
| 		} | ||||
| 
 | ||||
| 		// validate the input Allocation IPs
 | ||||
| 		allocatedIPs, err := util.GetAllocatedIPs(client.ID) | ||||
| 		check, err := util.ValidateIPAllocation(serverInterface.Addresses, allocatedIPs, _client.AllocatedIPs) | ||||
| 		if !check { | ||||
| 			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, fmt.Sprintf("%s", err)}) | ||||
| 		} | ||||
| 
 | ||||
| 		// validate the input AllowedIPs
 | ||||
| 		if util.ValidateAllowedIPs(_client.AllowedIPs) == false { | ||||
| 			log.Warnf("Invalid Allowed IPs input from user: %v", _client.AllowedIPs) | ||||
| 			return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Allowed IPs must be in CIDR format"}) | ||||
| 		} | ||||
| 
 | ||||
| 		// map new data
 | ||||
| 		client.Name = _client.Name | ||||
| 		client.Email = _client.Email | ||||
| 		client.Enabled = _client.Enabled | ||||
| 		client.AllocatedIPs = _client.AllocatedIPs | ||||
| 		client.AllowedIPs = _client.AllowedIPs | ||||
| 		client.UpdatedAt = time.Now().UTC() | ||||
| 
 | ||||
| 		// write to the database
 | ||||
| 		db.Write("clients", client.ID, &client) | ||||
| 		log.Infof("Updated client information successfully => %v", client) | ||||
| 
 | ||||
| 		return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated client successfully"}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // SetClientStatus handler to enable / disable a client
 | ||||
| func SetClientStatus() echo.HandlerFunc { | ||||
| 	return func(c echo.Context) error { | ||||
|  | @ -181,7 +269,7 @@ func SetClientStatus() echo.HandlerFunc { | |||
| 
 | ||||
| 		client := model.Client{} | ||||
| 		if err := db.Read("clients", clientID, &client); err != nil { | ||||
| 			log.Error("Cannot fetch server interface config from database: ", err) | ||||
| 			log.Error("Cannot get client from database: ", err) | ||||
| 		} | ||||
| 
 | ||||
| 		client.Enabled = status | ||||
|  | @ -200,15 +288,16 @@ func DownloadClient() echo.HandlerFunc { | |||
| 			return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Missing clientid parameter"}) | ||||
| 		} | ||||
| 
 | ||||
| 		client, err := util.GetClientByID(clientID) | ||||
| 		clientData, err := util.GetClientByID(clientID, false) | ||||
| 		if err != nil { | ||||
| 			log.Errorf("Cannot generate client id %s config file for downloading: %v", clientID, err) | ||||
| 			return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Client not found"}) | ||||
| 		} | ||||
| 
 | ||||
| 		// build config
 | ||||
| 		server, _ := util.GetServer() | ||||
| 		globalSettings, _ := util.GetGlobalSettings() | ||||
| 		config := util.BuildClientConfig(client, server, globalSettings) | ||||
| 		config := util.BuildClientConfig(*clientData.Client, server, globalSettings) | ||||
| 
 | ||||
| 		// create io reader from string
 | ||||
| 		reader := strings.NewReader(config) | ||||
|  | @ -419,7 +508,7 @@ func SuggestIPAllocation() echo.HandlerFunc { | |||
| 		// we take the first available ip address from
 | ||||
| 		// each server's network addresses.
 | ||||
| 		suggestedIPs := make([]string, 0) | ||||
| 		allocatedIPs, err := util.GetAllocatedIPs() | ||||
| 		allocatedIPs, err := util.GetAllocatedIPs("") | ||||
| 		if err != nil { | ||||
| 			log.Error("Cannot suggest ip allocation. Failed to get list of allocated ip addresses: ", err) | ||||
| 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot suggest ip allocation: failed to get list of allocated ip addresses"}) | ||||
|  |  | |||
							
								
								
									
										3
									
								
								main.go
								
								
								
								
							
							
						
						
									
										3
									
								
								main.go
								
								
								
								
							|  | @ -49,6 +49,7 @@ func main() { | |||
| 	app.POST("/login", handler.Login()) | ||||
| 	app.GET("/logout", handler.Logout()) | ||||
| 	app.POST("/new-client", handler.NewClient()) | ||||
| 	app.POST("/update-client", handler.UpdateClient()) | ||||
| 	app.POST("/client/set-status", handler.SetClientStatus()) | ||||
| 	app.POST("/remove-client", handler.RemoveClient()) | ||||
| 	app.GET("/download", handler.DownloadClient()) | ||||
|  | @ -57,6 +58,8 @@ func main() { | |||
| 	app.POST("wg-server/keypair", handler.WireGuardServerKeyPair()) | ||||
| 	app.GET("/global-settings", handler.GlobalSettings()) | ||||
| 	app.POST("/global-settings", handler.GlobalSettingSubmit()) | ||||
| 	app.GET("/api/clients", handler.GetClients()) | ||||
| 	app.GET("/api/client/:id", handler.GetClient()) | ||||
| 	app.GET("/api/machine-ips", handler.MachineIPAddresses()) | ||||
| 	app.GET("/api/suggest-client-ips", handler.SuggestIPAllocation()) | ||||
| 	app.GET("/api/apply-wg-config", handler.ApplyServerConfig(tmplBox)) | ||||
|  |  | |||
|  | @ -11,6 +11,9 @@ mkdir -p "${DIR}/assets/dist/js" "${DIR}/assets/dist/css" && \ | |||
|   cp -r "${DIR}/node_modules/admin-lte/dist/js/adminlte.min.js" "${DIR}/assets/dist/js/adminlte.min.js" && \ | ||||
|   cp -r "${DIR}/node_modules/admin-lte/dist/css/adminlte.min.css" "${DIR}/assets/dist/css/adminlte.min.css" | ||||
| 
 | ||||
| # Copy helper js | ||||
| cp -r "${DIR}/custom" "${DIR}/assets" | ||||
| 
 | ||||
| # Copy plugins | ||||
| mkdir -p "${DIR}/assets/plugins" && \ | ||||
|   cp -r "${DIR}/node_modules/admin-lte/plugins/jquery" \ | ||||
|  |  | |||
|  | @ -56,16 +56,17 @@ | |||
|                 </div> | ||||
|             </form> | ||||
| 
 | ||||
|         <!-- Right navbar links --> | ||||
|         <div class="navbar-nav ml-auto"> | ||||
|             <button style="margin-left: 0.5em;" type="button" class="btn btn-outline-primary btn-sm" data-toggle="modal" | ||||
|                 data-target="#modal_new_client"><i class="nav-icon fas fa-plus"></i> New | ||||
|                 Client</button> | ||||
|             <button style="margin-left: 0.5em;" type="button" class="btn btn-outline-danger btn-sm" data-toggle="modal" | ||||
|                 data-target="#modal_apply_config"><i class="nav-icon fas fa-check"></i> Apply | ||||
|                 Config</button> | ||||
|             <button onclick="location.href='/logout';" style="margin-left: 0.5em;" type="button" | ||||
|                 class="btn btn-outline-danger btn-sm"><i class="nav-icon fas fa-sign-out-alt"></i> Logout</button> | ||||
|             <!-- Right navbar links --> | ||||
|             <div class="navbar-nav ml-auto"> | ||||
|                 <button style="margin-left: 0.5em;" type="button" class="btn btn-outline-primary btn-sm" data-toggle="modal" | ||||
|                     data-target="#modal_new_client"><i class="nav-icon fas fa-plus"></i> New | ||||
|                     Client</button> | ||||
|                 <button style="margin-left: 0.5em;" type="button" class="btn btn-outline-danger btn-sm" data-toggle="modal" | ||||
|                     data-target="#modal_apply_config"><i class="nav-icon fas fa-check"></i> Apply | ||||
|                     Config</button> | ||||
|                 <button onclick="location.href='/logout';" style="margin-left: 0.5em;" type="button" | ||||
|                     class="btn btn-outline-danger btn-sm"><i class="nav-icon fas fa-sign-out-alt"></i> Logout</button> | ||||
|             </div> | ||||
|         </nav> | ||||
|         <!-- /.navbar --> | ||||
| 
 | ||||
|  | @ -245,7 +246,28 @@ | |||
|     <script src="static/plugins/jquery-tags-input/dist/jquery.tagsinput.min.js"></script> | ||||
|     <!-- AdminLTE App --> | ||||
|     <script src="static/dist/js/adminlte.min.js"></script> | ||||
|     <!-- Custom js --> | ||||
|     <script src="static/custom/js/helper.js"></script> | ||||
|     <script> | ||||
|         // populateClient function for render new client info | ||||
|         // on the client page. | ||||
|         function populateClient(client_id) { | ||||
|             $.ajax({ | ||||
|                 cache: false, | ||||
|                 method: 'GET', | ||||
|                 url: '/api/client/' + client_id, | ||||
|                 dataType: 'json', | ||||
|                 contentType: "application/json", | ||||
|                 success: function (resp) { | ||||
|                     renderClientList([resp]); | ||||
|                 }, | ||||
|                 error: function (jqXHR, exception) { | ||||
|                     const responseJson = jQuery.parseJSON(jqXHR.responseText); | ||||
|                     toastr.error(responseJson['message']); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         // submitNewClient function for new client form submission | ||||
|         function submitNewClient() { | ||||
|             const name = $("#client_name").val(); | ||||
|  | @ -260,7 +282,6 @@ | |||
| 
 | ||||
|             const data = {"name": name, "email": email, "allocated_ips": allocated_ips, "allowed_ips": allowed_ips, | ||||
|             "enabled": enabled}; | ||||
|             console.log(data); | ||||
| 
 | ||||
|             $.ajax({ | ||||
|                 cache: false, | ||||
|  | @ -269,12 +290,12 @@ | |||
|                 dataType: 'json', | ||||
|                 contentType: "application/json", | ||||
|                 data: JSON.stringify(data), | ||||
|                 success: function(data) { | ||||
|                 success: function(resp) { | ||||
|                     $("#modal_new_client").modal('hide'); | ||||
|                     toastr.success('Created new client successfully'); | ||||
|                     // Refresh the home page (clients page) after adding successfully | ||||
|                     // Update the home page (clients page) after adding successfully | ||||
|                     if (window.location.pathname === "/") { | ||||
|                         location.reload(); | ||||
|                         populateClient(resp.id); | ||||
|                     } | ||||
|                 }, | ||||
|                 error: function(jqXHR, exception) { | ||||
|  | @ -356,7 +377,7 @@ | |||
|                     }, | ||||
|                     client_email: { | ||||
|                         required: "Please enter an email address", | ||||
|                         email: "Please enter a vaild email address" | ||||
|                         email: "Please enter a valid email address" | ||||
|                     }, | ||||
|                 }, | ||||
|                 errorElement: 'span', | ||||
|  | @ -376,6 +397,8 @@ | |||
|         // New Client modal event | ||||
|         $(document).ready(function () { | ||||
|             $("#modal_new_client").on('shown.bs.modal', function (e) { | ||||
|                 $("#client_name").val(""); | ||||
|                 $("#client_email").val(""); | ||||
|                 $("#client_allocated_ips").importTags(''); | ||||
|                 updateIPAllocationSuggestion(); | ||||
|             }); | ||||
|  |  | |||
|  | @ -24,55 +24,61 @@ Wireguard Clients | |||
| <section class="content"> | ||||
|     <div class="container-fluid"> | ||||
|         <!-- <h5 class="mt-4 mb-2">Wireguard Clients</h5> --> | ||||
|         <div class="row"> | ||||
|             {{range .clientDataList}} | ||||
|             <div class="col-sm-6" id="client_{{.Client.ID}}"> | ||||
|                 <div class="info-box"> | ||||
|                     <div class="overlay" id="paused_{{.Client.ID}}" | ||||
|                         {{if eq .Client.Enabled true}}style="visibility: hidden;" {{end}}> | ||||
|                         <i class="paused-client fas fa-3x fa-play" onclick="resumeClient('{{.Client.ID}}')"></i> | ||||
|                     </div> | ||||
|                     <img | ||||
|                         src="{{ .QRCode }}" /> | ||||
|                     <div class="info-box-content"> | ||||
|                         <div class="btn-group"> | ||||
|                             <button onclick="location.href='/download?clientid={{ .Client.ID }}'" type="button" | ||||
|                                 class="btn btn-outline-success btn-sm">Download</button> | ||||
|                             <!-- <button type="button" class="btn btn-outline-primary btn-sm">Edit</button> --> | ||||
|                             <button type="button" class="btn btn-outline-warning btn-sm" data-toggle="modal" | ||||
|                                 data-target="#modal_pause_client" data-clientid="{{ .Client.ID }}" | ||||
|                                 data-clientname="{{ .Client.Name }}">Disable</button> | ||||
|                             <button type="button" class="btn btn-outline-danger btn-sm" data-toggle="modal" | ||||
|                                 data-target="#modal_remove_client" data-clientid="{{ .Client.ID }}" | ||||
|                                 data-clientname="{{ .Client.Name }}">Remove</button> | ||||
|                         </div> | ||||
|                         <hr> | ||||
|                         <span class="info-box-text"><i class="fas fa-user"></i> {{ .Client.Name }}</span> | ||||
|                         <span class="info-box-text"><i class="fas fa-envelope"></i> {{ .Client.Email }}</span> | ||||
|                         <span class="info-box-text"><i class="fas fa-clock"></i> | ||||
|                             {{ .Client.CreatedAt.Format "2 Jan 2006 15:04" }}</span> | ||||
|                         <span class="info-box-text"><i class="fas fa-history"></i> | ||||
|                             {{ .Client.UpdatedAt.Format "2 Jan 2006 15:04" }}</span> | ||||
|                         <span class="info-box-text"><strong>IP Allocation</strong></span> | ||||
|                         {{range .Client.AllocatedIPs}} | ||||
|                         <small class="badge badge-secondary">{{.}}</small> | ||||
|                         {{end}} | ||||
|                         <span class="info-box-text"><strong>Allowed IPs</strong></span> | ||||
|                         {{range .Client.AllowedIPs}} | ||||
|                         <small class="badge badge-secondary">{{.}}</small> | ||||
|                         {{end}} | ||||
|                     </div> | ||||
|                     <!-- /.info-box-content --> | ||||
|                 </div> | ||||
|                 <!-- /.info-box --> | ||||
|             </div> | ||||
|             <!-- /.col --> | ||||
|             {{end}} | ||||
|         <div class="row" id="client-list"> | ||||
|         </div> | ||||
|         <!-- /.row --> | ||||
|     </div> | ||||
| </section> | ||||
| 
 | ||||
| <div class="modal fade" id="modal_edit_client"> | ||||
|     <div class="modal-dialog"> | ||||
|         <div class="modal-content"> | ||||
|             <div class="modal-header"> | ||||
|                 <h4 class="modal-title">Edit Client</h4> | ||||
|                 <button type="button" class="close" data-dismiss="modal" aria-label="Close"> | ||||
|                     <span aria-hidden="true">×</span> | ||||
|                 </button> | ||||
|             </div> | ||||
|             <form name="frm_edit_client" id="frm_edit_client"> | ||||
|                 <div class="modal-body"> | ||||
|                     <input type="hidden" id="_client_id" name="_client_id"> | ||||
|                     <div class="form-group"> | ||||
|                         <label for="_client_name" class="control-label">Name</label> | ||||
|                         <input type="text" class="form-control" id="_client_name" name="_client_name"> | ||||
|                     </div> | ||||
|                     <div class="form-group"> | ||||
|                         <label for="_client_email" class="control-label">Email</label> | ||||
|                         <input type="text" class="form-control" id="_client_email" name="client_email"> | ||||
|                     </div> | ||||
|                     <div class="form-group"> | ||||
|                         <label for="_client_allocated_ips" class="control-label">IP Allocation</label> | ||||
|                         <input type="text" data-role="tagsinput" class="form-control" id="_client_allocated_ips"> | ||||
|                     </div> | ||||
|                     <div class="form-group"> | ||||
|                         <label for="_client_allowed_ips" class="control-label">Allowed IPs</label> | ||||
|                         <input type="text" data-role="tagsinput" class="form-control" id="_client_allowed_ips"> | ||||
|                     </div> | ||||
|                     <div class="form-group"> | ||||
|                         <div class="icheck-primary d-inline"> | ||||
|                             <input type="checkbox" id="_enabled"> | ||||
|                             <label for="_enabled"> | ||||
|                                 Enable this client | ||||
|                             </label> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <div class="modal-footer justify-content-between"> | ||||
|                     <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> | ||||
|                     <button type="submit" class="btn btn-success">Save</button> | ||||
|                 </div> | ||||
|             </form> | ||||
|         </div> | ||||
|         <!-- /.modal-content --> | ||||
|     </div> | ||||
|     <!-- /.modal-dialog --> | ||||
| </div> | ||||
| <!-- /.modal --> | ||||
| 
 | ||||
| <div class="modal fade" id="modal_pause_client"> | ||||
|     <div class="modal-dialog"> | ||||
|         <div class="modal-content bg-warning"> | ||||
|  | @ -120,6 +126,23 @@ Wireguard Clients | |||
| 
 | ||||
| {{define "bottom_js"}} | ||||
|     <script> | ||||
|         function populateClientList() { | ||||
|             $.ajax({ | ||||
|                 cache: false, | ||||
|                 method: 'GET', | ||||
|                 url: '/api/clients', | ||||
|                 dataType: 'json', | ||||
|                 contentType: "application/json", | ||||
|                 success: function (data) { | ||||
|                     renderClientList(data); | ||||
|                 }, | ||||
|                 error: function (jqXHR, exception) { | ||||
|                     const responseJson = jQuery.parseJSON(jqXHR.responseText); | ||||
|                     toastr.error(responseJson['message']); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         function setClientStatus(clientID, status) { | ||||
|             const data = {"id": clientID, "status": status}; | ||||
|             $.ajax({ | ||||
|  | @ -130,7 +153,7 @@ Wireguard Clients | |||
|                 contentType: "application/json", | ||||
|                 data: JSON.stringify(data), | ||||
|                 success: function (data) { | ||||
|                     console.log("Set client " + clientID + " status to " + status) | ||||
|                     console.log("Set client " + clientID + " status to " + status); | ||||
|                 }, | ||||
|                 error: function (jqXHR, exception) { | ||||
|                     const responseJson = jQuery.parseJSON(jqXHR.responseText); | ||||
|  | @ -152,6 +175,11 @@ Wireguard Clients | |||
|         } | ||||
|     </script> | ||||
|     <script> | ||||
|         // load client list | ||||
|         $(document).ready(function () { | ||||
|             populateClientList(); | ||||
|         }) | ||||
| 
 | ||||
|         // modal_pause_client modal event | ||||
|         $("#modal_pause_client").on('show.bs.modal', function (event) { | ||||
|             const button = $(event.relatedTarget); | ||||
|  | @ -206,5 +234,147 @@ Wireguard Clients | |||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
| 
 | ||||
|         // Edit client modal event | ||||
|         $(document).ready(function () { | ||||
|             $("#modal_edit_client").on('shown.bs.modal', function (event) { | ||||
|                 let modal = $(this); | ||||
|                 const button = $(event.relatedTarget); | ||||
|                 const client_id = button.data('clientid'); | ||||
| 
 | ||||
|                 // IP Allocation tag input | ||||
|                 modal.find("#_client_allocated_ips").tagsInput({ | ||||
|                     'width': '100%', | ||||
|                     'height': '75%', | ||||
|                     'interactive': true, | ||||
|                     'defaultText': 'Add More', | ||||
|                     'removeWithBackspace': true, | ||||
|                     'minChars': 0, | ||||
|                     'maxChars': 18, | ||||
|                     'placeholderColor': '#666666' | ||||
|                 }); | ||||
| 
 | ||||
|                 // AllowedIPs tag input | ||||
|                 modal.find("#_client_allowed_ips").tagsInput({ | ||||
|                     'width': '100%', | ||||
|                     'height': '75%', | ||||
|                     'interactive': true, | ||||
|                     'defaultText': 'Add More', | ||||
|                     'removeWithBackspace': true, | ||||
|                     'minChars': 0, | ||||
|                     'maxChars': 18, | ||||
|                     'placeholderColor': '#666666' | ||||
|                 }); | ||||
| 
 | ||||
|                 // update client modal data | ||||
|                 $.ajax({ | ||||
|                     cache: false, | ||||
|                     method: 'GET', | ||||
|                     url: '/api/client/' + client_id, | ||||
|                     dataType: 'json', | ||||
|                     contentType: "application/json", | ||||
|                     success: function (resp) { | ||||
|                         const client = resp.Client; | ||||
| 
 | ||||
|                         modal.find(".modal-title").text("Edit Client " + client.name); | ||||
|                         modal.find("#_client_id").val(client.id); | ||||
|                         modal.find("#_client_name").val(client.name); | ||||
|                         modal.find("#_client_email").val(client.email); | ||||
| 
 | ||||
|                         modal.find("#_client_allocated_ips").importTags(''); | ||||
|                         client.allocated_ips.forEach(function (obj) { | ||||
|                             modal.find("#_client_allocated_ips").addTag(obj); | ||||
|                         }); | ||||
| 
 | ||||
|                         modal.find("#_client_allowed_ips").importTags(''); | ||||
|                         client.allowed_ips.forEach(function (obj) { | ||||
|                             modal.find("#_client_allowed_ips").addTag(obj); | ||||
|                         }); | ||||
| 
 | ||||
|                         modal.find("#_enabled").prop("checked", client.enabled); | ||||
|                     }, | ||||
|                     error: function (jqXHR, exception) { | ||||
|                         const responseJson = jQuery.parseJSON(jqXHR.responseText); | ||||
|                         toastr.error(responseJson['message']); | ||||
|                     } | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
| 
 | ||||
|         // submitEditClient function for updating an existing client | ||||
|         function submitEditClient() { | ||||
|             const client_id = $("#_client_id").val(); | ||||
|             const name = $("#_client_name").val(); | ||||
|             const email = $("#_client_email").val(); | ||||
|             const allocated_ips = $("#_client_allocated_ips").val().split(","); | ||||
|             const allowed_ips = $("#_client_allowed_ips").val().split(","); | ||||
|             let enabled = false; | ||||
| 
 | ||||
|             if ($("#_enabled").is(':checked')){ | ||||
|                 enabled = true; | ||||
|             } | ||||
| 
 | ||||
|             const data = {"id": client_id, "name": name, "email": email, "allocated_ips": allocated_ips, | ||||
|                 "allowed_ips": allowed_ips, "enabled": enabled}; | ||||
| 
 | ||||
|             $.ajax({ | ||||
|                 cache: false, | ||||
|                 method: 'POST', | ||||
|                 url: '/update-client', | ||||
|                 dataType: 'json', | ||||
|                 contentType: "application/json", | ||||
|                 data: JSON.stringify(data), | ||||
|                 success: function(resp) { | ||||
|                     $("#modal_edit_client").modal('hide'); | ||||
|                     toastr.success('Updated client successfully'); | ||||
|                      // Refresh the home page (clients page) after updating successfully | ||||
|                     location.reload(); | ||||
|                 }, | ||||
|                 error: function(jqXHR, exception) { | ||||
|                     const responseJson = jQuery.parseJSON(jqXHR.responseText); | ||||
|                     toastr.error(responseJson['message']); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         // Edit client form validation | ||||
|         $(document).ready(function () { | ||||
|             $.validator.setDefaults({ | ||||
|                 submitHandler: function () { | ||||
|                     submitEditClient(); | ||||
|                 } | ||||
|             }); | ||||
|             $("#frm_edit_client").validate({ | ||||
|                 rules: { | ||||
|                     client_name: { | ||||
|                         required: true, | ||||
|                     }, | ||||
|                     client_email: { | ||||
|                         required: true, | ||||
|                         email: true, | ||||
|                     }, | ||||
|                 }, | ||||
|                 messages: { | ||||
|                     client_name: { | ||||
|                         required: "Please enter a name" | ||||
|                     }, | ||||
|                     client_email: { | ||||
|                         required: "Please enter an email address", | ||||
|                         email: "Please enter a valid email address" | ||||
|                     }, | ||||
|                 }, | ||||
|                 errorElement: 'span', | ||||
|                 errorPlacement: function (error, element) { | ||||
|                     error.addClass('invalid-feedback'); | ||||
|                     element.closest('.form-group').append(error); | ||||
|                 }, | ||||
|                 highlight: function (element, errorClass, validClass) { | ||||
|                     $(element).addClass('is-invalid'); | ||||
|                 }, | ||||
|                 unhighlight: function (element, errorClass, validClass) { | ||||
|                     $(element).removeClass('is-invalid'); | ||||
|                 } | ||||
|             }); | ||||
|         }); | ||||
|     </script> | ||||
| {{end}} | ||||
							
								
								
									
										26
									
								
								util/db.go
								
								
								
								
							
							
						
						
									
										26
									
								
								util/db.go
								
								
								
								
							|  | @ -209,7 +209,7 @@ func GetClients(hasQRCode bool) ([]model.ClientData, error) { | |||
| 			server, _ := GetServer() | ||||
| 			globalSettings, _ := GetGlobalSettings() | ||||
| 
 | ||||
| 			png, _ := qrcode.Encode(BuildClientConfig(client, server, globalSettings), qrcode.Medium, 256) | ||||
| 			png, err := qrcode.Encode(BuildClientConfig(client, server, globalSettings), qrcode.Medium, 256) | ||||
| 			if err == nil { | ||||
| 				clientData.QRCode = "data:image/png;base64," + base64.StdEncoding.EncodeToString([]byte(png)) | ||||
| 			} else { | ||||
|  | @ -226,18 +226,34 @@ func GetClients(hasQRCode bool) ([]model.ClientData, error) { | |||
| } | ||||
| 
 | ||||
| // GetClientByID func to query a client from the database
 | ||||
| func GetClientByID(clientID string) (model.Client, error) { | ||||
| func GetClientByID(clientID string, hasQRCode bool) (model.ClientData, error) { | ||||
| 	client := model.Client{} | ||||
| 	clientData := model.ClientData{} | ||||
| 
 | ||||
| 	db, err := DBConn() | ||||
| 	if err != nil { | ||||
| 		return client, err | ||||
| 		return clientData, err | ||||
| 	} | ||||
| 
 | ||||
| 	// read client information
 | ||||
| 	if err := db.Read("clients", clientID, &client); err != nil { | ||||
| 		return client, err | ||||
| 		return clientData, err | ||||
| 	} | ||||
| 
 | ||||
| 	return client, nil | ||||
| 	// generate client qrcode image in base64
 | ||||
| 	if hasQRCode { | ||||
| 		server, _ := GetServer() | ||||
| 		globalSettings, _ := GetGlobalSettings() | ||||
| 
 | ||||
| 		png, err := qrcode.Encode(BuildClientConfig(client, server, globalSettings), qrcode.Medium, 256) | ||||
| 		if err == nil { | ||||
| 			clientData.QRCode = "data:image/png;base64," + base64.StdEncoding.EncodeToString([]byte(png)) | ||||
| 		} else { | ||||
| 			fmt.Print("Cannot generate QR code: ", err) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	clientData.Client = &client | ||||
| 
 | ||||
| 	return clientData, nil | ||||
| } | ||||
|  |  | |||
							
								
								
									
										14
									
								
								util/util.go
								
								
								
								
							
							
						
						
									
										14
									
								
								util/util.go
								
								
								
								
							|  | @ -173,7 +173,7 @@ func GetIPFromCIDR(cidr string) (string, error) { | |||
| } | ||||
| 
 | ||||
| // GetAllocatedIPs to get all ip addresses allocated to clients and server
 | ||||
| func GetAllocatedIPs() ([]string, error) { | ||||
| func GetAllocatedIPs(ignoreClientID string) ([]string, error) { | ||||
| 	allocatedIPs := make([]string, 0) | ||||
| 
 | ||||
| 	// initialize database directory
 | ||||
|  | @ -211,12 +211,14 @@ func GetAllocatedIPs() ([]string, error) { | |||
| 			return nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		for _, cidr := range client.AllocatedIPs { | ||||
| 			ip, err := GetIPFromCIDR(cidr) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 		if client.ID != ignoreClientID { | ||||
| 			for _, cidr := range client.AllocatedIPs { | ||||
| 				ip, err := GetIPFromCIDR(cidr) | ||||
| 				if err != nil { | ||||
| 					return nil, err | ||||
| 				} | ||||
| 				allocatedIPs = append(allocatedIPs, ip) | ||||
| 			} | ||||
| 			allocatedIPs = append(allocatedIPs, ip) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue