Telegram support, send config button
This commit is contained in:
		
							parent
							
								
									c691f0733d
								
							
						
					
					
						commit
						4c6080b3fa
					
				|  | @ -1,5 +1,15 @@ | |||
| function renderClientList(data) { | ||||
|     $.each(data, function(index, obj) { | ||||
|         // render telegram button
 | ||||
|         let telegramButton = '' | ||||
|         if (obj.Client.telegram_userid) { | ||||
|             telegramButton =    `<div class="btn-group">      
 | ||||
|                                     <button type="button" class="btn btn-outline-primary btn-sm" data-toggle="modal" | ||||
|                                         data-target="#modal_telegram_client" data-clientid="${obj.Client.id}" | ||||
|                                         data-clientname="${obj.Client.name}">Telegram</button> | ||||
|                                 </div>` | ||||
|         } | ||||
| 
 | ||||
|         // render client status css tag style
 | ||||
|         let clientStatusHtml = '>' | ||||
|         if (obj.Client.enabled) { | ||||
|  | @ -43,7 +53,7 @@ function renderClientList(data) { | |||
|                                         data-target="#modal_email_client" data-clientid="${obj.Client.id}" | ||||
|                                         data-clientname="${obj.Client.name}">Email</button> | ||||
|                                 </div> | ||||
| 
 | ||||
|                                 ${telegramButton} | ||||
|                                 <div class="btn-group"> | ||||
|                                     <button type="button" class="btn btn-outline-danger btn-sm">More</button> | ||||
|                                     <button type="button" class="btn btn-outline-danger btn-sm dropdown-toggle dropdown-icon"  | ||||
|  |  | |||
							
								
								
									
										1
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										1
									
								
								go.mod
								
								
								
								
							|  | @ -3,6 +3,7 @@ module github.com/ngoduykhanh/wireguard-ui | |||
| go 1.16 | ||||
| 
 | ||||
| require ( | ||||
| 	github.com/NicoNex/echotron/v3 v3.27.0 | ||||
| 	github.com/glendc/go-external-ip v0.0.0-20170425150139-139229dcdddd | ||||
| 	github.com/go-playground/universal-translator v0.17.0 // indirect | ||||
| 	github.com/gorilla/sessions v1.2.0 | ||||
|  |  | |||
							
								
								
									
										2
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										2
									
								
								go.sum
								
								
								
								
							|  | @ -1,4 +1,6 @@ | |||
| github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= | ||||
| github.com/NicoNex/echotron/v3 v3.27.0 h1:iq4BLPO+Dz1JHjh2HPk0D0NldAZSYcAjaOicgYEhUzw= | ||||
| github.com/NicoNex/echotron/v3 v3.27.0/go.mod h1:LpP5IyHw0y+DZUZMBgXEDAF9O8feXrQu7w7nlJzzoZI= | ||||
| github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= | ||||
| github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= | ||||
| github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw= | ||||
|  |  | |||
|  | @ -19,12 +19,14 @@ import ( | |||
| 	"github.com/labstack/echo/v4" | ||||
| 	"github.com/labstack/gommon/log" | ||||
| 	"github.com/rs/xid" | ||||
| 	"github.com/skip2/go-qrcode" | ||||
| 	"golang.zx2c4.com/wireguard/wgctrl" | ||||
| 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes" | ||||
| 
 | ||||
| 	"github.com/ngoduykhanh/wireguard-ui/emailer" | ||||
| 	"github.com/ngoduykhanh/wireguard-ui/model" | ||||
| 	"github.com/ngoduykhanh/wireguard-ui/store" | ||||
| 	"github.com/ngoduykhanh/wireguard-ui/telegram" | ||||
| 	"github.com/ngoduykhanh/wireguard-ui/util" | ||||
| ) | ||||
| 
 | ||||
|  | @ -569,6 +571,56 @@ func EmailClient(db store.IStore, mailer emailer.Emailer, emailSubject, emailCon | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| // SendTelegramClient handler to send the configuration via Telegram
 | ||||
| func SendTelegramClient(db store.IStore) echo.HandlerFunc { | ||||
| 	type clientIdUseridPayload struct { | ||||
| 		ID     string `json:"id"` | ||||
| 		Userid string `json:"userid"` | ||||
| 	} | ||||
| 	return func(c echo.Context) error { | ||||
| 		var payload clientIdUseridPayload | ||||
| 		c.Bind(&payload) | ||||
| 
 | ||||
| 		qrCodeSettings := model.QRCodeSettings{ | ||||
| 			Enabled:    true, | ||||
| 			IncludeDNS: true, | ||||
| 			IncludeMTU: true, | ||||
| 		} | ||||
| 		clientData, err := db.GetClientByID(payload.ID, qrCodeSettings) | ||||
| 		if err != nil { | ||||
| 			log.Errorf("Cannot generate client id %s config file for downloading: %v", payload.ID, err) | ||||
| 			return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Client not found"}) | ||||
| 		} | ||||
| 
 | ||||
| 		// build config
 | ||||
| 		server, _ := db.GetServer() | ||||
| 		globalSettings, _ := db.GetGlobalSettings() | ||||
| 		config := util.BuildClientConfig(*clientData.Client, server, globalSettings) | ||||
| 		configData := []byte(config) | ||||
| 		var qrData []byte | ||||
| 
 | ||||
| 		if clientData.Client.PrivateKey != "" { | ||||
| 			qrData, err = qrcode.Encode(config, qrcode.Medium, 512) | ||||
| 			if err != nil { | ||||
| 				return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "qr gen: " + err.Error()}) | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		userid, err := strconv.ParseInt(clientData.Client.TgUserid, 10, 64) | ||||
| 		if err != nil { | ||||
| 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "userid: " + err.Error()}) | ||||
| 		} | ||||
| 
 | ||||
| 		err = telegram.SendConfig(userid, clientData.Client.Name, configData, qrData) | ||||
| 
 | ||||
| 		if err != nil { | ||||
| 			return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()}) | ||||
| 		} | ||||
| 
 | ||||
| 		return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Telegram message sent successfully"}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // UpdateClient handler to update client information
 | ||||
| func UpdateClient(db store.IStore) echo.HandlerFunc { | ||||
| 	return func(c echo.Context) error { | ||||
|  |  | |||
							
								
								
									
										30
									
								
								main.go
								
								
								
								
							
							
						
						
									
										30
									
								
								main.go
								
								
								
								
							|  | @ -15,6 +15,7 @@ import ( | |||
| 	"github.com/labstack/echo/v4" | ||||
| 	"github.com/labstack/gommon/log" | ||||
| 	"github.com/ngoduykhanh/wireguard-ui/store" | ||||
| 	"github.com/ngoduykhanh/wireguard-ui/telegram" | ||||
| 
 | ||||
| 	"github.com/ngoduykhanh/wireguard-ui/emailer" | ||||
| 	"github.com/ngoduykhanh/wireguard-ui/handler" | ||||
|  | @ -42,6 +43,9 @@ var ( | |||
| 	flagSendgridApiKey           string | ||||
| 	flagEmailFrom                string | ||||
| 	flagEmailFromName            string = "WireGuard UI" | ||||
| 	flagTelegramToken            string | ||||
| 	flagTelegramAllowConfRequest bool   = false | ||||
| 	flagTelegramFloodWait        int    = 60 | ||||
| 	flagSessionSecret            string = util.RandomString(32) | ||||
| 	flagWgConfTemplate           string | ||||
| 	flagBasePath                 string | ||||
|  | @ -80,6 +84,9 @@ func init() { | |||
| 	flag.StringVar(&flagSmtpAuthType, "smtp-auth-type", util.LookupEnvOrString("SMTP_AUTH_TYPE", flagSmtpAuthType), "SMTP Auth Type : PLAIN, LOGIN or NONE.") | ||||
| 	flag.StringVar(&flagEmailFrom, "email-from", util.LookupEnvOrString("EMAIL_FROM_ADDRESS", flagEmailFrom), "'From' email address.") | ||||
| 	flag.StringVar(&flagEmailFromName, "email-from-name", util.LookupEnvOrString("EMAIL_FROM_NAME", flagEmailFromName), "'From' email name.") | ||||
| 	flag.StringVar(&flagTelegramToken, "telegram-token", util.LookupEnvOrString("TELEGRAM_TOKEN", flagTelegramToken), "Telegram bot token for distributing configs to clients.") | ||||
| 	flag.BoolVar(&flagTelegramAllowConfRequest, "telegram-allow-conf-request", util.LookupEnvOrBool("TELEGRAM_ALLOW_CONF_REQUEST", flagTelegramAllowConfRequest), "Allow users to get configs from the bot by sending a message.") | ||||
| 	flag.IntVar(&flagTelegramFloodWait, "telegram-flood-wait", util.LookupEnvOrInt("TELEGRAM_FLOOD_WAIT", flagTelegramFloodWait), "Time in minutes before the next conf request is processed.") | ||||
| 	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(&flagSubnetRanges, "subnet-ranges", util.LookupEnvOrString("SUBNET_RANGES", flagSubnetRanges), "IP ranges to choose from when assigning an IP for a client.") | ||||
|  | @ -131,8 +138,15 @@ func init() { | |||
| 	util.BasePath = util.ParseBasePath(flagBasePath) | ||||
| 	util.SubnetRanges = util.ParseSubnetRanges(flagSubnetRanges) | ||||
| 
 | ||||
| 	lvl, _ := util.ParseLogLevel(util.LookupEnvOrString(util.LogLevel, "INFO")) | ||||
| 
 | ||||
| 	telegram.TelegramToken = flagTelegramToken | ||||
| 	telegram.TelegramAllowConfRequest = flagTelegramAllowConfRequest | ||||
| 	telegram.TelegramFloodWait = flagTelegramFloodWait | ||||
| 	telegram.LogLevel = lvl | ||||
| 
 | ||||
| 	// print only if log level is INFO or lower
 | ||||
| 	if lvl, _ := util.ParseLogLevel(util.LookupEnvOrString(util.LogLevel, "INFO")); lvl <= log.INFO { | ||||
| 	if lvl <= log.INFO { | ||||
| 		// print app information
 | ||||
| 		fmt.Println("Wireguard UI") | ||||
| 		fmt.Println("App Version\t:", appVersion) | ||||
|  | @ -221,6 +235,7 @@ func main() { | |||
| 	app.POST(util.BasePath+"/new-client", handler.NewClient(db), handler.ValidSession, handler.ContentTypeJson) | ||||
| 	app.POST(util.BasePath+"/update-client", handler.UpdateClient(db), handler.ValidSession, handler.ContentTypeJson) | ||||
| 	app.POST(util.BasePath+"/email-client", handler.EmailClient(db, sendmail, defaultEmailSubject, defaultEmailContent), handler.ValidSession, handler.ContentTypeJson) | ||||
| 	app.POST(util.BasePath+"/send-telegram-client", handler.SendTelegramClient(db), handler.ValidSession, handler.ContentTypeJson) | ||||
| 	app.POST(util.BasePath+"/client/set-status", handler.SetClientStatus(db), handler.ValidSession, handler.ContentTypeJson) | ||||
| 	app.POST(util.BasePath+"/remove-client", handler.RemoveClient(db), handler.ValidSession, handler.ContentTypeJson) | ||||
| 	app.GET(util.BasePath+"/download", handler.DownloadClient(db), handler.ValidSession) | ||||
|  | @ -248,6 +263,8 @@ func main() { | |||
| 	// serves other static files
 | ||||
| 	app.GET(util.BasePath+"/static/*", echo.WrapHandler(http.StripPrefix(util.BasePath+"/static/", assetHandler))) | ||||
| 
 | ||||
| 	initTelegram(db, util.BuildClientConfig) | ||||
| 
 | ||||
| 	if strings.HasPrefix(util.BindAddress, "unix://") { | ||||
| 		// Listen on unix domain socket.
 | ||||
| 		// https://github.com/labstack/echo/issues/830
 | ||||
|  | @ -296,3 +313,14 @@ func initServerConfig(db store.IStore, tmplDir fs.FS) { | |||
| 		log.Fatalf("Cannot create server config: ", err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func initTelegram(db store.IStore, buildClientConfig telegram.BuildClientConfig) { | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			err := telegram.Start(db, buildClientConfig) | ||||
| 			if err == nil { | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	}() | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,116 @@ | |||
| package telegram | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/NicoNex/echotron/v3" | ||||
| 	"github.com/labstack/gommon/log" | ||||
| 	"github.com/ngoduykhanh/wireguard-ui/model" | ||||
| 	"github.com/ngoduykhanh/wireguard-ui/store" | ||||
| ) | ||||
| 
 | ||||
| type BuildClientConfig func(client model.Client, server model.Server, setting model.GlobalSetting) string | ||||
| 
 | ||||
| var ( | ||||
| 	TelegramToken            string | ||||
| 	TelegramAllowConfRequest bool | ||||
| 	TelegramFloodWait        int | ||||
| 	LogLevel                 log.Lvl | ||||
| 
 | ||||
| 	TgBot      *echotron.API | ||||
| 	TgBotMutex sync.RWMutex | ||||
| 
 | ||||
| 	floodWait = make(map[int64]int64, 0) | ||||
| ) | ||||
| 
 | ||||
| func Start(db store.IStore, buildClientConfig BuildClientConfig) (err error) { | ||||
| 	defer func() { | ||||
| 		TgBotMutex.Lock() | ||||
| 		TgBot = nil | ||||
| 		TgBotMutex.Unlock() | ||||
| 		if r := recover(); r != nil { | ||||
| 			err = fmt.Errorf("[PANIC] recovered from panic: %v", r) | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	token := TelegramToken | ||||
| 	if token == "" || len(token) < 30 { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	bot := echotron.NewAPI(token) | ||||
| 
 | ||||
| 	res, err := bot.GetMe() | ||||
| 	if !res.Ok || err != nil { | ||||
| 		log.Warnf("[Telegram] Unable to connect to bot.\n%v\n%v", res.Description, err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	TgBotMutex.Lock() | ||||
| 	TgBot = &bot | ||||
| 	TgBotMutex.Unlock() | ||||
| 
 | ||||
| 	if LogLevel <= log.INFO { | ||||
| 		fmt.Printf("[Telegram] Authorized as %s\n", res.Result.Username) | ||||
| 	} | ||||
| 
 | ||||
| 	if !TelegramAllowConfRequest { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	ticker := time.NewTicker(time.Minute) | ||||
| 	go func() { | ||||
| 		for range ticker.C { | ||||
| 			updateFloodWait() | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	updatesChan := echotron.PollingUpdatesOptions(token, false, echotron.UpdateOptions{AllowedUpdates: []echotron.UpdateType{echotron.MessageUpdate}}) | ||||
| 	for update := range updatesChan { | ||||
| 		if update.Message != nil { | ||||
| 			floodWait[update.Message.Chat.ID] = time.Now().Unix() | ||||
| 		} | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func SendConfig(userid int64, clientName string, confData, qrData []byte) error { | ||||
| 	TgBotMutex.RLock() | ||||
| 	defer TgBotMutex.RUnlock() | ||||
| 
 | ||||
| 	if TgBot == nil { | ||||
| 		return fmt.Errorf("telegram bot is not configured or not available") | ||||
| 	} | ||||
| 
 | ||||
| 	if _, wait := floodWait[userid]; wait { | ||||
| 		return fmt.Errorf("this client already got their config less than %d minutes ago", TelegramFloodWait) | ||||
| 	} | ||||
| 
 | ||||
| 	qrAttachment := echotron.NewInputFileBytes("qr.png", qrData) | ||||
| 	_, err := TgBot.SendPhoto(qrAttachment, userid, &echotron.PhotoOptions{Caption: clientName}) | ||||
| 	if err != nil { | ||||
| 		log.Error(err) | ||||
| 		return fmt.Errorf("unable to send qr picture") | ||||
| 	} | ||||
| 
 | ||||
| 	confAttachment := echotron.NewInputFileBytes(clientName+".conf", confData) | ||||
| 	_, err = TgBot.SendDocument(confAttachment, userid, nil) | ||||
| 	if err != nil { | ||||
| 		log.Error(err) | ||||
| 		return fmt.Errorf("unable to send conf file") | ||||
| 	} | ||||
| 
 | ||||
| 	floodWait[userid] = time.Now().Unix() | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func updateFloodWait() { | ||||
| 	thresholdTS := time.Now().Unix() - 60000*int64(TelegramFloodWait) | ||||
| 	for userid, ts := range floodWait { | ||||
| 		if ts < thresholdTS { | ||||
| 			delete(floodWait, userid) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -80,6 +80,35 @@ Wireguard Clients | |||
| </div> | ||||
| <!-- /.modal --> | ||||
| 
 | ||||
| <div class="modal fade" id="modal_telegram_client"> | ||||
|     <div class="modal-dialog"> | ||||
|         <div class="modal-content"> | ||||
|             <div class="modal-header"> | ||||
|                 <h4 class="modal-title">Telegram Configuration</h4> | ||||
|                 <button type="button" class="close" data-dismiss="modal" aria-label="Close"> | ||||
|                     <span aria-hidden="true">×</span> | ||||
|                 </button> | ||||
|             </div> | ||||
|             <form name="frm_telegram_client" id="frm_telegram_client"> | ||||
|                 <div class="modal-body"> | ||||
|                     <input type="hidden" id="tg_client_id" name="tg_client_id"> | ||||
|                     <div class="form-group"> | ||||
|                         <label for="tg_client_userid" class="control-label">Telegram userid</label> | ||||
|                         <input type="text" class="form-control" id="tg_client_userid" name="tg_client_userid"> | ||||
|                     </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">Send</button> | ||||
|                 </div> | ||||
|             </form> | ||||
|         </div> | ||||
|         <!-- /.modal-content --> | ||||
|     </div> | ||||
|     <!-- /.modal-dialog --> | ||||
| </div> | ||||
| <!-- /.modal --> | ||||
| 
 | ||||
| <div class="modal fade" id="modal_edit_client"> | ||||
|     <div class="modal-dialog"> | ||||
|         <div class="modal-content"> | ||||
|  | @ -683,6 +712,31 @@ Wireguard Clients | |||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         // submitTelegramClient function for sending a telegram message with the configuration to the client | ||||
|         function submitTelegramClient() { | ||||
|             const client_id = $("#tg_client_id").val(); | ||||
|             const userid = $("#tg_client_userid").val(); | ||||
|             const data = {"id": client_id, "userid": userid}; | ||||
|             $.ajax({ | ||||
|                 cache: false, | ||||
|                 method: 'POST', | ||||
|                 url: '{{.basePath}}/send-telegram-client', | ||||
|                 dataType: 'json', | ||||
|                 contentType: "application/json", | ||||
|                 data: JSON.stringify(data), | ||||
|                 success: function(resp) { | ||||
|                     $("#modal_telegram_client").modal('hide'); | ||||
|                     toastr.success('Sent config via telegram to client successfully'); | ||||
|                     // Refresh the home page (clients page) after sending email successfully | ||||
|                     location.reload(); | ||||
|                 }, | ||||
|                 error: function(jqXHR, exception) { | ||||
|                     const responseJson = jQuery.parseJSON(jqXHR.responseText); | ||||
|                     toastr.error(responseJson['message']); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         // submitEditClient function for updating an existing client | ||||
|         // This sends dialogue data to the back-end when user presses "Save" | ||||
|         // See e.g. routes.go:UpdateClient for where data is processed/verified. | ||||
|  | @ -745,6 +799,8 @@ Wireguard Clients | |||
|                 submitEditClient(); | ||||
|             } else if (formId === "frm_email_client") { | ||||
|                 submitEmailClient(); | ||||
|             } else if (formId === "frm_telegram_client") { | ||||
|                 submitTelegramClient(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  | @ -781,6 +837,30 @@ Wireguard Clients | |||
|             regenerateQRCode(); | ||||
|         }); | ||||
| 
 | ||||
|         $("#modal_telegram_client").on('show.bs.modal', function (event) { | ||||
|             let modal = $(this); | ||||
|             const button = $(event.relatedTarget); | ||||
|             const client_id = button.data('clientid'); | ||||
|             $.ajax({ | ||||
|                 cache: false, | ||||
|                 method: 'GET', | ||||
|                 url: '{{.basePath}}/api/client/' + client_id, | ||||
|                 dataType: 'json', | ||||
|                 contentType: "application/json", | ||||
|                 success: function (resp) { | ||||
|                     const client = resp.Client; | ||||
| 
 | ||||
|                     modal.find(".modal-title").text("Send config to client " + client.name); | ||||
|                     modal.find("#tg_client_id").val(client.id); | ||||
|                     modal.find("#tg_client_userid").val(client.telegram_userid); | ||||
|                 }, | ||||
|                 error: function (jqXHR, exception) { | ||||
|                     const responseJson = jQuery.parseJSON(jqXHR.responseText); | ||||
|                     toastr.error(responseJson['message']); | ||||
|                 } | ||||
|             }); | ||||
|         }); | ||||
| 
 | ||||
|         $(document).ready(function () { | ||||
|             $.validator.setDefaults({ | ||||
|                 submitHandler: function (form) { | ||||
|  | @ -836,6 +916,32 @@ Wireguard Clients | |||
|                     $(element).removeClass('is-invalid'); | ||||
|                 } | ||||
|             }); | ||||
|             // Telegram client form validation | ||||
|             $("#frm_telegram_client").validate({ | ||||
|                 rules: { | ||||
|                     tg_client_userid: { | ||||
|                         required: true, | ||||
|                         number: true, | ||||
|                     }, | ||||
|                 }, | ||||
|                 messages: { | ||||
|                     tg_client_userid: { | ||||
|                         required: "Please enter a telegram userid", | ||||
|                         number: "Please enter a valid telegram userid" | ||||
|                     }, | ||||
|                 }, | ||||
|                 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> | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ import ( | |||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/ngoduykhanh/wireguard-ui/store" | ||||
| 	"github.com/ngoduykhanh/wireguard-ui/telegram" | ||||
| 	"golang.org/x/mod/sumdb/dirhash" | ||||
| 
 | ||||
| 	externalip "github.com/glendc/go-external-ip" | ||||
|  | @ -28,7 +29,7 @@ import ( | |||
| ) | ||||
| 
 | ||||
| // BuildClientConfig to create wireguard client config string
 | ||||
| func BuildClientConfig(client model.Client, server model.Server, setting model.GlobalSetting) string { | ||||
| var BuildClientConfig telegram.BuildClientConfig = func(client model.Client, server model.Server, setting model.GlobalSetting) string { | ||||
| 	// Interface section
 | ||||
| 	clientAddress := fmt.Sprintf("Address = %s\n", strings.Join(client.AllocatedIPs, ",")) | ||||
| 	clientPrivateKey := fmt.Sprintf("PrivateKey = %s\n", client.PrivateKey) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue