Replace from-scratch rewrite with real ngoduykhanh/wireguard-ui fork
The from-scratch Go rewrite had unresolved bugs (missing go.sum, UI 404s, path issues) from being built without a working local Go toolchain to verify against. Switching strategy: use the actual upstream wireguard-ui codebase (proven, battle-tested single-server manager) as the base, and extend it for multi-server support instead of re-deriving everything from zero. Kept our own installers (bootstrap.sh, update.sh, scripts/install.sh, scripts/proxmox-install.sh) - these still apply, just need updating to build/install the upstream module layout instead of the old cmd/wireguard-ui-multi structure. Module path intentionally left as upstream's own (github.com/ngoduykhanh/wireguard-ui) for now to avoid touching every internal import; revisit if this needs to be fully rebranded. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
6eeea65ede
commit
867dc7740a
@@ -0,0 +1,145 @@
|
||||
{{ 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>
|
||||
{{ if .gitCommit }}
|
||||
<div class="form-group">
|
||||
<label for="version" class="control-label">git commit hash</label>
|
||||
<input type="text" class="form-control" id="version" value="{{ .gitCommit }}" readonly>
|
||||
</div>
|
||||
{{ end }}
|
||||
<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 ©
|
||||
<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 }}
|
||||
@@ -0,0 +1,674 @@
|
||||
{{define "base.html"}}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<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">
|
||||
<!-- iCheck for checkboxes and radio inputs -->
|
||||
<link rel="stylesheet" href="{{.basePath}}/static/plugins/icheck-bootstrap/icheck-bootstrap.min.css">
|
||||
<!-- Select2 -->
|
||||
<link rel="stylesheet" href="{{.basePath}}/static/plugins/select2/css/select2.min.css">
|
||||
<!-- Toastr -->
|
||||
<link rel="stylesheet" href="{{.basePath}}/static/plugins/toastr/toastr.min.css">
|
||||
<!-- Jquery Tags Input -->
|
||||
<link rel="stylesheet" href="{{.basePath}}/static/plugins/jquery-tags-input/dist/jquery.tagsinput.min.css">
|
||||
<!-- Ionicons -->
|
||||
<link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
|
||||
<!-- overlayScrollbars -->
|
||||
<link rel="stylesheet" href="{{.basePath}}/static/dist/css/adminlte.min.css">
|
||||
<!-- Google Font: Source Sans Pro -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet">
|
||||
|
||||
<!-- START: On page css -->
|
||||
{{template "top_css" .}}
|
||||
<!-- END: On page css -->
|
||||
</head>
|
||||
|
||||
<body class="hold-transition sidebar-mini">
|
||||
<!-- Site wrapper -->
|
||||
<div class="wrapper">
|
||||
<!-- Navbar -->
|
||||
<nav class="main-header navbar navbar-expand navbar-white navbar-light">
|
||||
<!-- Left navbar links -->
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- SEARCH 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>
|
||||
<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;">
|
||||
<!-- THIS SECTION IS OVERRIDDEN BY JS. SEE updateSearchList() function in clients.html BEFORE EDITING -->
|
||||
<option value="All">All</option>
|
||||
<option value="Enabled">Enabled</option>
|
||||
<option value="Disabled">Disabled</option>
|
||||
<option value="Connected">Connected</option>
|
||||
<option value="Disconnected">Disconnected</option>
|
||||
<!-- THIS SECTION IS OVERRIDDEN BY JS. SEE updateSearchList() function in clients.html BEFORE EDITING -->
|
||||
</select>
|
||||
</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 id="apply-config-button" style="margin-left: 0.5em; display: none;" 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>
|
||||
{{if .baseData.CurrentUser}}
|
||||
<button onclick="location.href='{{.basePath}}/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>
|
||||
{{end}}
|
||||
</div>
|
||||
</nav>
|
||||
<!-- /.navbar -->
|
||||
|
||||
<!-- Main Sidebar Container -->
|
||||
<aside class="main-sidebar sidebar-dark-primary elevation-4">
|
||||
<!-- Brand Logo -->
|
||||
<a href="{{.basePath}}" class="brand-link">
|
||||
<span class="brand-text"> WIREGUARD UI</span>
|
||||
</a>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<!-- Sidebar user (optional) -->
|
||||
<div class="user-panel mt-3 pb-3 mb-3 d-flex">
|
||||
<div class="image">
|
||||
<i class="nav-icon fas fa-2x fa-user"></i>
|
||||
</div>
|
||||
<div class="info">
|
||||
{{if .baseData.CurrentUser}}
|
||||
|
||||
{{if .baseData.Admin}}
|
||||
<a href="{{.basePath}}/profile" class="d-block">Administrator: {{.baseData.CurrentUser}}</a>
|
||||
{{else}}
|
||||
<a href="{{.basePath}}/profile" class="d-block">Manager: {{.baseData.CurrentUser}}</a>
|
||||
{{end}}
|
||||
|
||||
{{else}}
|
||||
<a href="#" class="d-block">Administrator</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar Menu -->
|
||||
<nav class="mt-2">
|
||||
<ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
|
||||
<li class="nav-header">MAIN</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{.basePath}}/" class="nav-link {{if eq .baseData.Active ""}}active{{end}}">
|
||||
<i class="nav-icon fas fa-user-secret"></i>
|
||||
<p>
|
||||
Wireguard Clients
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{{if .baseData.Admin}}
|
||||
<li class="nav-item">
|
||||
<a href="{{.basePath}}/wg-server" class="nav-link {{if eq .baseData.Active "wg-server" }}active{{end}}">
|
||||
<i class="nav-icon fas fa-server"></i>
|
||||
<p>
|
||||
Wireguard Server
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
<li class="nav-header">SETTINGS</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{.basePath}}/global-settings" class="nav-link {{if eq .baseData.Active "global-settings" }}active{{end}}">
|
||||
<i class="nav-icon fas fa-cog"></i>
|
||||
<p>
|
||||
Global Settings
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
{{if not .loginDisabled}}
|
||||
<li class="nav-item">
|
||||
<a href="{{.basePath}}/users-settings" class="nav-link {{if eq .baseData.Active "users-settings" }}active{{end}}">
|
||||
<i class="nav-icon fas fa-cog"></i>
|
||||
<p>
|
||||
Users Settings
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
<li class="nav-header">UTILITIES</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{.basePath}}/status" class="nav-link {{if eq .baseData.Active "status" }}active{{end}}">
|
||||
<i class="nav-icon fas fa-signal"></i>
|
||||
<p>
|
||||
Status
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{.basePath}}/wake_on_lan_hosts" class="nav-link {{if eq .baseData.Active "wake_on_lan_hosts" }}active{{end}}">
|
||||
<i class="nav-icon fas fa-solid fa-power-off"></i>
|
||||
<p>
|
||||
WoL Hosts
|
||||
</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 -->
|
||||
</div>
|
||||
<!-- /.sidebar -->
|
||||
</aside>
|
||||
|
||||
<div class="modal fade" id="modal_new_client">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">New Wireguard Client</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form name="frm_new_client" id="frm_new_client">
|
||||
<div class="modal-body">
|
||||
<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="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">
|
||||
<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
|
||||
<i class="fas fa-info-circle" data-toggle="tooltip"
|
||||
data-original-title="Specify a list of addresses that will get routed to the
|
||||
server. These addresses will be included in 'AllowedIPs' of client config">
|
||||
</i>
|
||||
</label>
|
||||
<input type="text" data-role="tagsinput" class="form-control" id="client_allowed_ips"
|
||||
value="{{ StringsJoin .client_defaults.AllowedIps "," }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="client_extra_allowed_ips" class="control-label">Extra Allowed IPs
|
||||
<i class="fas fa-info-circle" data-toggle="tooltip"
|
||||
data-original-title="Specify a list of addresses that will get routed to the
|
||||
client. These addresses will be included in 'AllowedIPs' of WG server config">
|
||||
</i>
|
||||
</label>
|
||||
<input type="text" data-role="tagsinput" class="form-control" id="client_extra_allowed_ips" value="{{ StringsJoin .client_defaults.ExtraAllowedIps "," }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="client_endpoint" class="control-label">Endpoint</label>
|
||||
<input type="text" class="form-control" id="client_endpoint" name="client_endpoint">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="icheck-primary d-inline">
|
||||
<input type="checkbox" id="use_server_dns" {{ if .client_defaults.UseServerDNS }}checked{{ end }}>
|
||||
<label for="use_server_dns">
|
||||
Use server DNS
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="icheck-primary d-inline">
|
||||
<input type="checkbox" id="enabled" {{ if .client_defaults.EnableAfterCreation }}checked{{ end }}>
|
||||
<label for="enabled">
|
||||
Enable after creation
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<details>
|
||||
<summary><strong>Public and Preshared Keys</strong>
|
||||
<i class="fas fa-info-circle" data-toggle="tooltip"
|
||||
data-original-title="If you don't want to let the server generate and store the
|
||||
client's private key, you can manually specify its public and preshared key here
|
||||
. Note: QR code will not be generated">
|
||||
</i>
|
||||
</summary>
|
||||
<div class="form-group" style="margin-top: 1rem">
|
||||
<label for="client_public_key" class="control-label">
|
||||
Public Key
|
||||
</label>
|
||||
<input type="text" class="form-control" id="client_public_key" name="client_public_key" placeholder="Autogenerated" aria-invalid="false">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="client_preshared_key" class="control-label">
|
||||
Preshared Key
|
||||
</label>
|
||||
<input type="text" class="form-control" id="client_preshared_key" name="client_preshared_key" placeholder="Autogenerated - enter "-" to skip generation">
|
||||
</div>
|
||||
</details>
|
||||
<details style="margin-top: 0.5rem;">
|
||||
<summary><strong>Additional configuration</strong>
|
||||
</summary>
|
||||
<div class="form-group" style="margin-top: 0.5rem;">
|
||||
<label for="client_telegram_userid" class="control-label">Telegram userid</label>
|
||||
<input type="text" class="form-control" id="client_telegram_userid" name="client_telegram_userid">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="additional_notes" class="control-label">Notes</label>
|
||||
<textarea class="form-control" style="min-height: 6rem;" id="additional_notes" name="additional_notes" placeholder="Additional notes about this client"></textarea>
|
||||
</div>
|
||||
</details>
|
||||
</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-primary">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- /.modal-content -->
|
||||
</div>
|
||||
<!-- /.modal-dialog -->
|
||||
</div>
|
||||
<!-- /.modal -->
|
||||
|
||||
<div class="modal fade" id="modal_apply_config">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Apply Config</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Do you want to write config file and restart WireGuard server?</p>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-between">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-danger" id="apply_config_confirm">Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.modal-content -->
|
||||
</div>
|
||||
<!-- /.modal-dialog -->
|
||||
</div>
|
||||
<!-- /.modal -->
|
||||
|
||||
<!-- Content Wrapper. Contains page content -->
|
||||
<div class="content-wrapper">
|
||||
<!-- Content Header (Page header) -->
|
||||
<section class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">
|
||||
<h1>{{template "page_title" .}}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.container-fluid -->
|
||||
</section>
|
||||
|
||||
<!-- Main content -->
|
||||
{{template "page_content" .}}
|
||||
<!-- /.content -->
|
||||
</div>
|
||||
<!-- /.content-wrapper -->
|
||||
<!--
|
||||
<footer class="main-footer">
|
||||
<div class="float-right d-none d-sm-block">
|
||||
<b>Version</b> {{ .appVersion }}
|
||||
</div>
|
||||
<strong>Copyright © <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 -->
|
||||
</aside>
|
||||
<!-- /.control-sidebar -->
|
||||
</div>
|
||||
<!-- ./wrapper -->
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="{{.basePath}}/static/plugins/jquery/jquery.min.js"></script>
|
||||
<!-- Bootstrap 4 -->
|
||||
<script src="{{.basePath}}/static/plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Select2 -->
|
||||
<script src="{{.basePath}}/static/plugins/select2/js/select2.full.min.js"></script>
|
||||
<!-- jquery-validation -->
|
||||
<script src="{{.basePath}}/static/plugins/jquery-validation/jquery.validate.min.js"></script>
|
||||
<!-- Toastr -->
|
||||
<script src="{{.basePath}}/static/plugins/toastr/toastr.min.js"></script>
|
||||
<!-- Jquery Tags Input -->
|
||||
<script src="{{.basePath}}/static/plugins/jquery-tags-input/dist/jquery.tagsinput.min.js"></script>
|
||||
<!-- AdminLTE App -->
|
||||
<script src="{{.basePath}}/static/dist/js/adminlte.min.js"></script>
|
||||
<!-- Custom js -->
|
||||
<script src="{{.basePath}}/static/custom/js/helper.js"></script>
|
||||
<script>
|
||||
// initialize all tooltips
|
||||
$(function () {
|
||||
$('[data-toggle="tooltip"]').tooltip()
|
||||
})
|
||||
|
||||
$(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()
|
||||
|
||||
});
|
||||
|
||||
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({
|
||||
cache: false,
|
||||
method: 'GET',
|
||||
url: '{{.basePath}}/test-hash',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
success: function(data) {
|
||||
if (data.status) {
|
||||
$("#apply-config-button").show()
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#apply-config-button").hide()
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// populateClient function for render new client info
|
||||
// on the client page.
|
||||
function populateClient(client_id) {
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'GET',
|
||||
url: '{{.basePath}}/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();
|
||||
const email = $("#client_email").val();
|
||||
const telegram_userid = $("#client_telegram_userid").val();
|
||||
const allocated_ips = $("#client_allocated_ips").val().split(",");
|
||||
const allowed_ips = $("#client_allowed_ips").val().split(",");
|
||||
const endpoint = $("#client_endpoint").val();
|
||||
let use_server_dns = false;
|
||||
let extra_allowed_ips = [];
|
||||
|
||||
if ($("#client_extra_allowed_ips").val() !== "") {
|
||||
extra_allowed_ips = $("#client_extra_allowed_ips").val().split(",");
|
||||
}
|
||||
|
||||
|
||||
if ($("#use_server_dns").is(':checked')){
|
||||
use_server_dns = true;
|
||||
}
|
||||
|
||||
let enabled = false;
|
||||
|
||||
if ($("#enabled").is(':checked')){
|
||||
enabled = true;
|
||||
}
|
||||
const public_key = $("#client_public_key").val();
|
||||
const preshared_key = $("#client_preshared_key").val();
|
||||
|
||||
const additional_notes = $("#additional_notes").val();
|
||||
|
||||
const data = {"name": name, "email": email, "telegram_userid": telegram_userid, "allocated_ips": allocated_ips, "allowed_ips": allowed_ips,
|
||||
"extra_allowed_ips": extra_allowed_ips, "endpoint": endpoint, "use_server_dns": use_server_dns, "enabled": enabled,
|
||||
"public_key": public_key, "preshared_key": preshared_key, "additional_notes": additional_notes};
|
||||
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'POST',
|
||||
url: '{{.basePath}}/new-client',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: function(resp) {
|
||||
$("#modal_new_client").modal('hide');
|
||||
toastr.success('Created new client successfully');
|
||||
// Update the home page (clients page) after adding successfully
|
||||
if (window.location.pathname === "{{.basePath}}/") {
|
||||
populateClient(resp.id);
|
||||
}
|
||||
updateApplyConfigVisibility()
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// updateIPAllocationSuggestion function for automatically fill
|
||||
// the IP Allocation input with suggested ip addresses
|
||||
function updateIPAllocationSuggestion(forceDefault = false) {
|
||||
let subnetRange = $("#subnet_ranges").select2('val');
|
||||
|
||||
if (forceDefault || !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']);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
//Initialize Select2 Elements
|
||||
$(".select2").select2()
|
||||
|
||||
// IP Allocation tag input
|
||||
$("#client_allocated_ips").tagsInput({
|
||||
'width': '100%',
|
||||
'height': '75%',
|
||||
'interactive': true,
|
||||
'defaultText': 'Add More',
|
||||
'removeWithBackspace': true,
|
||||
'minChars': 0,
|
||||
'minInputWidth': '100%',
|
||||
'placeholderColor': '#666666'
|
||||
});
|
||||
|
||||
// AllowedIPs tag input
|
||||
$("#client_allowed_ips").tagsInput({
|
||||
'width': '100%',
|
||||
'height': '75%',
|
||||
'interactive': true,
|
||||
'defaultText': 'Add More',
|
||||
'removeWithBackspace': true,
|
||||
'minChars': 0,
|
||||
'minInputWidth': '100%',
|
||||
'placeholderColor': '#666666'
|
||||
});
|
||||
|
||||
$("#client_extra_allowed_ips").tagsInput({
|
||||
'width': '100%',
|
||||
'height': '75%',
|
||||
'interactive': true,
|
||||
'defaultText': 'Add More',
|
||||
'removeWithBackspace': true,
|
||||
'minChars': 0,
|
||||
'minInputWidth': '100%',
|
||||
'placeholderColor': '#666666'
|
||||
});
|
||||
|
||||
// New client form validation
|
||||
$(document).ready(function () {
|
||||
$.validator.setDefaults({
|
||||
submitHandler: function () {
|
||||
submitNewClient();
|
||||
}
|
||||
});
|
||||
$("#frm_new_client").validate({
|
||||
rules: {
|
||||
client_name: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
client_name: {
|
||||
required: "Please enter a name"
|
||||
},
|
||||
},
|
||||
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');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// New Client modal event
|
||||
$(document).ready(function () {
|
||||
$("#modal_new_client").on('shown.bs.modal', function (e) {
|
||||
$("#client_name").val("");
|
||||
$("#client_email").val("");
|
||||
$("#client_public_key").val("");
|
||||
$("#client_preshared_key").val("");
|
||||
$("#client_allocated_ips").importTags('');
|
||||
$("#client_extra_allowed_ips").importTags('');
|
||||
$("#client_endpoint").val('');
|
||||
$("#client_telegram_userid").val('');
|
||||
$("#additional_notes").val('');
|
||||
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
|
||||
$(document).ready(function () {
|
||||
$("#apply_config_confirm").click(function () {
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'POST',
|
||||
url: '{{.basePath}}/api/apply-wg-config',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
success: function(data) {
|
||||
updateApplyConfigVisibility()
|
||||
$("#modal_apply_config").modal('hide');
|
||||
toastr.success('Applied config successfully');
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- START: On page script -->
|
||||
{{template "bottom_js" .}}
|
||||
<!-- END: On page script -->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
{{end}}
|
||||
@@ -0,0 +1,964 @@
|
||||
{{define "title"}}
|
||||
Wireguard Clients
|
||||
{{end}}
|
||||
|
||||
{{define "top_css"}}
|
||||
<style>
|
||||
.paused-client {
|
||||
transition: transform .2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
i[class^="paused-client"]:hover { transform: scale(1.5); }
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
{{define "username"}}
|
||||
{{ .username }}
|
||||
{{end}}
|
||||
|
||||
{{define "page_title"}}
|
||||
Wireguard Clients
|
||||
{{end}}
|
||||
|
||||
{{define "page_content"}}
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<!-- <h5 class="mt-4 mb-2">Wireguard Clients</h5> -->
|
||||
<div class="row" id="client-list">
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="modal fade" id="modal_email_client">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Email Configuration</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form name="frm_email_client" id="frm_email_client">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="e_client_id" name="e_client_id">
|
||||
<div class="form-group">
|
||||
<label for="e_client_email" class="control-label">Email address</label>
|
||||
<input type="text" class="form-control" id="e_client_email" name="e_client_email">
|
||||
</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_qr_client">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">QR Code</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</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" />
|
||||
<!-- do not include FwMark in any client configs: it is INVALID. -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.modal-content -->
|
||||
</div>
|
||||
<!-- /.modal-dialog -->
|
||||
</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">
|
||||
<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="_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">
|
||||
<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">
|
||||
<label for="_client_extra_allowed_ips" class="control-label">Extra Allowed IPs</label>
|
||||
<input type="text" data-role="tagsinput" class="form-control"
|
||||
id="_client_extra_allowed_ips">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="_client_endpoint" class="control-label">Endpoint</label>
|
||||
<input type="text" class="form-control" id="_client_endpoint" name="client_endpoint">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="icheck-primary d-inline">
|
||||
<input type="checkbox" id="_use_server_dns">
|
||||
<label for="_use_server_dns">
|
||||
Use server DNS
|
||||
</label>
|
||||
</div>
|
||||
</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>
|
||||
<details>
|
||||
<summary><strong>Public and Preshared Keys</strong>
|
||||
<i class="fas fa-info-circle" data-toggle="tooltip"
|
||||
data-original-title="Update the server stored
|
||||
client Public and Preshared keys.">
|
||||
</i>
|
||||
</summary>
|
||||
<div class="form-group" style="margin-top: 1rem">
|
||||
<label for="_client_public_key" class="control-label">
|
||||
Public Key
|
||||
</label>
|
||||
<input type="text" class="form-control" id="_client_public_key" name="_client_public_key" aria-invalid="false">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="_client_preshared_key" class="control-label">
|
||||
Preshared Key
|
||||
</label>
|
||||
<input type="text" class="form-control" id="_client_preshared_key" name="_client_preshared_key">
|
||||
</div>
|
||||
</details>
|
||||
<details style="margin-top: 0.5rem;">
|
||||
<summary><strong>Additional configuration</strong>
|
||||
</summary>
|
||||
<div class="form-group" style="margin-top: 0.5rem;">
|
||||
<label for="_client_telegram_userid" class="control-label">Telegram userid</label>
|
||||
<input type="text" class="form-control" id="_client_telegram_userid" name="_client_telegram_userid">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="_additional_notes" class="control-label">Notes</label>
|
||||
<textarea class="form-control" style="min-height: 6rem;" id="_additional_notes" name="_additional_notes" placeholder="Additional notes about this client"></textarea>
|
||||
</div>
|
||||
</details>
|
||||
</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">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Disable</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
</div>
|
||||
<div class="modal-footer justify-content-between">
|
||||
<button type="button" class="btn btn-outline-dark" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-outline-dark" id="pause_client_confirm">Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.modal-content -->
|
||||
</div>
|
||||
<!-- /.modal-dialog -->
|
||||
</div>
|
||||
<!-- /.modal -->
|
||||
|
||||
<div class="modal fade" id="modal_remove_client">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content bg-danger">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Remove</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
</div>
|
||||
<div class="modal-footer justify-content-between">
|
||||
<button type="button" class="btn btn-outline-dark" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-outline-dark" id="remove_client_confirm">Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.modal-content -->
|
||||
</div>
|
||||
<!-- /.modal-dialog -->
|
||||
</div>
|
||||
<!-- /.modal -->
|
||||
{{end}}
|
||||
|
||||
{{define "bottom_js"}}
|
||||
<script>
|
||||
function populateClientList() {
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'GET',
|
||||
url: '{{.basePath}}/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({
|
||||
cache: false,
|
||||
method: 'POST',
|
||||
url: '{{.basePath}}/client/set-status',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: function (data) {
|
||||
console.log("Set client " + clientID + " status to " + status);
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resumeClient(clientID) {
|
||||
setClientStatus(clientID, true);
|
||||
const divElement = document.getElementById("paused_" + clientID);
|
||||
divElement.style.visibility = "hidden";
|
||||
updateApplyConfigVisibility()
|
||||
}
|
||||
|
||||
function pauseClient(clientID) {
|
||||
setClientStatus(clientID, false);
|
||||
const divElement = document.getElementById("paused_" + clientID);
|
||||
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>
|
||||
// load client list
|
||||
$(document).ready(function () {
|
||||
updateSearchList();
|
||||
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 () {
|
||||
$("#status-selector").val("All");
|
||||
let query = $(this).val().trim();
|
||||
$('.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();
|
||||
$(".fa-tguserid").each(function () {
|
||||
if ($(this).parent().text().trim().indexOf(query) != -1) {
|
||||
$(this).closest('.col-lg-4').show();
|
||||
}
|
||||
})
|
||||
let upperQuery = query.toUpperCase()
|
||||
$(".fa-additional_notes").each(function () {
|
||||
if ($(this).parent().text().trim().indexOf(upperQuery) != -1) {
|
||||
$(this).closest('.col-lg-4').show();
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
$("#status-selector").on('change', function () {
|
||||
$('#search-input').val("");
|
||||
switch ($("#status-selector").val()) {
|
||||
case "All":
|
||||
$('.col-lg-4').show();
|
||||
break;
|
||||
case "Enabled":
|
||||
$('.col-lg-4').hide();
|
||||
$('[id^="paused_"]').each(function () {
|
||||
if ($(this).css("visibility") === "hidden") {
|
||||
$(this).parent().parent().show();
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "Disabled":
|
||||
$('.col-lg-4').hide();
|
||||
$('[id^="paused_"]').each(function () {
|
||||
if ($(this).css("visibility") !== "hidden") {
|
||||
$(this).parent().parent().show();
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "Connected":
|
||||
$('.col-lg-4').hide();
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'GET',
|
||||
url: '{{.basePath}}/status',
|
||||
success: function (data) {
|
||||
const returnedHTML = $(data).find(".table-success").get();
|
||||
var returnedString = "";
|
||||
returnedHTML.forEach(entry => returnedString += entry.outerHTML);
|
||||
$(".fa-key").each(function () {
|
||||
if (returnedString.indexOf($(this).parent().text().trim()) != -1) {
|
||||
$(this).closest('.col-lg-4').show();
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "Disconnected":
|
||||
$('.col-lg-4').show();
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'GET',
|
||||
url: '{{.basePath}}/status',
|
||||
success: function (data) {
|
||||
const returnedHTML = $(data).find(".table-success").get();
|
||||
var returnedString = "";
|
||||
returnedHTML.forEach(entry => returnedString += entry.outerHTML);
|
||||
$(".fa-key").each(function () {
|
||||
if (returnedString.indexOf($(this).parent().text().trim()) != -1) {
|
||||
$(this).closest('.col-lg-4').hide();
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
$('.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;
|
||||
}
|
||||
});
|
||||
|
||||
// modal_pause_client modal event
|
||||
$("#modal_pause_client").on('show.bs.modal', function (event) {
|
||||
const button = $(event.relatedTarget);
|
||||
const client_id = button.data('clientid');
|
||||
const client_name = button.data('clientname');
|
||||
const modal = $(this);
|
||||
modal.find('.modal-body').text("You are about to disable client " + client_name);
|
||||
modal.find('#pause_client_confirm').val(client_id);
|
||||
})
|
||||
|
||||
// pause_client_confirm button event
|
||||
$(document).ready(function () {
|
||||
$("#pause_client_confirm").click(function () {
|
||||
const client_id = $(this).val();
|
||||
pauseClient(client_id);
|
||||
$("#modal_pause_client").modal('hide');
|
||||
});
|
||||
});
|
||||
|
||||
// modal_remove_client modal event
|
||||
$("#modal_remove_client").on('show.bs.modal', function (event) {
|
||||
const button = $(event.relatedTarget);
|
||||
const client_id = button.data('clientid');
|
||||
const client_name = button.data('clientname');
|
||||
const modal = $(this);
|
||||
modal.find('.modal-body').text("You are about to remove client " + client_name);
|
||||
modal.find('#remove_client_confirm').val(client_id);
|
||||
})
|
||||
|
||||
// remove_client_confirm button event
|
||||
$(document).ready(function () {
|
||||
$("#remove_client_confirm").click(function () {
|
||||
const client_id = $(this).val();
|
||||
const data = {"id": client_id};
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'POST',
|
||||
url: '{{.basePath}}/remove-client',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: function(data) {
|
||||
$("#modal_remove_client").modal('hide');
|
||||
toastr.success('Removed client successfully');
|
||||
const divElement = document.getElementById('client_' + client_id);
|
||||
divElement.style.display = "none";
|
||||
updateApplyConfigVisibility()
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Edit client modal event
|
||||
// This fills the modal dialogue with data from the DB when we open the edit menu
|
||||
$(document).ready(function () {
|
||||
$("#modal_edit_client").on('show.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,
|
||||
'minInputWidth': '100%',
|
||||
'placeholderColor': '#666666'
|
||||
});
|
||||
|
||||
// AllowedIPs tag input
|
||||
modal.find("#_client_allowed_ips").tagsInput({
|
||||
'width': '100%',
|
||||
'height': '75%',
|
||||
'interactive': true,
|
||||
'defaultText': 'Add More',
|
||||
'removeWithBackspace': true,
|
||||
'minChars': 0,
|
||||
'minInputWidth': '100%',
|
||||
'placeholderColor': '#666666'
|
||||
});
|
||||
|
||||
modal.find("#_client_extra_allowed_ips").tagsInput({
|
||||
'width': '100%',
|
||||
'height': '75%',
|
||||
'interactive': true,
|
||||
'defaultText': 'Add More',
|
||||
'removeWithBackspace' : true,
|
||||
'minChars': 0,
|
||||
'minInputWidth': '100%',
|
||||
'placeholderColor': '#666666'
|
||||
})
|
||||
|
||||
// update client modal data
|
||||
$.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("Edit Client " + client.name);
|
||||
modal.find("#_client_id").val(client.id);
|
||||
modal.find("#_client_telegram_userid").val(client.telegram_userid);
|
||||
modal.find("#_client_name").val(client.name);
|
||||
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('');
|
||||
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("#_client_extra_allowed_ips").importTags('');
|
||||
client.extra_allowed_ips.forEach(function (obj) {
|
||||
modal.find("#_client_extra_allowed_ips").addTag(obj);
|
||||
});
|
||||
|
||||
modal.find("#_client_endpoint").val(client.endpoint);
|
||||
|
||||
modal.find("#_use_server_dns").prop("checked", client.use_server_dns);
|
||||
modal.find("#_enabled").prop("checked", client.enabled);
|
||||
|
||||
modal.find("#_client_public_key").val(client.public_key);
|
||||
modal.find("#_client_preshared_key").val(client.preshared_key);
|
||||
|
||||
modal.find("#_additional_notes").val(client.additional_notes);
|
||||
|
||||
// handle subnet range select
|
||||
$('#_subnet_ranges').on('select2:select', function (e) {
|
||||
updateIPAllocationSuggestionExisting();
|
||||
});
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// regenerateQRCode function for regenerating QR Code adding/removing some parts of configuration because of compatibility issues with some clients
|
||||
function regenerateQRCode() {
|
||||
const client_id = $("#qr_client_id").val();
|
||||
const QRCodeImg = $("#qr_code");
|
||||
const QRCodeA = $("#qr_code_a");
|
||||
QRCodeImg.hide();
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'GET',
|
||||
url: '{{.basePath}}/api/client/' + client_id,
|
||||
data: {
|
||||
|
||||
},
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
success: function (resp) {
|
||||
const client = resp.Client;
|
||||
|
||||
$(".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);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// submitEmailClient function for sending an email with the configuration to the client
|
||||
function submitEmailClient() {
|
||||
const client_id = $("#e_client_id").val();
|
||||
const email = $("#e_client_email").val();
|
||||
const data = {"id": client_id, "email": email};
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'POST',
|
||||
url: '{{.basePath}}/email-client',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: function(resp) {
|
||||
$("#modal_email_client").modal('hide');
|
||||
toastr.success('Sent email to client successfully');
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 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');
|
||||
},
|
||||
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.
|
||||
function submitEditClient() {
|
||||
const client_id = $("#_client_id").val();
|
||||
const name = $("#_client_name").val();
|
||||
const email = $("#_client_email").val();
|
||||
const telegram_userid = $("#_client_telegram_userid").val();
|
||||
const allocated_ips = $("#_client_allocated_ips").val().split(",");
|
||||
const allowed_ips = $("#_client_allowed_ips").val().split(",");
|
||||
let use_server_dns = false;
|
||||
let extra_allowed_ips = [];
|
||||
const public_key = $("#_client_public_key").val();
|
||||
const preshared_key = $("#_client_preshared_key").val();
|
||||
|
||||
if( $("#_client_extra_allowed_ips").val() !== "" ) {
|
||||
extra_allowed_ips = $("#_client_extra_allowed_ips").val().split(",");
|
||||
}
|
||||
|
||||
const endpoint = $("#_client_endpoint").val();
|
||||
|
||||
if ($("#_use_server_dns").is(':checked')){
|
||||
use_server_dns = true;
|
||||
}
|
||||
|
||||
let enabled = false;
|
||||
|
||||
if ($("#_enabled").is(':checked')){
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
const additional_notes = $("#_additional_notes").val();
|
||||
|
||||
const data = {"id": client_id, "name": name, "email": email, "telegram_userid": telegram_userid, "allocated_ips": allocated_ips,
|
||||
"allowed_ips": allowed_ips, "extra_allowed_ips": extra_allowed_ips, "endpoint": endpoint,
|
||||
"use_server_dns": use_server_dns, "enabled": enabled, "public_key": public_key, "preshared_key": preshared_key, "additional_notes": additional_notes};
|
||||
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'POST',
|
||||
url: '{{.basePath}}/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']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// submitHandler
|
||||
function submitHandler(form) {
|
||||
const formId = $(form).attr('id');
|
||||
if (formId === "frm_edit_client") {
|
||||
submitEditClient();
|
||||
} else if (formId === "frm_email_client") {
|
||||
submitEmailClient();
|
||||
} else if (formId === "frm_telegram_client") {
|
||||
submitTelegramClient();
|
||||
}
|
||||
}
|
||||
|
||||
$("#modal_email_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("#e_client_id").val(client.id);
|
||||
modal.find("#e_client_email").val(client.email);
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#modal_qr_client").on('show.bs.modal', function (event) {
|
||||
let modal = $(this);
|
||||
const button = $(event.relatedTarget);
|
||||
const client_id = button.data('clientid');
|
||||
|
||||
modal.find("#qr_client_id").val(client_id);
|
||||
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) {
|
||||
submitHandler(form);
|
||||
}
|
||||
});
|
||||
// Edit client form validation
|
||||
$("#frm_edit_client").validate({
|
||||
rules: {
|
||||
client_name: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
client_name: {
|
||||
required: "Please enter a name"
|
||||
},
|
||||
},
|
||||
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');
|
||||
}
|
||||
});
|
||||
// Email client form validation
|
||||
$("#frm_email_client").validate({
|
||||
rules: {
|
||||
e_client_email: {
|
||||
required: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
e_client_email: {
|
||||
required: "Please enter an email"
|
||||
},
|
||||
},
|
||||
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');
|
||||
}
|
||||
});
|
||||
// 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>
|
||||
{{end}}
|
||||
@@ -0,0 +1,284 @@
|
||||
{{define "title"}}
|
||||
Global Settings
|
||||
{{end}}
|
||||
|
||||
{{define "top_css"}}
|
||||
{{end}}
|
||||
|
||||
{{define "username"}}
|
||||
{{ .username }}
|
||||
{{end}}
|
||||
|
||||
{{define "page_title"}}
|
||||
Global Settings
|
||||
{{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">Wireguard Global Settings</h3>
|
||||
</div>
|
||||
<!-- /.card-header -->
|
||||
<!-- form start -->
|
||||
<form role="form" id="frm_global_settings" name="frm_global_settings">
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label for="endpoint_address">Endpoint Address</label>
|
||||
<div class="input-group input-group">
|
||||
<input type="text" class="form-control" id="endpoint_address" name="endpoint_address" placeholder="Endpoint Address"
|
||||
value="{{ .globalSettings.EndpointAddress }}">
|
||||
<span class="input-group-append">
|
||||
<button type="button" class="btn btn-success btn-flat" data-toggle="modal"
|
||||
data-target="#modal_endpoint_address_suggestion"><i
|
||||
class="nav-icon fas fa-magic"></i> Suggest</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="dns_servers" class="control-label">DNS Servers</label>
|
||||
<input type="text" data-role="tagsinput" class="form-control" id="dns_servers" value="">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="mtu">MTU</label>
|
||||
<input type="text" class="form-control" id="mtu" name="mtu" placeholder="MTU"
|
||||
value="{{if .globalSettings.MTU}}{{ .globalSettings.MTU }}{{end}}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="persistent_keepalive">Persistent Keepalive</label>
|
||||
<input type="text" class="form-control" id="persistent_keepalive"
|
||||
name="persistent_keepalive" placeholder="Persistent Keepalive"
|
||||
value="{{if .globalSettings.PersistentKeepalive }}{{ .globalSettings.PersistentKeepalive }}{{end}}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="firewall_mark">Firewall Mark</label>
|
||||
<input type="text" class="form-control" id="firewall_mark"
|
||||
name="firewall_mark" placeholder="Firewall Mark"
|
||||
value="{{ .globalSettings.FirewallMark }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="Table">Table</label>
|
||||
<input type="text" class="form-control" id="table"
|
||||
name="table" placeholder="auto"
|
||||
value="{{ .globalSettings.Table }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config_file_path">Wireguard Config File Path</label>
|
||||
<input type="text" class="form-control" id="config_file_path"
|
||||
name="config_file_path" placeholder="E.g. /etc/wireguard/wg0.conf"
|
||||
value="{{ .globalSettings.ConfigFilePath }}">
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.card-body -->
|
||||
|
||||
<div class="card-footer">
|
||||
<button type="submit" class="btn btn-success">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- /.card -->
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card card-success">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Help</h3>
|
||||
</div>
|
||||
<!-- /.card-header -->
|
||||
<div class="card-body">
|
||||
<dl>
|
||||
<dt>1. Endpoint Address</dt>
|
||||
<dd>The public IP address of your Wireguard server that the client will connect to. Click on
|
||||
<strong>Suggest</strong> button to auto detect the public IP address of your server.</dd>
|
||||
<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>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>
|
||||
<dd>By default, WireGuard peers remain silent while they do not need to communicate,
|
||||
so peers located behind a NAT and/or firewall may be unreachable from other peers
|
||||
until they reach out to other peers themselves. Adding <code>PersistentKeepalive</code>
|
||||
can ensure that the connection remains open.</dd>
|
||||
<dd>Leave blank to omit this setting in the Client config.</dd>
|
||||
<dt>5. Firewall Mark</dt>
|
||||
<dd>Add a matching <code>fwmark</code> on all packets going out of a WireGuard non-default-route tunnel. Default value: <code>0xca6c</code></dd>
|
||||
<dt>6. Table</dt>
|
||||
<dd>Value for the <code>Table</code> setting in the wg conf file. Default value: <code>auto</code></dd>
|
||||
<dt>7. Wireguard Config File Path</dt>
|
||||
<dd>The path of your Wireguard server config file. Please make sure the parent directory
|
||||
exists and is writable.</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.card -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="modal fade" id="modal_endpoint_address_suggestion">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Endpoint Address Suggestion</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Following is the list of public and local IP addresses for your consideration.</p>
|
||||
<select id="ip_suggestion" class="select2"
|
||||
data-placeholder="Select an IP address" style="width: 100%;">
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-between">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-success" id="btn_use_ip" disabled>Use selected IP address</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.modal-content -->
|
||||
</div>
|
||||
<!-- /.modal-dialog -->
|
||||
</div>
|
||||
<!-- /.modal -->
|
||||
{{end}}
|
||||
|
||||
{{define "bottom_js"}}
|
||||
<script>
|
||||
function submitGlobalSettings() {
|
||||
const endpoint_address = $("#endpoint_address").val();
|
||||
const dns_servers = $("#dns_servers").val().split(",");
|
||||
const mtu = $("#mtu").val();
|
||||
const persistent_keepalive = $("#persistent_keepalive").val();
|
||||
const firewall_mark = $("#firewall_mark").val();
|
||||
const table = $("#table").val();
|
||||
const config_file_path = $("#config_file_path").val();
|
||||
const data = {"endpoint_address": endpoint_address, "dns_servers": dns_servers, "mtu": mtu, "persistent_keepalive": persistent_keepalive, "firewall_mark": firewall_mark, "table": table, "config_file_path": config_file_path};
|
||||
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'POST',
|
||||
url: '{{.basePath}}/global-settings',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: function(data) {
|
||||
$("#modal_new_client").modal('hide');
|
||||
toastr.success('Update global settings successfully');
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateEndpointSuggestionIP() {
|
||||
$.getJSON("{{.basePath}}/api/machine-ips", null, function(data) {
|
||||
$("#ip_suggestion option").remove();
|
||||
$.each(data, function(index, item) {
|
||||
$("#ip_suggestion").append(
|
||||
$("<option></option>")
|
||||
.text(item.ip_address + ' - ' + item.name)
|
||||
.val(item.ip_address)
|
||||
);
|
||||
});
|
||||
document.getElementById("btn_use_ip").disabled = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
// Wireguard Interface DNS server tag input
|
||||
$("#dns_servers").tagsInput({
|
||||
'width': '100%',
|
||||
'height': '75%',
|
||||
'interactive': true,
|
||||
'defaultText': 'Add More',
|
||||
'removeWithBackspace': true,
|
||||
'minChars': 0,
|
||||
'minInputWidth': '100%',
|
||||
'placeholderColor': '#666666'
|
||||
});
|
||||
|
||||
// Load DNS server to the form
|
||||
{{range .globalSettings.DNSServers}}
|
||||
$("#dns_servers").removeTag('{{.}}');
|
||||
$("#dns_servers").addTag('{{.}}');
|
||||
{{end}}
|
||||
|
||||
// Global setting form validation
|
||||
$(document).ready(function () {
|
||||
$.validator.setDefaults({
|
||||
submitHandler: function () {
|
||||
submitGlobalSettings();
|
||||
}
|
||||
});
|
||||
$("#frm_global_settings").validate({
|
||||
rules: {
|
||||
mtu: {
|
||||
digits: true,
|
||||
range: [68, 65535]
|
||||
},
|
||||
persistent_keepalive: {
|
||||
digits: true
|
||||
},
|
||||
config_file_path: {
|
||||
required: true
|
||||
},
|
||||
firewall_mark: {
|
||||
required: false
|
||||
},
|
||||
table: {
|
||||
required: false
|
||||
}
|
||||
},
|
||||
messages: {
|
||||
mtu: {
|
||||
digits: "MTU must be an integer",
|
||||
range: "MTU must be in range 68..65535"
|
||||
},
|
||||
persistent_keepalive: {
|
||||
digits: "Persistent keepalive must be an integer"
|
||||
},
|
||||
config_file_path: {
|
||||
required: "Please enter WireGuard config file path"
|
||||
}
|
||||
},
|
||||
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');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Endpoint IP suggestion modal event
|
||||
$(document).ready(function () {
|
||||
$("#modal_endpoint_address_suggestion").on('shown.bs.modal', function (e) {
|
||||
updateEndpointSuggestionIP();
|
||||
});
|
||||
});
|
||||
|
||||
// Use selected IP address from suggestion form
|
||||
$(document).ready(function () {
|
||||
$("#btn_use_ip").click(function () {
|
||||
const ip = $("#ip_suggestion").select2('val');
|
||||
$("#endpoint_address").val(ip);
|
||||
$("#modal_endpoint_address_suggestion").modal('hide');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -0,0 +1,130 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<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">
|
||||
<!-- Ionicons -->
|
||||
<link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
|
||||
<!-- icheck bootstrap -->
|
||||
<link rel="stylesheet" href="{{.basePath}}/static/plugins/icheck-bootstrap/icheck-bootstrap.min.css">
|
||||
<!-- Theme style -->
|
||||
<link rel="stylesheet" href="{{.basePath}}/static/dist/css/adminlte.min.css">
|
||||
<!-- Google Font: Source Sans Pro -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body class="hold-transition login-page">
|
||||
<div class="login-box">
|
||||
<div class="login-logo">
|
||||
<a href="https://github.com/ngoduykhanh/wireguard-ui">WireGuard UI</a>
|
||||
</div>
|
||||
<!-- /.login-logo -->
|
||||
<div class="card">
|
||||
<div class="card-body login-card-body">
|
||||
<p class="login-box-msg">Sign in to start your session</p>
|
||||
<form action="" method="post">
|
||||
<div class="input-group mb-3">
|
||||
<input id="username" type="text" class="form-control" placeholder="Username">
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">
|
||||
<span class="fas fa-envelope"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<input id="password" type="password" class="form-control" placeholder="Password">
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">
|
||||
<span class="fas fa-lock"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<div class="icheck-primary">
|
||||
<input type="checkbox" id="remember">
|
||||
<label for="remember">
|
||||
Remember Me
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
<div class="col-4">
|
||||
<button id="btn_login" type="submit" class="btn btn-primary btn-block">Sign In</button>
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
</div>
|
||||
</form>
|
||||
<div class="text-center mb-3">
|
||||
<p id="message"></p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.login-card-body -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.login-box -->
|
||||
<!-- jQuery -->
|
||||
<script src="{{.basePath}}/static/plugins/jquery/jquery.min.js"></script>
|
||||
<!-- Bootstrap 4 -->
|
||||
<script src="{{.basePath}}/static/plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- AdminLTE App -->
|
||||
<script src="{{.basePath}}/static/dist/js/adminlte.min.js"></script>
|
||||
|
||||
</body>
|
||||
<script>
|
||||
function redirectNext() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const nextURL = urlParams.get('next');
|
||||
if (nextURL && /(?:^\/[a-zA-Z_])|(?:^\/$)/.test(nextURL.trim())) {
|
||||
window.location.href = nextURL;
|
||||
} else {
|
||||
window.location.href = '/{{.basePath}}';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('form').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
$("#btn_login").trigger('click');
|
||||
});
|
||||
|
||||
$("#btn_login").click(function () {
|
||||
const username = $("#username").val();
|
||||
const password = $("#password").val();
|
||||
let rememberMe = false;
|
||||
if ($("#remember").is(':checked')){
|
||||
rememberMe = true;
|
||||
}
|
||||
const data = {"username": username, "password": password, "rememberMe": rememberMe}
|
||||
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'POST',
|
||||
url: '{{.basePath}}/login',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: function(data) {
|
||||
document.getElementById("message").innerHTML = `<p style="color:green">${data['message']}</p>`;
|
||||
// redirect after logging in successfully
|
||||
redirectNext();
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
document.getElementById("message").innerHTML = `<p style="color:#ff0000">${responseJson['message']}</p>`;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
@@ -0,0 +1,136 @@
|
||||
{{ define "title"}}
|
||||
Profile
|
||||
{{ end }}
|
||||
|
||||
{{ define "top_css"}}
|
||||
{{ end }}
|
||||
|
||||
{{ define "username"}}
|
||||
{{ .username }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "page_title"}}
|
||||
Profile
|
||||
{{ 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">Update user information</h3>
|
||||
</div>
|
||||
<!-- /.card-header -->
|
||||
<!-- form start -->
|
||||
<form role="form" id="frm_profile" name="frm_profile">
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label for="username" class="control-label">Username</label>
|
||||
<input type="text" class="form-control" name="username" id="username"
|
||||
value="">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password" class="control-label">Password</label>
|
||||
<input type="password" class="form-control" name="password" id="password"
|
||||
value="" placeholder="Leave empty to keep the password unchanged">
|
||||
</div>
|
||||
<!-- /.card-body -->
|
||||
<div class="card-footer">
|
||||
<button type="submit" class="btn btn-success" id="update">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- /.card -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
</div>
|
||||
</section>
|
||||
{{ end }}
|
||||
|
||||
{{ define "bottom_js"}}
|
||||
<script>
|
||||
{
|
||||
var previous_username;
|
||||
var admin;
|
||||
}
|
||||
$(document).ready(function () {
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'GET',
|
||||
url: '{{.basePath}}/api/user/{{.baseData.CurrentUser}}',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
success: function (resp) {
|
||||
const user = resp;
|
||||
$("#username").val(user.username);
|
||||
previous_username = user.username;
|
||||
admin = user.admin;
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function updateUserInfo() {
|
||||
const username = $("#username").val();
|
||||
const password = $("#password").val();
|
||||
const data = {"username": username, "password": password, "previous_username": previous_username, "admin":admin};
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'POST',
|
||||
url: '{{.basePath}}/update-user',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: function (data) {
|
||||
toastr.success("Updated user information successfully");
|
||||
location.reload();
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$.validator.setDefaults({
|
||||
submitHandler: function () {
|
||||
updateUserInfo();
|
||||
}
|
||||
});
|
||||
$("#frm_profile").validate({
|
||||
rules: {
|
||||
username: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
messages: {
|
||||
username: {
|
||||
required: "Please enter a username",
|
||||
}
|
||||
},
|
||||
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 }}
|
||||
@@ -0,0 +1,255 @@
|
||||
{{define "title"}}
|
||||
Wireguard Server
|
||||
{{end}}
|
||||
|
||||
{{define "top_css"}}
|
||||
{{end}}
|
||||
|
||||
{{define "username"}}
|
||||
{{ .username }}
|
||||
{{end}}
|
||||
|
||||
{{define "page_title"}}
|
||||
Wireguard Server Settings
|
||||
{{end}}
|
||||
|
||||
{{define "page_content"}}
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<!-- <h5 class="mt-4 mb-2">Wireguard Server</h5> -->
|
||||
<div class="row">
|
||||
<!-- left column -->
|
||||
<div class="col-md-6">
|
||||
<div class="card card-success">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Interface</h3>
|
||||
</div>
|
||||
<!-- /.card-header -->
|
||||
<!-- form start -->
|
||||
<form role="form" id="frm_server_interface" name="frm_server_interface">
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label for="addresses" class="control-label">Server Interface Addresses</label>
|
||||
<input type="text" data-role="tagsinput" class="form-control" id="addresses" value="">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="listen_port">Listen Port</label>
|
||||
<input type="text" class="form-control" id="listen_port" name="listen_port"
|
||||
placeholder="Listen Port" value="{{ .serverInterface.ListenPort }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="post_up">Post Up Script</label>
|
||||
<input type="text" class="form-control" id="post_up" name="post_up"
|
||||
placeholder="Post Up Script" value="{{ .serverInterface.PostUp }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="pre_down">Pre Down Script</label>
|
||||
<input type="text" class="form-control" id="pre_down" name="pre_down"
|
||||
placeholder="Pre Down Script" value="{{ .serverInterface.PreDown }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="post_down">Post Down Script</label>
|
||||
<input type="text" class="form-control" id="post_down" name="post_down"
|
||||
placeholder="Post Down Script" value="{{ .serverInterface.PostDown }}">
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.card-body -->
|
||||
|
||||
<div class="card-footer">
|
||||
<button type="submit" class="btn btn-success">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- /.card -->
|
||||
</div>
|
||||
<!-- right column -->
|
||||
<div class="col-md-6">
|
||||
<div class="card card-danger">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Key Pair</h3>
|
||||
</div>
|
||||
<!-- /.card-header -->
|
||||
<!-- form start -->
|
||||
<form role="form">
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label for="private_key">Private Key</label>
|
||||
<div class="input-group input-group">
|
||||
<input type="password" class="form-control" id="private_key" placeholder="Private Key"
|
||||
value="{{ .serverKeyPair.PrivateKey }}" disabled>
|
||||
<span class="input-group-append">
|
||||
<button type="button" class="btn btn-danger btn-flat"
|
||||
id="btn_show_private_key">Show</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="public_key">Public Key</label>
|
||||
<input type="text" class="form-control" id="public_key" placeholder="Public Key"
|
||||
value="{{ .serverKeyPair.PublicKey }}" disabled>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.card-body -->
|
||||
|
||||
<div class="card-footer">
|
||||
<button type="button" class="btn btn-danger" data-toggle="modal"
|
||||
data-target="#modal_keypair_confirmation">Generate</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- /.card -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="modal fade" id="modal_keypair_confirmation">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content bg-warning">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">KeyPair Generation</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure to generate a new key pair for the Wireguard server?<br/>
|
||||
The existing Client's peer public key need to be updated to keep the connection working.</p>
|
||||
</div>
|
||||
<div class="modal-footer justify-content-between">
|
||||
<button type="button" class="btn btn-outline-dark" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-outline-dark" id="btn_generate_confirm">Generate</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.modal-content -->
|
||||
</div>
|
||||
<!-- /.modal-dialog -->
|
||||
</div>
|
||||
<!-- /.modal -->
|
||||
{{end}}
|
||||
|
||||
{{define "bottom_js"}}
|
||||
<script>
|
||||
function submitServerInterfaceSetting() {
|
||||
const addresses = $("#addresses").val().split(",");
|
||||
const listen_port = $("#listen_port").val();
|
||||
const post_up = $("#post_up").val();
|
||||
const pre_down = $("#pre_down").val();
|
||||
const post_down = $("#post_down").val();
|
||||
const data = {"addresses": addresses, "listen_port": listen_port, "post_up": post_up, "pre_down": pre_down, "post_down": post_down};
|
||||
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'POST',
|
||||
url: '{{.basePath}}/wg-server/interfaces',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: function(data) {
|
||||
$("#modal_new_client").modal('hide');
|
||||
toastr.success('Updated Wireguard server interface addresses successfully');
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
// Wireguard Interface Addresses tag input
|
||||
$("#addresses").tagsInput({
|
||||
'width': '100%',
|
||||
// 'height': '75%',
|
||||
'interactive': true,
|
||||
'defaultText': 'Add More',
|
||||
'removeWithBackspace': true,
|
||||
'minChars': 0,
|
||||
'minInputWidth': '100%',
|
||||
'placeholderColor': '#666666'
|
||||
});
|
||||
|
||||
// Load server addresses to the form
|
||||
{{range .serverInterface.Addresses}}
|
||||
$("#addresses").removeTag('{{.}}');
|
||||
$("#addresses").addTag('{{.}}');
|
||||
{{end}}
|
||||
|
||||
// Wireguard Interface Addresses form validation
|
||||
$(document).ready(function () {
|
||||
$.validator.setDefaults({
|
||||
submitHandler: function () {
|
||||
submitServerInterfaceSetting();
|
||||
}
|
||||
});
|
||||
$("#frm_server_interface").validate({
|
||||
rules: {
|
||||
listen_port: {
|
||||
required: true,
|
||||
digits: true,
|
||||
range: [1, 65535]
|
||||
}
|
||||
},
|
||||
messages: {
|
||||
listen_port: {
|
||||
required: "Please enter a port",
|
||||
digits: "Port must be an integer",
|
||||
range: "Port must be in range 1..65535"
|
||||
}
|
||||
},
|
||||
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');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Wireguard Key Pair generation confirmation button
|
||||
$(document).ready(function () {
|
||||
$("#btn_generate_confirm").click(function () {
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'POST',
|
||||
url: '{{.basePath}}/wg-server/keypair',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
success: function(data) {
|
||||
$("#modal_keypair_confirmation").modal('hide');
|
||||
toastr.success('Generate new key pair successfully');
|
||||
// update the UI
|
||||
$("#private_key").val(data['private_key']);
|
||||
$("#public_key").val(data['public_key']);
|
||||
},
|
||||
error: function(jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Show private key button event
|
||||
$(document).ready(function () {
|
||||
$("#btn_show_private_key").click(function () {
|
||||
const privateElement = document.getElementById("private_key");
|
||||
const btnElement = document.getElementById("btn_show_private_key");
|
||||
if (privateElement.type === 'password') {
|
||||
privateElement.type = 'text';
|
||||
btnElement.innerText = 'Hide';
|
||||
} else {
|
||||
privateElement.type = 'password';
|
||||
btnElement.innerText = 'Show';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -0,0 +1,75 @@
|
||||
{{define "title"}}
|
||||
Connected Peers
|
||||
{{end}}
|
||||
|
||||
{{define "top_css"}}
|
||||
{{end}}
|
||||
|
||||
{{define "username"}}
|
||||
{{ .username }}
|
||||
{{end}}
|
||||
|
||||
{{define "page_title"}}
|
||||
Connected Peers
|
||||
{{end}}
|
||||
|
||||
{{define "page_content"}}
|
||||
<script>
|
||||
function bytesToHumanReadable(temporal) {
|
||||
const units = [" ", " K", " M", " G", " T", " P", " E", " Z", " Y"]
|
||||
let pow = 0
|
||||
|
||||
while (temporal > 1024) {
|
||||
temporal /= 1024
|
||||
pow ++
|
||||
if (pow == units.length-1) break
|
||||
}
|
||||
|
||||
return parseFloat(temporal.toFixed(2)) + units[pow]+"B"
|
||||
}
|
||||
</script>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
{{ if .error }}
|
||||
<div class="alert alert-warning" role="alert">{{.error}}</div>
|
||||
{{ end}}
|
||||
{{ range $dev := .devices }}
|
||||
<table class="table table-sm">
|
||||
<caption>List of connected peers for device with name {{ $dev.Name }} </caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<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>
|
||||
<th scope="col">Connected (Approximation)</th>
|
||||
<th scope="col">Last Handshake</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range $idx, $peer := $dev.Peers }}
|
||||
<tr {{ if $peer.Connected }} class="table-success" {{ end }}>
|
||||
<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>
|
||||
<td>{{ if $peer.Connected }}✓{{end}}</td>
|
||||
<td>{{ $peer.LastHandshakeTime.Format "2006-01-02 15:04:05 MST" }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ end }}
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
{{define "bottom_js"}}
|
||||
{{end}}
|
||||
@@ -0,0 +1,294 @@
|
||||
{{define "title"}}
|
||||
Users Settings
|
||||
{{end}}
|
||||
|
||||
{{define "top_css"}}
|
||||
{{end}}
|
||||
|
||||
{{define "username"}}
|
||||
{{ .username }}
|
||||
{{end}}
|
||||
|
||||
{{define "page_title"}}
|
||||
Users Settings
|
||||
{{end}}
|
||||
|
||||
{{define "page_content"}}
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row" id="users-list">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="modal fade" id="modal_edit_user">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Edit User</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form name="frm_edit_user" id="frm_edit_user">
|
||||
<div class="modal-body">
|
||||
<div class="form-group" style="display:none">
|
||||
<input type="text" style="display:none" class="form-control" id="_previous_user_name"
|
||||
name="_previous_user_name">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="_user_name" class="control-label">Name</label>
|
||||
<input type="text" class="form-control" id="_user_name" name="_user_name">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="_user_password" class="control-label">Password</label>
|
||||
<input type="password" class="form-control" id="_user_password" name="_user_password" value=""
|
||||
placeholder="Leave empty to keep the password unchanged">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="icheck-primary d-inline">
|
||||
<input type="checkbox" id="_admin">
|
||||
<label for="_admin">
|
||||
Admin
|
||||
</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_remove_user">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content bg-danger">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Remove</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
</div>
|
||||
<div class="modal-footer justify-content-between">
|
||||
<button type="button" class="btn btn-outline-dark" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-outline-dark" id="remove_user_confirm">Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.modal-content -->
|
||||
</div>
|
||||
<!-- /.modal-dialog -->
|
||||
</div>
|
||||
<!-- /.modal -->
|
||||
{{end}}
|
||||
|
||||
{{define "bottom_js"}}
|
||||
<script>
|
||||
function populateUsersList() {
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'GET',
|
||||
url: '{{.basePath}}/get-users',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
success: function (data) {
|
||||
renderUserList(data);
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
// load user list
|
||||
$(document).ready(function () {
|
||||
populateUsersList();
|
||||
let newUserHtml = '<div class="col-sm-2 offset-md-4" style=" text-align: right;">' +
|
||||
'<button style="" id="btn_new_user" type="button" class="btn btn-outline-primary btn-sm" ' +
|
||||
'data-toggle="modal" data-target="#modal_edit_user" data-username="">' +
|
||||
'<i class="nav-icon fas fa-plus"></i> New User</button></div>';
|
||||
$('h1').parents(".row").append(newUserHtml);
|
||||
})
|
||||
|
||||
// modal_remove_user modal event
|
||||
$("#modal_remove_user").on('show.bs.modal', function (event) {
|
||||
const button = $(event.relatedTarget);
|
||||
const user_name = button.data('username');
|
||||
const modal = $(this);
|
||||
modal.find('.modal-body').text("You are about to remove user " + user_name);
|
||||
modal.find('#remove_user_confirm').val(user_name);
|
||||
})
|
||||
|
||||
// remove_user_confirm button event
|
||||
$(document).ready(function () {
|
||||
$("#remove_user_confirm").click(function () {
|
||||
const user_name = $(this).val();
|
||||
const data = {"username": user_name};
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'POST',
|
||||
url: '{{.basePath}}/remove-user',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: function (data) {
|
||||
$("#modal_remove_user").modal('hide');
|
||||
toastr.success('Removed user successfully');
|
||||
const divElement = document.getElementById('user_' + user_name);
|
||||
divElement.style.display = "none";
|
||||
location.reload()
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Edit user modal event
|
||||
$(document).ready(function () {
|
||||
$("#modal_edit_user").on('show.bs.modal', function (event) {
|
||||
let modal = $(this);
|
||||
const button = $(event.relatedTarget);
|
||||
const user_name = button.data('username');
|
||||
|
||||
// update user modal data
|
||||
if (user_name !== "") {
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'GET',
|
||||
url: '{{.basePath}}/api/user/' + user_name,
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
success: function (resp) {
|
||||
const user = resp;
|
||||
|
||||
modal.find(".modal-title").text("Edit user " + user.username);
|
||||
modal.find("#_user_name").val(user.username);
|
||||
modal.find("#_previous_user_name").val(user.username);
|
||||
modal.find("#_user_password").val("");
|
||||
modal.find("#_user_password").prop("placeholder", "Leave empty to keep the password unchanged")
|
||||
modal.find("#_admin").prop("checked", user.admin);
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
modal.find(".modal-title").text("Add new user");
|
||||
modal.find("#_user_name").val("");
|
||||
modal.find("#_previous_user_name").val("");
|
||||
modal.find("#_user_password").val("");
|
||||
modal.find("#_user_password").prop("placeholder", "")
|
||||
modal.find("#_admin").prop("checked", false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function updateUserInfo() {
|
||||
const username = $("#_user_name").val();
|
||||
const previous_username = $("#_previous_user_name").val();
|
||||
const password = $("#_user_password").val();
|
||||
let admin = false;
|
||||
if ($("#_admin").is(':checked')) {
|
||||
admin = true;
|
||||
}
|
||||
const data = {
|
||||
"username": username,
|
||||
"password": password,
|
||||
"previous_username": previous_username,
|
||||
"admin": admin
|
||||
};
|
||||
|
||||
if (previous_username !== "") {
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'POST',
|
||||
url: '{{.basePath}}/update-user',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: function (data) {
|
||||
toastr.success("Updated user information successfully");
|
||||
location.reload();
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$.ajax({
|
||||
cache: false,
|
||||
method: 'POST',
|
||||
url: '{{.basePath}}/create-user',
|
||||
dataType: 'json',
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(data),
|
||||
success: function (data) {
|
||||
toastr.success("Created user successfully");
|
||||
location.reload();
|
||||
},
|
||||
error: function (jqXHR, exception) {
|
||||
const responseJson = jQuery.parseJSON(jqXHR.responseText);
|
||||
toastr.error(responseJson['message']);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$.validator.setDefaults({
|
||||
submitHandler: function (form) {
|
||||
updateUserInfo();
|
||||
}
|
||||
});
|
||||
// Edit user form validation
|
||||
$("#frm_edit_user").validate({
|
||||
rules: {
|
||||
_user_name: {
|
||||
required: true
|
||||
},
|
||||
_user_password: {
|
||||
required: function () {
|
||||
return $("#_previous_user_name").val() === "";
|
||||
}
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
_user_name: {
|
||||
required: "Please enter a username"
|
||||
},
|
||||
_user_password: {
|
||||
required: "Please input a password"
|
||||
},
|
||||
},
|
||||
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}}
|
||||
@@ -0,0 +1,123 @@
|
||||
{{define "title"}}
|
||||
Wake On Lan Hosts
|
||||
{{end}}
|
||||
|
||||
{{define "top_css"}}
|
||||
{{end}}
|
||||
|
||||
{{define "username"}}
|
||||
{{ .username }}
|
||||
{{end}}
|
||||
|
||||
{{define "page_title"}}
|
||||
Wake On Lan Hosts
|
||||
{{end}}
|
||||
|
||||
{{define "page_content"}}
|
||||
|
||||
<div class="modal fade" id="modal_wake_on_lan_host">
|
||||
<!-- MacAddress string `json:"MacAddress"`-->
|
||||
<!-- Name string `json:"Name"`-->
|
||||
<!-- LatestIPAddress string `json:"LatestIPAddress"`-->
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">New Wake On Lan Host</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form name="frm_wake_on_lan_host" id="frm_wake_on_lan_host">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="frm_wake_on_lan_host_old_mac_address" name="old_mac_address">
|
||||
<div class="form-group">
|
||||
<label for="frm_wake_on_lan_host_name" class="control-label">Name</label>
|
||||
<input type="text" class="form-control" id="frm_wake_on_lan_host_name" name="name">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="frm_wake_on_lan_host_mac_address" class="control-label">Mac Address</label>
|
||||
<input type="text" class="form-control" id="frm_wake_on_lan_host_mac_address"
|
||||
name="mac_address">
|
||||
</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-primary">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- /.modal-content -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modal_remove_wake_on_lan_host">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content bg-danger">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Remove</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
</div>
|
||||
<div class="modal-footer justify-content-between">
|
||||
<button type="button" class="btn btn-outline-dark" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-outline-dark" id="remove_wake_on_host_confirm">Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.modal-content -->
|
||||
</div>
|
||||
<!-- /.modal-dialog -->
|
||||
</div>
|
||||
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
{{ if .error }}
|
||||
<div class="alert alert-warning" role="alert">{{.error}}</div>
|
||||
{{ end}}
|
||||
|
||||
<div class="row">
|
||||
{{ range $idx, $host := .hosts }}
|
||||
{{- /*gotype: github.com/ngoduykhanh/wireguard-ui/model.WakeOnLanHost*/ -}}
|
||||
<div class="col-sm-4" id="{{ $host.ResolveResourceName }}">
|
||||
<div class="info-box">
|
||||
<div class="info-box-content">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-outline-success btn-sm"
|
||||
data-mac-address="{{ .MacAddress }}">Wake On
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-outline-primary btn-sm btn_modify_wake_on_lan_host"
|
||||
data-toggle="modal" data-target="#modal_wake_on_lan_host"
|
||||
data-name="{{ .Name }}" data-mac-address="{{ .MacAddress }}">Edit
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger btn-sm" data-toggle="modal"
|
||||
data-target="#modal_remove_wake_on_lan_host"
|
||||
data-mac-address="{{ .MacAddress }}">Remove
|
||||
</button>
|
||||
</div>
|
||||
<hr>
|
||||
<span class="info-box-text"><i class="fas fa-address-card"></i> <span class="name">{{ .Name }}</span></span>
|
||||
<span class="info-box-text"><i class="fas fa-ethernet"></i> <span class="mac-address">{{ .MacAddress }}</span></span>
|
||||
<span class="info-box-text"><i class="fas fa-clock"></i>
|
||||
<span class="latest-used">
|
||||
{{ if .LatestUsed }}
|
||||
{{ .LatestUsed.Format "2006-01-02T15:04:05Z07:00"}}
|
||||
{{ else }}
|
||||
Unused
|
||||
{{ end }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{end}}
|
||||
{{define "bottom_js"}}
|
||||
<script src="{{.basePath}}/static/custom/js/wake_on_lan_hosts.js"></script>
|
||||
{{end}}
|
||||
@@ -0,0 +1,33 @@
|
||||
# This file was generated using wireguard-ui (https://github.com/ngoduykhanh/wireguard-ui)
|
||||
# Please don't modify it manually, otherwise your change might get replaced.
|
||||
|
||||
# Address updated at: {{ .serverConfig.Interface.UpdatedAt }}
|
||||
# Private Key updated at: {{ .serverConfig.KeyPair.UpdatedAt }}
|
||||
[Interface]
|
||||
Address = {{$first :=true}}{{range .serverConfig.Interface.Addresses }}{{if $first}}{{$first = false}}{{else}},{{end}}{{.}}{{end}}
|
||||
ListenPort = {{ .serverConfig.Interface.ListenPort }}
|
||||
PrivateKey = {{ .serverConfig.KeyPair.PrivateKey }}
|
||||
{{if .globalSettings.MTU}}MTU = {{ .globalSettings.MTU }}{{end}}
|
||||
PostUp = {{ .serverConfig.Interface.PostUp }}
|
||||
PreDown = {{ .serverConfig.Interface.PreDown }}
|
||||
PostDown = {{ .serverConfig.Interface.PostDown }}
|
||||
Table = {{ .globalSettings.Table }}
|
||||
|
||||
{{range .clientDataList}}{{if eq .Client.Enabled true}}
|
||||
# ID: {{ .Client.ID }}
|
||||
# Name: {{ .Client.Name }}
|
||||
# Email: {{ .Client.Email }}
|
||||
# Telegram: {{ .Client.TgUserid }}
|
||||
# Created at: {{ .Client.CreatedAt }}
|
||||
# Update at: {{ .Client.UpdatedAt }}
|
||||
{{- if .Client.AdditionalNotes}}
|
||||
|
||||
# Notes:
|
||||
# {{ .Client.AdditionalNotes }}{{end}}
|
||||
[Peer]
|
||||
PublicKey = {{ .Client.PublicKey }}
|
||||
{{if .Client.PresharedKey}}PresharedKey = {{ .Client.PresharedKey }}{{end}}
|
||||
AllowedIPs = {{$first :=true}}{{range .Client.AllocatedIPs }}{{if $first}}{{$first = false}}{{else}},{{end}}{{.}}{{end}}{{range .Client.ExtraAllowedIPs }},{{.}}{{end}}
|
||||
{{if $.globalSettings.PersistentKeepalive}}PersistentKeepalive = {{ $.globalSettings.PersistentKeepalive }}{{end}}
|
||||
{{if .Client.Endpoint}}Endpoint = {{ .Client.Endpoint }}{{end}}
|
||||
{{end}}{{end}}
|
||||
Reference in New Issue
Block a user