Merge branch 'master' into show-apply-config-when-needed

This commit is contained in:
Arminas 2023-03-07 14:56:47 +02:00 committed by GitHub
commit 59ceb81804
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 245 additions and 19 deletions

View file

@ -53,6 +53,7 @@ Note:
| `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_HASH` | The password hash for the user on the login page. (alternative to `WGUI_PASSWORD`). Used for db initialization only | N/A |
| `WGUI_FAVICON_FILE_PATH` | The file path used as website favicon | Embedded WireGuard logo |
| `WGUI_ENDPOINT_ADDRESS` | The default endpoint address used in global settings | Resolved to your public ip address |
| `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` |
@ -68,7 +69,7 @@ Note:
| `SMTP_USERNAME` | The SMTP username | N/A |
| `SMTP_PASSWORD` | The SMTP user password | N/A |
| `SMTP_AUTH_TYPE` | The SMTP authentication type. Possible values: `PLAIN`, `LOGIN`, `NONE` | `NONE` |
| `SMTP_ENCRYPTION` | the encryption method. Possible values: `SSL`, `SSLTLS`, `TLS`, `STARTTLS` | `STARTTLS` |
| `SMTP_ENCRYPTION` | the encryption method. Possible values: `NONE`, `SSL`, `SSLTLS`, `TLS`, `STARTTLS` | `STARTTLS` |
### Defaults for server configuration

BIN
custom/img/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -33,6 +33,8 @@ func authType(authType string) mail.AuthType {
func encryptionType(encryptionType string) mail.Encryption {
switch strings.ToUpper(encryptionType) {
case "NONE":
return mail.EncryptionNone
case "SSL":
return mail.EncryptionSSL
case "SSLTLS":

View file

@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"os"
"sort"
"strings"
"time"
@ -32,6 +33,15 @@ func Health() echo.HandlerFunc {
}
}
func Favicon() echo.HandlerFunc {
return func(c echo.Context) error {
if favicon, ok := os.LookupEnv(util.FaviconFilePathEnvVar); ok {
return c.File(favicon)
}
return c.Redirect(http.StatusFound, util.BasePath+"/static/custom/img/favicon.ico")
}
}
// LoginPage handler
func LoginPage() echo.HandlerFunc {
return func(c echo.Context) error {
@ -607,6 +617,8 @@ func Status(db store.IStore) echo.HandlerFunc {
LastHandshakeTime time.Time
LastHandshakeRel time.Duration
Connected bool
AllocatedIP string
Endpoint string
}
type DeviceVM struct {
@ -654,12 +666,21 @@ func Status(db store.IStore) echo.HandlerFunc {
for i := range devices {
devVm := DeviceVM{Name: devices[i].Name}
for j := range devices[i].Peers {
var allocatedIPs string
for _, ip := range devices[i].Peers[j].AllowedIPs {
if len(allocatedIPs) > 0 {
allocatedIPs += "</br>"
}
allocatedIPs += ip.String()
}
pVm := PeerVM{
PublicKey: devices[i].Peers[j].PublicKey.String(),
ReceivedBytes: devices[i].Peers[j].ReceiveBytes,
TransmitBytes: devices[i].Peers[j].TransmitBytes,
LastHandshakeTime: devices[i].Peers[j].LastHandshakeTime,
LastHandshakeRel: time.Since(devices[i].Peers[j].LastHandshakeTime),
AllocatedIP: allocatedIPs,
Endpoint: devices[i].Peers[j].Endpoint.String(),
}
pVm.Connected = pVm.LastHandshakeRel.Minutes() < 3.
@ -817,6 +838,7 @@ func ApplyServerConfig(db store.IStore, tmplBox *rice.Box) echo.HandlerFunc {
}
}
// GetHashesChanges handler returns if database hashes have changed
func GetHashesChanges(db store.IStore) echo.HandlerFunc {
return func(c echo.Context) error {
@ -827,3 +849,13 @@ func GetHashesChanges(db store.IStore) echo.HandlerFunc {
}
}
}
// AboutPage handler
func AboutPage() echo.HandlerFunc {
return func(c echo.Context) error {
return c.Render(http.StatusOK, "about.html", map[string]interface{}{
"baseData": model.BaseData{Active: "about", CurrentUser: currentUser(c)},
})
}
}

View file

@ -61,7 +61,7 @@ func init() {
flag.StringVar(&flagSmtpUsername, "smtp-username", util.LookupEnvOrString("SMTP_USERNAME", flagSmtpUsername), "SMTP Username")
flag.StringVar(&flagSmtpPassword, "smtp-password", util.LookupEnvOrString("SMTP_PASSWORD", flagSmtpPassword), "SMTP Password")
flag.BoolVar(&flagSmtpNoTLSCheck, "smtp-no-tls-check", util.LookupEnvOrBool("SMTP_NO_TLS_CHECK", flagSmtpNoTLSCheck), "Disable TLS verification for SMTP. This is potentially dangerous.")
flag.StringVar(&flagSmtpEncryption, "smtp-encryption", util.LookupEnvOrString("SMTP_ENCRYPTION", flagSmtpEncryption), "SMTP Encryption : SSL, SSLTLS, TLS or STARTTLS (by default)")
flag.StringVar(&flagSmtpEncryption, "smtp-encryption", util.LookupEnvOrString("SMTP_ENCRYPTION", flagSmtpEncryption), "SMTP Encryption : NONE, SSL, SSLTLS, TLS or STARTTLS (by default)")
flag.StringVar(&flagSmtpAuthType, "smtp-auth-type", util.LookupEnvOrString("SMTP_AUTH_TYPE", flagSmtpAuthType), "SMTP Auth Type : PLAIN, LOGIN or NONE.")
flag.StringVar(&flagSendgridApiKey, "sendgrid-api-key", util.LookupEnvOrString("SENDGRID_API_KEY", flagSendgridApiKey), "Your sendgrid api key.")
flag.StringVar(&flagEmailFrom, "email-from", util.LookupEnvOrString("EMAIL_FROM_ADDRESS", flagEmailFrom), "'From' email address.")
@ -147,9 +147,10 @@ func main() {
sendmail = emailer.NewSmtpMail(util.SmtpHostname, util.SmtpPort, util.SmtpUsername, util.SmtpPassword, util.SmtpNoTLSCheck, util.SmtpAuthType, util.EmailFromName, util.EmailFrom, util.SmtpEncryption)
}
app.GET(util.BasePath+"/test-hash", handler.GetHashesChanges(db), handler.ValidSession)
app.GET(util.BasePath+"/about", handler.AboutPage())
app.GET(util.BasePath+"/_health", handler.Health())
app.GET(util.BasePath+"/favicon", handler.Favicon())
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)

View file

@ -93,6 +93,11 @@ func New(tmplBox *rice.Box, extraData map[string]string, secret []byte) *echo.Ec
log.Fatal(err)
}
aboutPageString, err := tmplBox.String("about.html")
if err != nil {
log.Fatal(err)
}
// create template list
funcs := template.FuncMap{
"StringsJoin": strings.Join,
@ -105,6 +110,7 @@ func New(tmplBox *rice.Box, extraData map[string]string, secret []byte) *echo.Ec
templates["global_settings.html"] = template.Must(template.New("global_settings").Funcs(funcs).Parse(tmplBaseString + tmplGlobalSettingsString))
templates["status.html"] = template.Must(template.New("status").Funcs(funcs).Parse(tmplBaseString + tmplStatusString))
templates["wake_on_lan_hosts.html"] = template.Must(template.New("wake_on_lan_hosts").Funcs(funcs).Parse(tmplBaseString + tmplWakeOnLanHostsString))
templates["about.html"] = template.Must(template.New("about").Funcs(funcs).Parse(tmplBaseString + aboutPageString))
e.Logger.SetLevel(log.DEBUG)
e.Pre(middleware.RemoveTrailingSlash())

139
templates/about.html Normal file
View file

@ -0,0 +1,139 @@
{{ define "title"}}
About
{{ end }}
{{ define "top_css"}}
{{ end }}
{{ define "username"}}
{{ .username }}
{{ end }}
{{ define "page_title"}}
About
{{ end }}
{{ define "page_content"}}
<section class="content">
<div class="container-fluid">
<!-- <h5 class="mt-4 mb-2">Global Settings</h5> -->
<div class="row">
<!-- left column -->
<div class="col-md-6">
<div class="card card-success">
<div class="card-header">
<h3 class="card-title">About Wireguard-UI</h3>
</div>
<!-- /.card-header -->
<div class="card-body">
<div class="form-group">
<label for="version" class="control-label">Current version</label>
<input type="text" class="form-control" id="version" value="{{ .appVersion }}" readonly>
</div>
<div class="form-group">
<label for="currentReleaseDate" class="control-label">Current version release date</label>
<input type="text" class="form-control" id="currentReleaseDate" readonly>
</div>
<div class="form-group">
<label for="latestRelease" class="control-label">Latest release</label>
<input type="text" class="form-control" id="latestRelease" readonly>
</div>
<div class="form-group">
<label for="latestReleaseDate" class="control-label">Latest release date</label>
<input type="text" class="form-control" id="latestReleaseDate" readonly>
</div>
<div class="form-group">
<label for="author" class="control-label">Author</label>
<div id="author">
<a id="authorLink">
<img id="authorImage"
style="width: 50px; height: 50px; border-radius: 50%; border: 1px solid #000;">
</a>
</div>
</div>
<div class="form-group">
<label for="contributors" class="control-label">Contributors</label>
<div id="contributors"></div>
</div>
<strong>Copyright &copy;
<script>document.write(new Date().getFullYear())</script>
<a href="https://github.com/ngoduykhanh/wireguard-ui">Wireguard UI</a>.
</strong> All rights reserved.
</div>
</div>
<!-- /.card -->
</div>
</div>
<!-- /.row -->
</div>
</section>
{{ end }}
{{ define "bottom_js"}}
<script>
$(document).ready(function () {
$.ajax({
cache: false,
method: 'GET',
url: 'https://api.github.com/repos/ngoduykhanh/wireguard-ui/releases/tags/' + $("#version").val(),
dataType: 'json',
contentType: "application/json",
success: function (data) {
$("#currentReleaseDate").attr("value", data.published_at.split("T")[0]);
},
error: function (jqXHR, exception) {
$("#currentReleaseDate").attr("value", "Could not find this version on GitHub.com");
}
});
$.ajax({
cache: false,
method: 'GET',
url: 'https://api.github.com/repos/ngoduykhanh/wireguard-ui/releases/latest',
dataType: 'json',
contentType: "application/json",
success: function (data) {
$("#latestRelease").attr("value", data.tag_name);
$("#latestReleaseDate").attr("value", data.published_at.split("T")[0]);
$("#author").attr("value", data.author.login);
$("#authorImage").attr("src", data.author.avatar_url);
$("#authorImage").after("<b> " + data.author.login + "</b>");
$("#authorLink").attr("href", data.author.html_url);
},
error: function (jqXHR, exception) {
$("#latestRelease").attr("value", "Could not connect to GitHub.com");
$("#latestReleaseDate").attr("value", "Could not connect to GitHub.com");
$("#author").attr("value", "Could not connect to GitHub.com");
}
});
$.ajax({
cache: false,
method: 'GET',
url: 'https://api.github.com/repos/ngoduykhanh/wireguard-ui/contributors',
dataType: 'json',
contentType: "application/json",
success: function (data) {
data.forEach(contributor => $("#contributors").append("<a href=\"" + contributor.html_url + "\" title=\"" + contributor.login + "\">" +
"<img src=\"" + contributor.avatar_url + "\" style=\"width: 50px; height: 50px; border-radius: 50%; border: 1px solid #000; margin: 5px;\"/></a>"));
},
error: function (jqXHR, exception) {
$("#contributors").html("<p>Could not connect to GitHub.com</p>");
}
});
});
$(document).ajaxStop(function () {
if (Date.parse($("#currentReleaseDate").val()) < Date.parse($("#latestReleaseDate").val())) {
$("#currentReleaseDate").after("<p style=\"color:red\">Current version is out of date</p>")
}
});
</script>
{{ end }}

View file

@ -8,6 +8,8 @@
<title>{{template "title" .}}</title>
<!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Favicon -->
<link rel="icon" href="{{.basePath}}/favicon">
<!-- Font Awesome -->
<link rel="stylesheet" href="{{.basePath}}/static/plugins/fontawesome-free/css/all.min.css">
@ -44,17 +46,17 @@
</ul>
<!-- SEARCH FORM -->
<!-- <form class="form-inline ml-3">-->
<!-- <div class="input-group input-group-sm">-->
<!-- <input class="form-control form-control-navbar" type="search" placeholder="Search"-->
<!-- aria-label="Search">-->
<!-- <div class="input-group-append">-->
<!-- <button class="btn btn-navbar" type="submit">-->
<!-- <i class="fas fa-search"></i>-->
<!-- </button>-->
<!-- </div>-->
<!-- </div>-->
<!-- </form>-->
<form class="form-inline ml-3" style="display: none" id="search-form">
<div class="input-group input-group-sm">
<input class="form-control form-control-navbar" placeholder="Search"
aria-label="Search" id="search-input">
<div class="input-group-append">
<button class="btn-navbar" type="submit" disabled>
<i class="fas fa-search"></i>
</button>
</div>
</div>
</form>
<!-- Right navbar links -->
<div class="navbar-nav ml-auto">
@ -141,6 +143,15 @@
</p>
</a>
</li>
<li class="nav-header">ABOUT</li>
<li class="nav-item">
<a href="{{.basePath}}/about" class="nav-link {{if eq .baseData.Active "about" }}active{{end}}">
<i class="nav-icon fas fa-solid fa-id-card"></i>
<p>
About
</p>
</a>
</li>
</ul>
</nav>
<!-- /.sidebar-menu -->
@ -281,7 +292,7 @@
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
<!--
<footer class="main-footer">
<div class="float-right d-none d-sm-block">
<b>Version</b> {{ .appVersion }}
@ -289,7 +300,7 @@
<strong>Copyright &copy; <script>document.write(new Date().getFullYear())</script> <a href="https://github.com/ngoduykhanh/wireguard-ui">Wireguard UI</a>.</strong> All rights
reserved.
</footer>
-->
<!-- Control Sidebar -->
<aside class="control-sidebar control-sidebar-dark">
<!-- Control sidebar content goes here -->

View file

@ -70,7 +70,9 @@ Wireguard Clients
</div>
<div class="modal-body">
<input type="hidden" id="qr_client_id" name="qr_client_id">
<img id="qr_code" class="w-100" style="image-rendering: pixelated;" src="" alt="QR code" />
<a href="" download="" id="qr_code_a">
<img id="qr_code" class="w-100" style="image-rendering: pixelated;" src="" alt="QR code" />
</a>
<div class="form-group">
<div class="icheck-primary d-inline">
<input type="checkbox" id="qr_include_fwmark" onchange="regenerateQRCode()">
@ -250,6 +252,28 @@ Wireguard Clients
populateClientList();
})
// show search bar and override :contains to be case-insensitive
$(document).ready(function () {
$("#search-form").show();
jQuery.expr[':'].contains = function(a, i, m) {
return jQuery(a).text().toUpperCase()
.indexOf(m[3].toUpperCase()) >= 0;
};
})
// hide all clients and display only the ones that meet the search criteria (name, email, IP)
$('#search-input').keyup(function () {
var query = $(this).val();
$('.col-lg-4').hide();
$(".info-box-text").each(function() {
if($(this).children('i.fa-user').length > 0 || $(this).children('i.fa-envelope').length > 0)
{
$(this).filter(':contains("' + query + '")').parent().parent().parent().show();
}
})
$(".badge-secondary").filter(':contains("' + query + '")').parent().parent().parent().show();
})
// modal_pause_client modal event
$("#modal_pause_client").on('show.bs.modal', function (event) {
const button = $(event.relatedTarget);
@ -391,6 +415,7 @@ Wireguard Clients
function regenerateQRCode() {
const client_id = $("#qr_client_id").val();
const QRCodeImg = $("#qr_code");
const QRCodeA = $("#qr_code_a");
let include_fwmark = false;
if ($("#qr_include_fwmark").is(':checked')){
include_fwmark = true;
@ -410,6 +435,8 @@ Wireguard Clients
$(".modal-title").text("Scan QR Code for " + client.name + " profile");
QRCodeImg.attr('src', resp.QRCode).show();
QRCodeA.attr('download', resp.Client.name);
QRCodeA.attr('href', resp.QRCode).show();
},
error: function (jqXHR, exception) {
const responseJson = jQuery.parseJSON(jqXHR.responseText);

View file

@ -91,7 +91,7 @@ Global Settings
<dt>2. DNS Servers</dt>
<dd>The DNS servers will be set to client config.</dd>
<dt>3. MTU</dt>
<dd>The MTU will be set to server and client config. By default it is <code>1420</code>. You might want
<dd>The MTU will be set to server and client config. By default it is <code>1450</code>. You might want
to adjust the MTU size if your connection (e.g PPPoE, 3G, satellite network, etc) has a low MTU.</dd>
<dd>Leave blank to omit this setting in the configs.</dd>
<dt>4. Persistent Keepalive</dt>

View file

@ -7,6 +7,8 @@
<title>WireGuard UI</title>
<!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Favicon -->
<link rel="icon" href="{{.basePath}}/favicon">
<!-- Font Awesome -->
<link rel="stylesheet" href="{{.basePath}}/static/plugins/fontawesome-free/css/all.min.css">

View file

@ -41,6 +41,8 @@ Connected Peers
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Allocated IPs</th>
<th scope="col">Endpoint</th>
<th scope="col">Public Key</th>
<th scope="col">Received</th>
<th scope="col">Transmitted</th>
@ -54,6 +56,8 @@ Connected Peers
<th scope="row">{{ $idx }}</th>
<td>{{ $peer.Name }}</td>
<td>{{ $peer.Email }}</td>
<td>{{ $peer.AllocatedIP }}</td>
<td>{{ $peer.Endpoint }}</td>
<td>{{ $peer.PublicKey }}</td>
<td title="{{ $peer.ReceivedBytes }} Bytes"><script>document.write(bytesToHumanReadable({{ $peer.ReceivedBytes }}))</script></td>
<td title="{{ $peer.TransmitBytes }} Bytes"><script>document.write(bytesToHumanReadable({{ $peer.TransmitBytes }}))</script></td>
@ -68,4 +72,4 @@ Connected Peers
</section>
{{end}}
{{define "bottom_js"}}
{{end}}
{{end}}

View file

@ -34,6 +34,7 @@ const (
UsernameEnvVar = "WGUI_USERNAME"
PasswordEnvVar = "WGUI_PASSWORD"
PasswordHashEnvVar = "WGUI_PASSWORD_HASH"
FaviconFilePathEnvVar = "WGUI_FAVICON_FILE_PATH"
EndpointAddressEnvVar = "WGUI_ENDPOINT_ADDRESS"
DNSEnvVar = "WGUI_DNS"
MTUEnvVar = "WGUI_MTU"