Add project files.
27
.dockerignore
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
**/.classpath
|
||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/.editorconfig
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
**/*.pdb
|
||||||
|
LICENSE
|
||||||
|
README.md
|
63
.gitattributes
vendored
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
###############################################################################
|
||||||
|
# Set default behavior to automatically normalize line endings.
|
||||||
|
###############################################################################
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Set default behavior for command prompt diff.
|
||||||
|
#
|
||||||
|
# This is need for earlier builds of msysgit that does not have it on by
|
||||||
|
# default for csharp files.
|
||||||
|
# Note: This is only used by command line
|
||||||
|
###############################################################################
|
||||||
|
#*.cs diff=csharp
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Set the merge driver for project and solution files
|
||||||
|
#
|
||||||
|
# Merging from the command prompt will add diff markers to the files if there
|
||||||
|
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||||
|
# the diff markers are never inserted). Diff markers may cause the following
|
||||||
|
# file extensions to fail to load in VS. An alternative would be to treat
|
||||||
|
# these files as binary and thus will always conflict and require user
|
||||||
|
# intervention with every merge. To do so, just uncomment the entries below
|
||||||
|
###############################################################################
|
||||||
|
#*.sln merge=binary
|
||||||
|
#*.csproj merge=binary
|
||||||
|
#*.vbproj merge=binary
|
||||||
|
#*.vcxproj merge=binary
|
||||||
|
#*.vcproj merge=binary
|
||||||
|
#*.dbproj merge=binary
|
||||||
|
#*.fsproj merge=binary
|
||||||
|
#*.lsproj merge=binary
|
||||||
|
#*.wixproj merge=binary
|
||||||
|
#*.modelproj merge=binary
|
||||||
|
#*.sqlproj merge=binary
|
||||||
|
#*.wwaproj merge=binary
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# behavior for image files
|
||||||
|
#
|
||||||
|
# image files are treated as binary by default.
|
||||||
|
###############################################################################
|
||||||
|
#*.jpg binary
|
||||||
|
#*.png binary
|
||||||
|
#*.gif binary
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# diff behavior for common document formats
|
||||||
|
#
|
||||||
|
# Convert binary document formats to text before diffing them. This feature
|
||||||
|
# is only available from the command line. Turn it on by uncommenting the
|
||||||
|
# entries below.
|
||||||
|
###############################################################################
|
||||||
|
#*.doc diff=astextplain
|
||||||
|
#*.DOC diff=astextplain
|
||||||
|
#*.docx diff=astextplain
|
||||||
|
#*.DOCX diff=astextplain
|
||||||
|
#*.dot diff=astextplain
|
||||||
|
#*.DOT diff=astextplain
|
||||||
|
#*.pdf diff=astextplain
|
||||||
|
#*.PDF diff=astextplain
|
||||||
|
#*.rtf diff=astextplain
|
||||||
|
#*.RTF diff=astextplain
|
368
.gitignore
vendored
Normal file
|
@ -0,0 +1,368 @@
|
||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Oo]ut/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
coverage*.json
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.info
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
.ionide/
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
FodyWeavers.xsd
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
[Pp]ublished/
|
||||||
|
*.tar
|
||||||
|
.editorconfig
|
447
APIHandler.cs
Normal file
|
@ -0,0 +1,447 @@
|
||||||
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
|
using MTWireGuard.Models;
|
||||||
|
using MTWireGuard.Models.Mikrotik;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MTWireGuard
|
||||||
|
{
|
||||||
|
public static class APIHandler
|
||||||
|
{
|
||||||
|
private static readonly string MT_IP = Environment.GetEnvironmentVariable("MT_IP");
|
||||||
|
private static readonly string MT_USER = Environment.GetEnvironmentVariable("MT_USER");
|
||||||
|
private static readonly string MT_PASS = Environment.GetEnvironmentVariable("MT_PASS");
|
||||||
|
|
||||||
|
public static async Task<List<Log>> GetLogsAsync()
|
||||||
|
{
|
||||||
|
string json = await SendGetRequestAsync("log");
|
||||||
|
return JsonConvert.DeserializeObject<List<Log>>(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<List<WGServer>> GetServersAsync()
|
||||||
|
{
|
||||||
|
string json = await SendGetRequestAsync("interface/wireguard");
|
||||||
|
return JsonConvert.DeserializeObject<List<WGServer>>(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<WGServer> GetServer(string Name)
|
||||||
|
{
|
||||||
|
var servers = await GetServersAsync();
|
||||||
|
return servers.Find(s => s.Name == Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<List<ServerTraffic>> GetServersTraffic()
|
||||||
|
{
|
||||||
|
var json = await SendPostRequestAsync("interface", "{\"stats\", {\".proplist\":\"name, type, rx-byte, tx-byte\"}}");
|
||||||
|
return JsonConvert.DeserializeObject<List<ServerTraffic>>(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<List<WGPeer>> GetUsersAsync()
|
||||||
|
{
|
||||||
|
using var db = new DBContext();
|
||||||
|
string json = await SendGetRequestAsync("interface/wireguard/peers");
|
||||||
|
return JsonConvert.DeserializeObject<List<WGPeer>>(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<WGPeer> GetUser(string id)
|
||||||
|
{
|
||||||
|
var users = await GetUsersAsync();
|
||||||
|
return users.Find(u => u.Id == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<MTInfo> GetInfo()
|
||||||
|
{
|
||||||
|
var json = await SendGetRequestAsync("system/resource");
|
||||||
|
return JsonConvert.DeserializeObject<MTInfo>(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<MTIdentity> GetName()
|
||||||
|
{
|
||||||
|
var json = await SendGetRequestAsync("system/identity");
|
||||||
|
return JsonConvert.DeserializeObject<MTIdentity>(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<LoginStatus> TryConnectAsync()
|
||||||
|
{
|
||||||
|
var connection = await SendGetRequestAsync("", true);
|
||||||
|
return JsonConvert.DeserializeObject<LoginStatus>(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<List<ActiveUser>> GetActiveSessions()
|
||||||
|
{
|
||||||
|
var json = await SendGetRequestAsync("user/active?name=" + MT_USER);
|
||||||
|
return JsonConvert.DeserializeObject<List<ActiveUser>>(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<List<Job>> GetJobs()
|
||||||
|
{
|
||||||
|
var json = await SendGetRequestAsync("system/script/job");
|
||||||
|
return JsonConvert.DeserializeObject<List<Job>>(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<string> KillJob(string JobID)
|
||||||
|
{
|
||||||
|
return await SendDeleteRequestAsync("system/script/job/" + JobID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<CreationStatus> CreateServer(WGServerCreateModel server)
|
||||||
|
{
|
||||||
|
var json = await SendPutRequestAsync("interface/wireguard", server);
|
||||||
|
var obj = JObject.Parse(json);
|
||||||
|
bool success = false;
|
||||||
|
string code = string.Empty, message = string.Empty, detail = string.Empty;
|
||||||
|
if (obj.TryGetValue(".id", out var Id))
|
||||||
|
{
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
else if (obj.TryGetValue("error", out var Error))
|
||||||
|
{
|
||||||
|
var error = JsonConvert.DeserializeObject<CreationStatus>(json);
|
||||||
|
success = false;
|
||||||
|
code = Error.Value<string>();
|
||||||
|
message = error.Message;
|
||||||
|
detail = error.Detail;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
message = "Failed";
|
||||||
|
detail = json;
|
||||||
|
};
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Code = code,
|
||||||
|
Message = message,
|
||||||
|
Detail = detail,
|
||||||
|
Success = success
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<CreationStatus> CreateUser(WGPeerCreateModel user)
|
||||||
|
{
|
||||||
|
var jsonData = JObject.Parse(JsonConvert.SerializeObject(user, new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
NullValueHandling = NullValueHandling.Ignore,
|
||||||
|
DefaultValueHandling = DefaultValueHandling.Ignore
|
||||||
|
}));
|
||||||
|
|
||||||
|
var json = await SendPutRequestAsync("interface/wireguard/peers", jsonData);
|
||||||
|
var obj = JObject.Parse(json);
|
||||||
|
bool success = false;
|
||||||
|
string code = string.Empty, message = string.Empty, detail = string.Empty;
|
||||||
|
WGPeer peer = null;
|
||||||
|
if (obj.TryGetValue(".id", out var Id))
|
||||||
|
{
|
||||||
|
success = true;
|
||||||
|
peer = JsonConvert.DeserializeObject<WGPeer>(json);
|
||||||
|
}
|
||||||
|
else if (obj.TryGetValue("error", out var Error))
|
||||||
|
{
|
||||||
|
var error = JsonConvert.DeserializeObject<CreationStatus>(json);
|
||||||
|
success = false;
|
||||||
|
code = Error.Value<string>();
|
||||||
|
message = error.Message;
|
||||||
|
detail = error.Detail;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
message = "Failed";
|
||||||
|
detail = json;
|
||||||
|
};
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Code = code,
|
||||||
|
Message = message,
|
||||||
|
Detail = detail,
|
||||||
|
Success = success,
|
||||||
|
Item = peer ?? null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<CreationStatus> UpdateServer(WGServerUpdateModel server)
|
||||||
|
{
|
||||||
|
var serverJson = JObject.Parse(JsonConvert.SerializeObject(server, new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
NullValueHandling = NullValueHandling.Ignore,
|
||||||
|
DefaultValueHandling = DefaultValueHandling.Ignore
|
||||||
|
}));
|
||||||
|
var json = await SendPatchRequestAsync($"interface/wireguard/{server.Id}", serverJson);
|
||||||
|
var obj = JObject.Parse(json);
|
||||||
|
bool success = false;
|
||||||
|
string code = string.Empty, message = string.Empty, detail = string.Empty;
|
||||||
|
WGServer srv = null;
|
||||||
|
if (obj.TryGetValue(".id", out var Id))
|
||||||
|
{
|
||||||
|
success = true;
|
||||||
|
srv = JsonConvert.DeserializeObject<WGServer>(json);
|
||||||
|
}
|
||||||
|
else if (obj.TryGetValue("error", out var Error))
|
||||||
|
{
|
||||||
|
var error = JsonConvert.DeserializeObject<CreationStatus>(json);
|
||||||
|
success = false;
|
||||||
|
code = Error.Value<string>();
|
||||||
|
message = error.Message;
|
||||||
|
detail = error.Detail;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
message = "Failed";
|
||||||
|
detail = json;
|
||||||
|
};
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Code = code,
|
||||||
|
Message = message,
|
||||||
|
Detail = detail,
|
||||||
|
Success = success,
|
||||||
|
Item = srv ?? null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<CreationStatus> UpdateUser(WGPeerUpdateModel user)
|
||||||
|
{
|
||||||
|
var userJson = JObject.Parse(JsonConvert.SerializeObject(user, new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
NullValueHandling = NullValueHandling.Ignore,
|
||||||
|
DefaultValueHandling = DefaultValueHandling.Ignore
|
||||||
|
}));
|
||||||
|
var json = await SendPatchRequestAsync($"interface/wireguard/peers/{user.Id}", userJson);
|
||||||
|
var obj = JObject.Parse(json);
|
||||||
|
bool success = false;
|
||||||
|
string code = string.Empty, message = string.Empty, detail = string.Empty;
|
||||||
|
WGPeer peer = null;
|
||||||
|
if (obj.TryGetValue(".id", out var Id))
|
||||||
|
{
|
||||||
|
success = true;
|
||||||
|
peer = JsonConvert.DeserializeObject<WGPeer>(json);
|
||||||
|
}
|
||||||
|
else if (obj.TryGetValue("error", out var Error))
|
||||||
|
{
|
||||||
|
var error = JsonConvert.DeserializeObject<CreationStatus>(json);
|
||||||
|
success = false;
|
||||||
|
code = Error.Value<string>();
|
||||||
|
message = error.Message;
|
||||||
|
detail = error.Detail;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
message = "Failed";
|
||||||
|
detail = json;
|
||||||
|
};
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Code = code,
|
||||||
|
Message = message,
|
||||||
|
Detail = detail,
|
||||||
|
Success = success,
|
||||||
|
Item = peer ?? null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<CreationStatus> SetServerEnabled(WGEnability enability)
|
||||||
|
{
|
||||||
|
var json = await SendPatchRequestAsync($"interface/wireguard/{enability.ID}", new { disabled = enability.Disabled });
|
||||||
|
var obj = JObject.Parse(json);
|
||||||
|
bool success = false;
|
||||||
|
string code = string.Empty, message = string.Empty, detail = string.Empty;
|
||||||
|
WGPeer peer = null;
|
||||||
|
if (obj.TryGetValue(".id", out var Id))
|
||||||
|
{
|
||||||
|
success = true;
|
||||||
|
peer = JsonConvert.DeserializeObject<WGPeer>(json);
|
||||||
|
}
|
||||||
|
else if (obj.TryGetValue("error", out var Error))
|
||||||
|
{
|
||||||
|
var error = JsonConvert.DeserializeObject<CreationStatus>(json);
|
||||||
|
success = false;
|
||||||
|
code = Error.Value<string>();
|
||||||
|
message = error.Message;
|
||||||
|
detail = error.Detail;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
message = "Failed";
|
||||||
|
detail = json;
|
||||||
|
};
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Code = code,
|
||||||
|
Message = message,
|
||||||
|
Detail = detail,
|
||||||
|
Success = success,
|
||||||
|
Item = peer ?? null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<CreationStatus> SetUserEnabled(WGEnability enability)
|
||||||
|
{
|
||||||
|
var json = await SendPatchRequestAsync($"interface/wireguard/peers/{enability.ID}", new { disabled = enability.Disabled });
|
||||||
|
var obj = JObject.Parse(json);
|
||||||
|
bool success = false;
|
||||||
|
string code = string.Empty, message = string.Empty, detail = string.Empty;
|
||||||
|
WGPeer peer = null;
|
||||||
|
if (obj.TryGetValue(".id", out var Id))
|
||||||
|
{
|
||||||
|
success = true;
|
||||||
|
peer = JsonConvert.DeserializeObject<WGPeer>(json);
|
||||||
|
}
|
||||||
|
else if (obj.TryGetValue("error", out var Error))
|
||||||
|
{
|
||||||
|
var error = JsonConvert.DeserializeObject<CreationStatus>(json);
|
||||||
|
success = false;
|
||||||
|
code = Error.Value<string>();
|
||||||
|
message = error.Message;
|
||||||
|
detail = error.Detail;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
message = "Failed";
|
||||||
|
detail = json;
|
||||||
|
};
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Code = code,
|
||||||
|
Message = message,
|
||||||
|
Detail = detail,
|
||||||
|
Success = success,
|
||||||
|
Item = peer ?? null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<CreationStatus> DeleteServer(string id)
|
||||||
|
{
|
||||||
|
var json = await SendDeleteRequestAsync("interface/wireguard/" + id);
|
||||||
|
if (string.IsNullOrWhiteSpace(json))
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Item = json
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<CreationStatus> DeleteUser(string id)
|
||||||
|
{
|
||||||
|
var json = await SendDeleteRequestAsync("interface/wireguard/peers/" + id);
|
||||||
|
if (string.IsNullOrWhiteSpace(json))
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Item = json
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<string> GetTrafficSpeed()
|
||||||
|
{
|
||||||
|
return await SendPostRequestAsync("interface/monitor-traffic", "{\"interface\":\"ether1\",\"duration\":\"3s\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> SendGetRequestAsync(string URL, bool IsTest = false)
|
||||||
|
{
|
||||||
|
HttpClientHandler handler = new()
|
||||||
|
{
|
||||||
|
ServerCertificateCustomValidationCallback = delegate { return true; }
|
||||||
|
};
|
||||||
|
using HttpClient httpClient = new(handler);
|
||||||
|
using var request = new HttpRequestMessage(new HttpMethod("GET"), $"https://{MT_IP}/rest/{URL}");
|
||||||
|
string base64authorization = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{MT_USER}:{MT_PASS}"));
|
||||||
|
if (!IsTest) request.Headers.TryAddWithoutValidation("Authorization", $"Basic {base64authorization}");
|
||||||
|
|
||||||
|
HttpResponseMessage response = await httpClient.SendAsync(request);
|
||||||
|
return await response.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> SendPostRequestAsync(string URL, string Data)
|
||||||
|
{
|
||||||
|
HttpClientHandler handler = new()
|
||||||
|
{
|
||||||
|
ServerCertificateCustomValidationCallback = (requestMessage, certificate, chain, policyErrors) => true
|
||||||
|
};
|
||||||
|
using HttpClient httpClient = new(handler);
|
||||||
|
using var request = new HttpRequestMessage(new HttpMethod("GET"), $"https://{MT_IP}/rest/{URL}");
|
||||||
|
string base64authorization = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{MT_USER}:{MT_PASS}"));
|
||||||
|
request.Headers.TryAddWithoutValidation("Authorization", $"Basic {base64authorization}");
|
||||||
|
|
||||||
|
request.Content = new StringContent(Data);
|
||||||
|
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
|
||||||
|
|
||||||
|
HttpResponseMessage response = await httpClient.SendAsync(request);
|
||||||
|
return await response.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> SendDeleteRequestAsync(string URL)
|
||||||
|
{
|
||||||
|
HttpClientHandler handler = new()
|
||||||
|
{
|
||||||
|
ServerCertificateCustomValidationCallback = (requestMessage, certificate, chain, policyErrors) => true
|
||||||
|
};
|
||||||
|
using HttpClient httpClient = new(handler);
|
||||||
|
using var request = new HttpRequestMessage(new HttpMethod("DELETE"), $"https://{MT_IP}/rest/{URL}");
|
||||||
|
string base64authorization = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{MT_USER}:{MT_PASS}"));
|
||||||
|
request.Headers.TryAddWithoutValidation("Authorization", $"Basic {base64authorization}");
|
||||||
|
|
||||||
|
HttpResponseMessage response = await httpClient.SendAsync(request);
|
||||||
|
return await response.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> SendPutRequestAsync(string URL, object Data)
|
||||||
|
{
|
||||||
|
HttpClientHandler handler = new()
|
||||||
|
{
|
||||||
|
ServerCertificateCustomValidationCallback = (requestMessage, certificate, chain, policyErrors) => true
|
||||||
|
};
|
||||||
|
using HttpClient httpClient = new(handler);
|
||||||
|
using var request = new HttpRequestMessage(new HttpMethod("PUT"), $"https://{MT_IP}/rest/{URL}");
|
||||||
|
string base64authorization = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{MT_USER}:{MT_PASS}"));
|
||||||
|
request.Headers.TryAddWithoutValidation("Authorization", $"Basic {base64authorization}");
|
||||||
|
|
||||||
|
request.Content = new StringContent(JsonConvert.SerializeObject(Data));
|
||||||
|
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
|
||||||
|
|
||||||
|
HttpResponseMessage response = await httpClient.SendAsync(request);
|
||||||
|
return await response.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> SendPatchRequestAsync(string URL, object Data)
|
||||||
|
{
|
||||||
|
HttpClientHandler handler = new()
|
||||||
|
{
|
||||||
|
ServerCertificateCustomValidationCallback = (requestMessage, certificate, chain, policyErrors) => true
|
||||||
|
};
|
||||||
|
using HttpClient httpClient = new(handler);
|
||||||
|
using var request = new HttpRequestMessage(new HttpMethod("PATCH"), $"https://{MT_IP}/rest/{URL}");
|
||||||
|
string base64authorization = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{MT_USER}:{MT_PASS}"));
|
||||||
|
request.Headers.TryAddWithoutValidation("Authorization", $"Basic {base64authorization}");
|
||||||
|
|
||||||
|
request.Content = new StringContent(JsonConvert.SerializeObject(Data));
|
||||||
|
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
|
||||||
|
|
||||||
|
HttpResponseMessage response = await httpClient.SendAsync(request);
|
||||||
|
return await response.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
DBContext.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using MTWireGuard.Models.Mikrotik;
|
||||||
|
|
||||||
|
namespace MTWireGuard
|
||||||
|
{
|
||||||
|
public class DBContext : DbContext
|
||||||
|
{
|
||||||
|
public DbSet<WGPeerDBModel> Users { get; set; }
|
||||||
|
|
||||||
|
public string DbPath { get; }
|
||||||
|
|
||||||
|
public DBContext()
|
||||||
|
{
|
||||||
|
DbPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "MikrotikWireguard.db");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||||
|
=> options.UseSqlite($"Data Source={DbPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
6
Dockerfile
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine AS runtime
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 80
|
||||||
|
EXPOSE 443
|
||||||
|
COPY published/ ./
|
||||||
|
ENTRYPOINT ["dotnet", "MTWireGuard.dll"]
|
88
Helper.cs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
using MTWireGuard.Models.Mikrotik;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MTWireGuard
|
||||||
|
{
|
||||||
|
public class Helper
|
||||||
|
{
|
||||||
|
public static readonly string[] UpperCaseTopics =
|
||||||
|
{
|
||||||
|
"dhcp",
|
||||||
|
"ppp",
|
||||||
|
"l2tp",
|
||||||
|
"pptp",
|
||||||
|
"sstp"
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly string[] SizeSuffixes =
|
||||||
|
{ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
|
||||||
|
public static string ConvertByteSize(long value, int decimalPlaces = 2)
|
||||||
|
{
|
||||||
|
if (decimalPlaces < 0) { throw new ArgumentOutOfRangeException("decimalPlaces"); }
|
||||||
|
if (value < 0) { return "-" + ConvertByteSize(-value, decimalPlaces); }
|
||||||
|
if (value == 0) { return "0"; }
|
||||||
|
|
||||||
|
// mag is 0 for bytes, 1 for KB, 2, for MB, etc.
|
||||||
|
int mag = (int)Math.Log(value, 1024);
|
||||||
|
|
||||||
|
// 1L << (mag * 10) == 2 ^ (10 * mag)
|
||||||
|
// [i.e. the number of bytes in the unit corresponding to mag]
|
||||||
|
decimal adjustedSize = (decimal)value / (1L << (mag * 10));
|
||||||
|
|
||||||
|
// make adjustment when the value is large enough that
|
||||||
|
// it would round up to 1000 or more
|
||||||
|
if (Math.Round(adjustedSize, decimalPlaces) >= 1000)
|
||||||
|
{
|
||||||
|
mag += 1;
|
||||||
|
adjustedSize /= 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Format("{0:n" + decimalPlaces + "} {1}",
|
||||||
|
adjustedSize,
|
||||||
|
SizeSuffixes[mag]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StringCompression
|
||||||
|
{
|
||||||
|
public static byte[] Compress(string str)
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(str);
|
||||||
|
|
||||||
|
using MemoryStream msi = new(bytes);
|
||||||
|
using MemoryStream mso = new();
|
||||||
|
using (GZipStream gs = new(mso, CompressionMode.Compress))
|
||||||
|
{
|
||||||
|
msi.CopyTo(gs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mso.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Decompress(byte[] zip)
|
||||||
|
{
|
||||||
|
using MemoryStream msi = new(zip);
|
||||||
|
using MemoryStream mso = new();
|
||||||
|
using (GZipStream gs = new(msi, CompressionMode.Decompress))
|
||||||
|
{
|
||||||
|
gs.CopyTo(mso);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Encoding.UTF8.GetString(mso.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StringExtensions
|
||||||
|
{
|
||||||
|
public static string FirstCharToUpper(this string input) =>
|
||||||
|
input switch
|
||||||
|
{
|
||||||
|
null => throw new ArgumentNullException(nameof(input)),
|
||||||
|
"" => throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input)),
|
||||||
|
_ => string.Concat(input[0].ToString().ToUpper(), input.AsSpan(1))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 MTWireguard
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
239
MTAPIHandler.cs
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
using MTWireGuard.Models;
|
||||||
|
using MTWireGuard.Models.Mikrotik;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database;
|
||||||
|
|
||||||
|
namespace MTWireGuard
|
||||||
|
{
|
||||||
|
public static class MTAPIHandler
|
||||||
|
{
|
||||||
|
private static readonly string MT_IP = Environment.GetEnvironmentVariable("MT_IP");
|
||||||
|
private static readonly string MT_USER = Environment.GetEnvironmentVariable("MT_USER");
|
||||||
|
private static readonly string MT_PASS = Environment.GetEnvironmentVariable("MT_PASS");
|
||||||
|
|
||||||
|
public static async Task<List<LogViewModel>> GetLogsAsync()
|
||||||
|
{
|
||||||
|
string json = await SendGetRequestAsync("log");
|
||||||
|
var model = JsonConvert.DeserializeObject<List<Log>>(json);
|
||||||
|
return model.Select(x => new LogViewModel()
|
||||||
|
{
|
||||||
|
Id = Convert.ToUInt64(x.Id[1..], 16),
|
||||||
|
Message = x.Message,
|
||||||
|
Time = x.Time,
|
||||||
|
Topics = x.Topics.Split(',').Select(t => t = Helper.UpperCaseTopics.Contains(t) ? t.ToUpper() : t.FirstCharToUpper()).ToList()
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<List<WGServerViewModel>> GetServersAsync()
|
||||||
|
{
|
||||||
|
string json = await SendGetRequestAsync("interface/wireguard");
|
||||||
|
var model = JsonConvert.DeserializeObject<List<WGServer>>(json);
|
||||||
|
return model.Select(x => new WGServerViewModel()
|
||||||
|
{
|
||||||
|
Id = Convert.ToInt32(x.Id[1..], 16),
|
||||||
|
Name = x.Name,
|
||||||
|
ListenPort = Convert.ToUInt16(x.ListenPort),
|
||||||
|
MTU = Convert.ToUInt16(x.MTU),
|
||||||
|
Running = x.Running,
|
||||||
|
IsEnabled = !x.Disabled,
|
||||||
|
PublicKey = x.PublicKey,
|
||||||
|
PrivateKey = x.PrivateKey
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<WGServerViewModel> GetServer(string Name)
|
||||||
|
{
|
||||||
|
var servers = await GetServersAsync();
|
||||||
|
return servers.Find(s => s.Name == Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<List<ServerTraffic>> GetServersTraffic()
|
||||||
|
{
|
||||||
|
var json = await SendPostRequestAsync("interface", "{\"stats\", {\".proplist\":\"name,rx-byte, tx-byte\"}}");
|
||||||
|
List<ServerTraffic> traffic = JsonConvert.DeserializeObject<List<ServerTraffic>>(json);
|
||||||
|
return traffic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<List<WGPeerViewModel>> GetUsersAsync()
|
||||||
|
{
|
||||||
|
using var db = new DBContext();
|
||||||
|
string json = await SendGetRequestAsync("interface/wireguard/peers");
|
||||||
|
List<WGPeer> apiUsers = JsonConvert.DeserializeObject<List<WGPeer>>(json);
|
||||||
|
List<WGPeerDBModel> dbUsers = db.Users.ToList();
|
||||||
|
Dictionary<int, bool> differences = new();
|
||||||
|
// Start Checking DB and Router sync
|
||||||
|
foreach (var apiUser in apiUsers)
|
||||||
|
{
|
||||||
|
var id = Convert.ToInt32(apiUser.Id[1..], 16);
|
||||||
|
var dbUser = dbUsers.Find(u => u.Id == id);
|
||||||
|
if (dbUser == null)
|
||||||
|
{
|
||||||
|
differences.Add(id, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string publickey = apiUser.PublicKey;
|
||||||
|
bool publicKeyDifferent = dbUser.PublicKey != publickey;
|
||||||
|
bool noPrivateKey = string.IsNullOrWhiteSpace(dbUser.PrivateKey);
|
||||||
|
differences.Add(id, publicKeyDifferent | noPrivateKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// End Checking
|
||||||
|
return apiUsers.Select(x => new WGPeerViewModel()
|
||||||
|
{
|
||||||
|
Id = Convert.ToInt32(x.Id[1..], 16),
|
||||||
|
Name = (dbUsers.Find(u => u.Id == Convert.ToInt32(x.Id[1..], 16)) != null) ? dbUsers.Find(u => u.Id == Convert.ToInt32(x.Id[1..], 16)).Name : "",
|
||||||
|
Address = x.AllowedAddress,
|
||||||
|
CurrentAddress = $"{x.CurrentEndpointAddress}:{x.CurrentEndpointPort}",
|
||||||
|
Interface = x.Interface,
|
||||||
|
IsEnabled = !x.Disabled,
|
||||||
|
PublicKey = x.PublicKey,
|
||||||
|
Download = x.RX,
|
||||||
|
Upload = x.TX,
|
||||||
|
IsDifferent = differences[Convert.ToInt32(x.Id[1..], 16)]
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<WGPeerViewModel> GetUser(int id)
|
||||||
|
{
|
||||||
|
var users = await GetUsersAsync();
|
||||||
|
return users.Find(u => u.Id == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<string> GetIPAddress()
|
||||||
|
{
|
||||||
|
var json = await SendGetRequestAsync("ip/address?interface=ether1&.proplist=address");
|
||||||
|
var IP = JsonConvert.DeserializeObject<List<EtherIP>>(json).FirstOrDefault();
|
||||||
|
var address = IP.Address[..IP.Address.IndexOf('/')];
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<MTInfoViewModel> GetInfo()
|
||||||
|
{
|
||||||
|
var json = await SendGetRequestAsync("system/resource");
|
||||||
|
var model = JsonConvert.DeserializeObject<MTInfo>(json);
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Architecture = model.ArchitectureName,
|
||||||
|
BoardName = model.BoardName,
|
||||||
|
CPU = model.CPU,
|
||||||
|
CPUCount = Convert.ToByte(model.CPUCount),
|
||||||
|
CPUFrequency = Convert.ToInt16(model.CPUFrequency),
|
||||||
|
CPULoad = Convert.ToByte(model.CPULoad),
|
||||||
|
TotalHDDBytes = Convert.ToInt64(model.TotalHDDSpace),
|
||||||
|
FreeHDDBytes = Convert.ToInt64(model.FreeHDDSpace),
|
||||||
|
UsedHDDBytes = Convert.ToInt64(model.TotalHDDSpace) - Convert.ToInt64(model.FreeHDDSpace),
|
||||||
|
FreeHDDPercentage = (byte)(Convert.ToInt64(model.FreeHDDSpace) * 100 / Convert.ToInt64(model.TotalHDDSpace)),
|
||||||
|
TotalRAMBytes = Convert.ToInt64(model.TotalMemory),
|
||||||
|
FreeRAMBytes = Convert.ToInt64(model.FreeMemory),
|
||||||
|
UsedRAMBytes = Convert.ToInt64(model.TotalMemory) - Convert.ToInt64(model.FreeMemory),
|
||||||
|
FreeRAMPercentage = (byte)(Convert.ToInt64(model.FreeMemory) * 100 / Convert.ToInt64(model.TotalMemory)),
|
||||||
|
TotalHDD = Helper.ConvertByteSize(Convert.ToInt64(model.TotalHDDSpace)),
|
||||||
|
FreeHDD = Helper.ConvertByteSize(Convert.ToInt64(model.FreeHDDSpace)),
|
||||||
|
UsedHDD = Helper.ConvertByteSize(Convert.ToInt64(model.TotalHDDSpace) - Convert.ToInt64(model.FreeHDDSpace)),
|
||||||
|
TotalRAM = Helper.ConvertByteSize(Convert.ToInt64(model.TotalMemory)),
|
||||||
|
FreeRAM = Helper.ConvertByteSize(Convert.ToInt64(model.FreeMemory)),
|
||||||
|
UsedRAM = Helper.ConvertByteSize(Convert.ToInt64(model.TotalMemory) - Convert.ToInt64(model.FreeMemory)),
|
||||||
|
UPTime = model.Uptime.
|
||||||
|
Replace('d', ' ').
|
||||||
|
Replace('h', ':').
|
||||||
|
Replace('m', ':').
|
||||||
|
Replace("s", ""),
|
||||||
|
Platform = model.Platform,
|
||||||
|
Version = model.Version
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<bool> TryConnectAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var connection = await SendGetRequestAsync("", true);
|
||||||
|
var status = JsonConvert.DeserializeObject<LoginStatus>(connection);
|
||||||
|
if ((status.Error == 400 && status.Message == "Bad Request") || (status.Error == 401 && status.Message == "Unauthorized"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw new($"[{status.Error}] Login failed, {status.Message}.<br>Enter router username/password in environment variables (MT_USER/MT_PASS).");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<string> GetCurrentSessionID()
|
||||||
|
{
|
||||||
|
var sessionJson = await SendGetRequestAsync("user/active?name=admin&via=api&.proplist=when");
|
||||||
|
JArray session = JArray.Parse(sessionJson);
|
||||||
|
var jobsJson = await SendGetRequestAsync("system/script/job?type=api-login&started=" + session.FirstOrDefault()["when"]);
|
||||||
|
JArray jobs = JArray.Parse(jobsJson);
|
||||||
|
return jobs.FirstOrDefault()[".id"].ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<string> KillJob(string JobID)
|
||||||
|
{
|
||||||
|
return await SendDeleteRequestAsync("system/script/job/" + JobID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<string> GetTrafficSpeed()
|
||||||
|
{
|
||||||
|
return await SendPostRequestAsync("interface/monitor-traffic", "{\"interface\":\"ether1\",\"duration\":\"3s\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> SendGetRequestAsync(string URL, bool IsTest = false)
|
||||||
|
{
|
||||||
|
HttpClientHandler handler = new()
|
||||||
|
{
|
||||||
|
ServerCertificateCustomValidationCallback = delegate { return true; }
|
||||||
|
};
|
||||||
|
using HttpClient httpClient = new(handler);
|
||||||
|
using var request = new HttpRequestMessage(new HttpMethod("GET"), $"https://{MT_IP}/rest/{URL}");
|
||||||
|
string base64authorization = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{MT_USER}:{MT_PASS}"));
|
||||||
|
if (!IsTest) request.Headers.TryAddWithoutValidation("Authorization", $"Basic {base64authorization}");
|
||||||
|
|
||||||
|
HttpResponseMessage response = await httpClient.SendAsync(request);
|
||||||
|
string APIResponse = await response.Content.ReadAsStringAsync();
|
||||||
|
return APIResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> SendPostRequestAsync(string URL, string Data)
|
||||||
|
{
|
||||||
|
HttpClientHandler handler = new()
|
||||||
|
{
|
||||||
|
ServerCertificateCustomValidationCallback = (requestMessage, certificate, chain, policyErrors) => true
|
||||||
|
};
|
||||||
|
using HttpClient httpClient = new(handler);
|
||||||
|
using var request = new HttpRequestMessage(new HttpMethod("GET"), $"https://{MT_IP}/rest/{URL}");
|
||||||
|
string base64authorization = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{MT_USER}:{MT_PASS}"));
|
||||||
|
request.Headers.TryAddWithoutValidation("Authorization", $"Basic {base64authorization}");
|
||||||
|
|
||||||
|
request.Content = new StringContent(Data);
|
||||||
|
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
|
||||||
|
|
||||||
|
HttpResponseMessage response = await httpClient.SendAsync(request);
|
||||||
|
return await response.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> SendDeleteRequestAsync(string URL)
|
||||||
|
{
|
||||||
|
HttpClientHandler handler = new()
|
||||||
|
{
|
||||||
|
ServerCertificateCustomValidationCallback = (requestMessage, certificate, chain, policyErrors) => true
|
||||||
|
};
|
||||||
|
using HttpClient httpClient = new(handler);
|
||||||
|
using var request = new HttpRequestMessage(new HttpMethod("DELETE"), $"https://{MT_IP}/rest/{URL}");
|
||||||
|
string base64authorization = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{MT_USER}:{MT_PASS}"));
|
||||||
|
request.Headers.TryAddWithoutValidation("Authorization", $"Basic {base64authorization}");
|
||||||
|
|
||||||
|
HttpResponseMessage response = await httpClient.SendAsync(request);
|
||||||
|
return await response.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
MTWireGuard.csproj
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UserSecretsId>943131a5-93e0-4ec4-91aa-e26e825730c4</UserSecretsId>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
<DockerfileContext>.</DockerfileContext>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AutoMapper" Version="12.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.1">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.12" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||||
|
<PackageReference Include="QRCoder" Version="1.4.2" />
|
||||||
|
<PackageReference Include="Razor.Templating.Core" Version="1.8.0" />
|
||||||
|
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
30
MTWireGuard.sln
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.3.32819.101
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MTWireGuard", "MTWireGuard.csproj", "{F4826D62-8AB3-4565-A3F1-8FD5998DFA43}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{810BE84C-BB0A-4280-BBE9-E0A1B859BDED}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
.editorconfig = .editorconfig
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{F4826D62-8AB3-4565-A3F1-8FD5998DFA43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F4826D62-8AB3-4565-A3F1-8FD5998DFA43}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F4826D62-8AB3-4565-A3F1-8FD5998DFA43}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F4826D62-8AB3-4565-A3F1-8FD5998DFA43}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {E6EC9B9D-E199-4D33-8B16-F398E2092A72}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
114
Mapper/MappingProfile.cs
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
using AutoMapper;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using MTWireGuard.Models;
|
||||||
|
using MTWireGuard.Models.Mikrotik;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Mapper
|
||||||
|
{
|
||||||
|
public class MappingProfile : Profile
|
||||||
|
{
|
||||||
|
public MappingProfile()
|
||||||
|
{
|
||||||
|
// Logs
|
||||||
|
CreateMap<Log, LogViewModel>()
|
||||||
|
.ForMember(dest => dest.Id,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToUInt64(src.Id.Substring(1), 16)))
|
||||||
|
.ForMember(dest =>dest.Topics,
|
||||||
|
opt => opt.MapFrom(src => FormatTopics(src.Topics)));
|
||||||
|
|
||||||
|
// Server Traffic
|
||||||
|
CreateMap<ServerTraffic, ServerTrafficViewModel>()
|
||||||
|
.ForMember(dest => dest.Name,
|
||||||
|
opt => opt.MapFrom(src => src.Name))
|
||||||
|
.ForMember(dest => dest.Type,
|
||||||
|
opt => opt.MapFrom(src => src.Type))
|
||||||
|
.ForMember(dest => dest.Upload,
|
||||||
|
opt => opt.MapFrom(src => Helper.ConvertByteSize(Convert.ToInt64(src.TX), 2)))
|
||||||
|
.ForMember(dest => dest.Download,
|
||||||
|
opt => opt.MapFrom(src => Helper.ConvertByteSize(Convert.ToInt64(src.RX), 2)))
|
||||||
|
.ForMember(dest => dest.UploadBytes,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToInt64(src.TX)))
|
||||||
|
.ForMember(dest => dest.DownloadBytes,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToInt64(src.RX)));
|
||||||
|
|
||||||
|
// Mikrotik HWInfo
|
||||||
|
CreateMap<MTInfo, MTInfoViewModel>()
|
||||||
|
.ForMember(dest => dest.Architecture,
|
||||||
|
opt => opt.MapFrom(src => src.ArchitectureName))
|
||||||
|
.ForMember(dest => dest.CPUCount,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToByte(src.CPUCount)))
|
||||||
|
.ForMember(dest => dest.CPUFrequency,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToInt16(src.CPUFrequency)))
|
||||||
|
.ForMember(dest => dest.CPULoad,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToByte(src.CPULoad)))
|
||||||
|
.ForMember(dest => dest.TotalHDDBytes,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToInt64(src.TotalHDDSpace)))
|
||||||
|
.ForMember(dest => dest.FreeHDDBytes,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToInt64(src.FreeHDDSpace)))
|
||||||
|
.ForMember(dest => dest.UsedHDDBytes,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToInt64(src.TotalHDDSpace) - Convert.ToInt64(src.FreeHDDSpace)))
|
||||||
|
.ForMember(dest => dest.FreeHDDPercentage,
|
||||||
|
opt => opt.MapFrom(src => (byte)(Convert.ToInt64(src.FreeHDDSpace) * 100 / Convert.ToInt64(src.TotalHDDSpace))))
|
||||||
|
.ForMember(dest => dest.TotalRAMBytes,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToInt64(src.TotalMemory)))
|
||||||
|
.ForMember(dest => dest.FreeRAMBytes,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToInt64(src.FreeMemory)))
|
||||||
|
.ForMember(dest => dest.UsedRAMBytes,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToInt64(src.TotalMemory) - Convert.ToInt64(src.FreeMemory)))
|
||||||
|
.ForMember(dest => dest.FreeRAMPercentage,
|
||||||
|
opt => opt.MapFrom(src => (byte)(Convert.ToInt64(src.FreeMemory) * 100 / Convert.ToInt64(src.TotalMemory))))
|
||||||
|
.ForMember(dest => dest.TotalHDD,
|
||||||
|
opt => opt.MapFrom(src => Helper.ConvertByteSize(Convert.ToInt64(src.TotalHDDSpace), 2)))
|
||||||
|
.ForMember(dest => dest.FreeHDD,
|
||||||
|
opt => opt.MapFrom(src => Helper.ConvertByteSize(Convert.ToInt64(src.FreeHDDSpace), 2)))
|
||||||
|
.ForMember(dest => dest.UsedHDD,
|
||||||
|
opt => opt.MapFrom(src => Helper.ConvertByteSize(Convert.ToInt64(src.TotalHDDSpace) - Convert.ToInt64(src.FreeHDDSpace), 2)))
|
||||||
|
.ForMember(dest => dest.TotalRAM,
|
||||||
|
opt => opt.MapFrom(src => Helper.ConvertByteSize(Convert.ToInt64(src.TotalMemory), 2)))
|
||||||
|
.ForMember(dest => dest.FreeRAM,
|
||||||
|
opt => opt.MapFrom(src => Helper.ConvertByteSize(Convert.ToInt64(src.FreeMemory), 2)))
|
||||||
|
.ForMember(dest => dest.UsedRAM,
|
||||||
|
opt => opt.MapFrom(src => Helper.ConvertByteSize(Convert.ToInt64(src.TotalMemory) - Convert.ToInt64(src.FreeMemory), 2)))
|
||||||
|
.ForMember(dest => dest.UPTime,
|
||||||
|
opt => opt.MapFrom(src => src.Uptime.
|
||||||
|
// Replace('d', ' ').
|
||||||
|
Replace('h', ':').
|
||||||
|
Replace('m', ':').
|
||||||
|
Replace("s", "")))
|
||||||
|
;
|
||||||
|
|
||||||
|
// Router Identity
|
||||||
|
CreateMap<MTIdentity, MTIdentityViewModel>();
|
||||||
|
|
||||||
|
// Active Users
|
||||||
|
CreateMap<ActiveUser, ActiveUserViewModel>()
|
||||||
|
.ForMember(dest => dest.Id,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToInt16(src.Id.Substring(1), 16)))
|
||||||
|
.ForMember(dest => dest.LoggedIn,
|
||||||
|
opt => opt.MapFrom(src => src.When));
|
||||||
|
|
||||||
|
// Active Jobs
|
||||||
|
CreateMap<Job, JobViewModel>()
|
||||||
|
.ForMember(dest => dest.Id,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToInt16(src.Id.Substring(1), 16)))
|
||||||
|
.ForMember(dest => dest.NextId,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToInt16(src.NextId.Substring(1), 16)))
|
||||||
|
.ForMember(dest => dest.Policies,
|
||||||
|
opt => opt.MapFrom(src => src.Policy.Split(',', StringSplitOptions.None).ToList()));
|
||||||
|
|
||||||
|
// Item Creation
|
||||||
|
CreateMap<CreationStatus, CreationResult>()
|
||||||
|
.ForMember(dest => dest.Code,
|
||||||
|
opt => opt.MapFrom(src => (src.Success) ? "200" : src.Code.ToString()))
|
||||||
|
.ForMember(dest => dest.Title,
|
||||||
|
opt => opt.MapFrom(src => (src.Success) ? "Done" : src.Message))
|
||||||
|
.ForMember(dest => dest.Description,
|
||||||
|
opt => opt.MapFrom(src => (src.Success) ? "Item created/updated successfully." : src.Detail));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<string> FormatTopics(string topics)
|
||||||
|
{
|
||||||
|
return topics.Split(',', StringSplitOptions.TrimEntries).Select(t => t = Helper.UpperCaseTopics.Contains(t) ? t.ToUpper() : t.FirstCharToUpper()).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
89
Mapper/PeerMapping.cs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
using AutoMapper;
|
||||||
|
using MTWireGuard.Models.Mikrotik;
|
||||||
|
using MTWireGuard.Models.Requests;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Mapper
|
||||||
|
{
|
||||||
|
public class PeerMapping : Profile
|
||||||
|
{
|
||||||
|
DBContext db;
|
||||||
|
public PeerMapping(DBContext context)
|
||||||
|
{
|
||||||
|
db = context;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mikrotik Peer to ViewModel
|
||||||
|
*/
|
||||||
|
CreateMap<WGPeer, WGPeerViewModel>()
|
||||||
|
.ForMember(dest => dest.Id,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToInt32(src.Id.Substring(1), 16)))
|
||||||
|
.ForMember(dest => dest.Name,
|
||||||
|
opt => opt.MapFrom(src => GetPeerName(src)))
|
||||||
|
.ForMember(dest => dest.PrivateKey,
|
||||||
|
opt => opt.MapFrom(src => GetPeerPrivateKey(src)))
|
||||||
|
.ForMember(dest => dest.Address,
|
||||||
|
opt => opt.MapFrom(src => src.AllowedAddress))
|
||||||
|
.ForMember(dest => dest.CurrentAddress,
|
||||||
|
opt => opt.MapFrom(src => $"{src.CurrentEndpointAddress}:{src.CurrentEndpointPort}"))
|
||||||
|
.ForMember(dest => dest.IsEnabled,
|
||||||
|
opt => opt.MapFrom(src => !src.Disabled))
|
||||||
|
.ForMember(dest => dest.IsDifferent,
|
||||||
|
opt => opt.MapFrom(src => HasDifferences(src)))
|
||||||
|
.ForMember(dest => dest.Upload,
|
||||||
|
opt => opt.MapFrom(src => Helper.ConvertByteSize(Convert.ToInt64(src.TX), 2)))
|
||||||
|
.ForMember(dest => dest.Download,
|
||||||
|
opt => opt.MapFrom(src => Helper.ConvertByteSize(Convert.ToInt64(src.RX), 2)))
|
||||||
|
.ForMember(dest => dest.UploadBytes,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToInt64(src.TX)))
|
||||||
|
.ForMember(dest => dest.DownloadBytes,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToInt64(src.RX)));
|
||||||
|
|
||||||
|
// WGPeer
|
||||||
|
CreateMap<UserCreateModel, WGPeerCreateModel>();
|
||||||
|
CreateMap<WGPeerCreateModel, WGPeerDBModel>();
|
||||||
|
CreateMap<UserUpdateModel, WGPeerUpdateModel>()
|
||||||
|
.ForMember(dest => dest.Id,
|
||||||
|
opt => opt.MapFrom(src => $"*{src.Id:X}"));
|
||||||
|
CreateMap<CreateClientRequest, UserCreateModel>()
|
||||||
|
.ForMember(dest => dest.Disabled,
|
||||||
|
opt => opt.MapFrom(src => !src.Enabled))
|
||||||
|
.ForMember(dest => dest.EndpointAddress,
|
||||||
|
opt => opt.MapFrom(src => src.Endpoint))
|
||||||
|
.ForMember(dest => dest.PersistentKeepalive,
|
||||||
|
opt => opt.MapFrom(src => src.KeepAlive.ToString()));
|
||||||
|
CreateMap<SyncUserRequest, UserSyncModel>();
|
||||||
|
CreateMap<UpdateClientRequest, UserUpdateModel>()
|
||||||
|
.ForMember(dest => dest.EndpointAddress,
|
||||||
|
opt => opt.MapFrom(src => src.Endpoint))
|
||||||
|
.ForMember(dest => dest.PersistentKeepalive,
|
||||||
|
opt => opt.MapFrom(src => src.KeepAlive));
|
||||||
|
|
||||||
|
// DBUser
|
||||||
|
CreateMap<WGPeerViewModel, WGPeerDBModel>();
|
||||||
|
CreateMap<UserSyncModel, WGPeerDBModel>();
|
||||||
|
CreateMap<UserSyncModel, WGPeerUpdateModel>()
|
||||||
|
.ForMember(dest => dest.Id,
|
||||||
|
opt => opt.MapFrom(src => $"*{src.Id:X}"));
|
||||||
|
CreateMap<UserUpdateModel, WGPeerDBModel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? GetPeerName(WGPeer source)
|
||||||
|
{
|
||||||
|
return (db.Users.ToList().Find(u => u.Id == Convert.ToInt32(source.Id[1..], 16)) != null) ? db.Users.ToList().Find(u => u.Id == Convert.ToInt32(source.Id[1..], 16)).Name : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? GetPeerPrivateKey(WGPeer source)
|
||||||
|
{
|
||||||
|
return (db.Users.ToList().Find(u => u.Id == Convert.ToInt32(source.Id[1..], 16)) != null) ? db.Users.ToList().Find(u => u.Id == Convert.ToInt32(source.Id[1..], 16)).PrivateKey : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasDifferences(WGPeer source)
|
||||||
|
{
|
||||||
|
var id = Convert.ToInt32(source.Id[1..], 16);
|
||||||
|
var dbUser = db.Users.ToList().Find(x => x.Id == id);
|
||||||
|
if (dbUser is null) return true;
|
||||||
|
if (dbUser.PublicKey != source.PublicKey) return true;
|
||||||
|
return string.IsNullOrWhiteSpace(dbUser.PrivateKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
Mapper/ServerMapping.cs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
using AutoMapper;
|
||||||
|
using MTWireGuard.Models.Mikrotik;
|
||||||
|
using MTWireGuard.Models.Requests;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Mapper
|
||||||
|
{
|
||||||
|
public class ServerMapping : Profile
|
||||||
|
{
|
||||||
|
public ServerMapping()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Convert Mikrotik Server Model to ViewModel
|
||||||
|
*/
|
||||||
|
CreateMap<WGServer, WGServerViewModel>()
|
||||||
|
.ForMember(dest => dest.Id,
|
||||||
|
opt => opt.MapFrom(src => Convert.ToInt32(src.Id.Substring(1), 16)))
|
||||||
|
.ForMember(dest => dest.IsEnabled,
|
||||||
|
opt => opt.MapFrom(src => !src.Disabled));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert Wrapper CreateModel to Rest-API CreateModel
|
||||||
|
*/
|
||||||
|
CreateMap<CreateServerRequest, ServerCreateModel>()
|
||||||
|
.ForMember(dest => dest.ListenPort,
|
||||||
|
opt => opt.MapFrom(src => src.Port));
|
||||||
|
|
||||||
|
CreateMap<ServerCreateModel, WGServerCreateModel>()
|
||||||
|
.ForMember(dest => dest.Disabled,
|
||||||
|
opt => opt.MapFrom(src => !src.Enabled));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert Wrapper UpdateModel to Rest-API UpdateModel
|
||||||
|
*/
|
||||||
|
CreateMap<UpdateServerRequest, ServerUpdateModel>()
|
||||||
|
.ForMember(dest => dest.ListenPort,
|
||||||
|
opt => opt.MapFrom(src => src.Port));
|
||||||
|
|
||||||
|
|
||||||
|
CreateMap<ServerUpdateModel, WGServerUpdateModel>()
|
||||||
|
.ForMember(dest => dest.Id,
|
||||||
|
opt => opt.MapFrom(src => $"*{src.Id:X}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
64
Middlewares/AntiForgeryMiddleware.cs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
using Microsoft.AspNetCore.Antiforgery;
|
||||||
|
using MTWireGuard.Pages;
|
||||||
|
using MTWireGuard.Repositories;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Razor.Templating.Core;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Middlewares
|
||||||
|
{
|
||||||
|
public class AntiForgeryMiddleware : IMiddleware
|
||||||
|
{
|
||||||
|
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||||
|
{
|
||||||
|
if (context.Request.Path.Value == "/Login")
|
||||||
|
{
|
||||||
|
await next(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var antiForgeryService = context.RequestServices.GetRequiredService<IAntiforgery>();
|
||||||
|
var isGetRequest = string.Equals("GET", context.Request.Method, StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (!isGetRequest)
|
||||||
|
{
|
||||||
|
await antiForgeryService.ValidateRequestAsync(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (AntiforgeryValidationException ex)
|
||||||
|
{
|
||||||
|
string response = string.Empty;
|
||||||
|
ErrorModel errorModel = new();
|
||||||
|
Dictionary<string, object> ViewBag = new()
|
||||||
|
{
|
||||||
|
["Title"] = "XSRF token validation failed!",
|
||||||
|
["Message"] = ex.Message
|
||||||
|
};
|
||||||
|
if (context.Request.Path.Value == "/Login")
|
||||||
|
response = await RazorTemplateEngine.RenderAsync("/Pages/Error.cshtml", errorModel, ViewBag);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{"background", "danger"},
|
||||||
|
{"title", ViewBag["Title"].ToString()},
|
||||||
|
{"body", ViewBag["Message"].ToString()},
|
||||||
|
};
|
||||||
|
response = JsonConvert.SerializeObject(result);
|
||||||
|
}
|
||||||
|
context.Response.StatusCode = 200;
|
||||||
|
await context.Response.WriteAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await next(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AntiForgeryMiddlewareExtensions
|
||||||
|
{
|
||||||
|
public static IApplicationBuilder UseAntiForgery(this IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
return builder.UseMiddleware<AntiForgeryMiddleware>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
Middlewares/DependencyCheckMiddleware.cs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
using MTWireGuard.Pages;
|
||||||
|
using MTWireGuard.Repositories;
|
||||||
|
using Razor.Templating.Core;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Middlewares
|
||||||
|
{
|
||||||
|
public class DependencyCheckMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly IMikrotikRepository API;
|
||||||
|
|
||||||
|
public DependencyCheckMiddleware(RequestDelegate next, IMikrotikRepository mikrotik)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
API = mikrotik;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InvokeAsync(HttpContext context)
|
||||||
|
{
|
||||||
|
bool Error = false;
|
||||||
|
|
||||||
|
string? IP = Environment.GetEnvironmentVariable("MT_IP");
|
||||||
|
string? USER = Environment.GetEnvironmentVariable("MT_USER");
|
||||||
|
string? PASS = Environment.GetEnvironmentVariable("MT_PASS");
|
||||||
|
string? PUBLICIP = Environment.GetEnvironmentVariable("MT_PUBLIC_IP");
|
||||||
|
|
||||||
|
ErrorModel errorModel = new();
|
||||||
|
Dictionary<string, object> ViewBag = new();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(IP) || string.IsNullOrEmpty(USER) || string.IsNullOrEmpty(PUBLICIP))
|
||||||
|
{
|
||||||
|
ViewBag["Title"] = "Environment variables are not set!";
|
||||||
|
ViewBag["Message"] = "Please set \"MT_IP\", \"MT_USER\", \"MT_PASS\", \"MT_PUBLIC_IP\" variables in container environment.";
|
||||||
|
Error = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (context.Request.Path.Value == "/Login")
|
||||||
|
{
|
||||||
|
await _next(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool APIEnabled = await API.TryConnectAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ViewBag["Title"] = "Error connecting to the router api!";
|
||||||
|
ViewBag["Message"] = ex.Message;
|
||||||
|
Error = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Error)
|
||||||
|
{
|
||||||
|
var html = await RazorTemplateEngine.RenderAsync("/Pages/Error.cshtml", errorModel, ViewBag);
|
||||||
|
context.Response.StatusCode = 200;
|
||||||
|
await context.Response.WriteAsync(html);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _next(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DependencyCheckMiddlewareExtensions
|
||||||
|
{
|
||||||
|
public static IApplicationBuilder UseDependencyCheck(this IApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
return builder.UseMiddleware<DependencyCheckMiddleware>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
Migrations/20230108171012_Initialize.Designer.cs
generated
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using MTWireGuard;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MTWireGuard.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(DBContext))]
|
||||||
|
[Migration("20230108171012_Initialize")]
|
||||||
|
partial class Initialize
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "7.0.1");
|
||||||
|
|
||||||
|
modelBuilder.Entity("MTWireGuard.Models.Mikrotik.WGPeerDBModel", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PrivateKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PublicKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("PrivateKey")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("PublicKey")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
Migrations/20230108171012_Initialize.cs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MTWireGuard.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Initialize : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Users",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
PrivateKey = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
PublicKey = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Users", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Users_PrivateKey",
|
||||||
|
table: "Users",
|
||||||
|
column: "PrivateKey",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Users_PublicKey",
|
||||||
|
table: "Users",
|
||||||
|
column: "PublicKey",
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Users");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
Migrations/DBContextModelSnapshot.cs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using MTWireGuard;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MTWireGuard.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(DBContext))]
|
||||||
|
partial class DBContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "7.0.1");
|
||||||
|
|
||||||
|
modelBuilder.Entity("MTWireGuard.Models.Mikrotik.WGPeerDBModel", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PrivateKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PublicKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("PrivateKey")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("PublicKey")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
Models/CreationStatus.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
namespace MTWireGuard.Models
|
||||||
|
{
|
||||||
|
public class CreationStatus
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Code { get; set; }
|
||||||
|
public string Detail { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public object Item { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CreationResult
|
||||||
|
{
|
||||||
|
public string Code { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
}
|
||||||
|
}
|
8
Models/LoginFailed.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace MTWireGuard.Models
|
||||||
|
{
|
||||||
|
public class LoginStatus
|
||||||
|
{
|
||||||
|
public short Error { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
25
Models/Mikrotik/ActiveUser.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Models.Mikrotik
|
||||||
|
{
|
||||||
|
public class ActiveUser
|
||||||
|
{
|
||||||
|
[JsonProperty(".id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Group { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public bool Radius { get; set; }
|
||||||
|
public string Via { get; set; }
|
||||||
|
public string When { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ActiveUserViewModel
|
||||||
|
{
|
||||||
|
public short Id { get; set; }
|
||||||
|
public string Group { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public bool Radius { get; set; }
|
||||||
|
public string Via { get; set; }
|
||||||
|
public string LoggedIn { get; set; }
|
||||||
|
}
|
||||||
|
}
|
12
Models/Mikrotik/EtherIP.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Models.Mikrotik
|
||||||
|
{
|
||||||
|
public class EtherIP
|
||||||
|
{
|
||||||
|
[JsonProperty(".id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Address { get; set; }
|
||||||
|
}
|
||||||
|
}
|
12
Models/Mikrotik/Identity.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
namespace MTWireGuard.Models.Mikrotik
|
||||||
|
{
|
||||||
|
public class MTIdentity
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MTIdentityViewModel
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
26
Models/Mikrotik/Job.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Models.Mikrotik
|
||||||
|
{
|
||||||
|
public class Job
|
||||||
|
{
|
||||||
|
[JsonProperty(".id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[JsonProperty(".nextid")]
|
||||||
|
public string NextId { get; set; }
|
||||||
|
public string Owner { get; set; }
|
||||||
|
public string Policy { get; set; }
|
||||||
|
public string Started { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class JobViewModel
|
||||||
|
{
|
||||||
|
public short Id { get; set; }
|
||||||
|
public short NextId { get; set; }
|
||||||
|
public string Owner { get; set; }
|
||||||
|
public List<string> Policies { get; set; }
|
||||||
|
public string Started { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
}
|
||||||
|
}
|
21
Models/Mikrotik/Log.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Models.Mikrotik
|
||||||
|
{
|
||||||
|
public class Log
|
||||||
|
{
|
||||||
|
[JsonProperty(".id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public string Time { get; set; }
|
||||||
|
public string Topics { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LogViewModel
|
||||||
|
{
|
||||||
|
public ulong Id { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
public string Time { get; set; }
|
||||||
|
public List<string> Topics { get; set; }
|
||||||
|
}
|
||||||
|
}
|
74
Models/Mikrotik/MTInfo.cs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Models.Mikrotik
|
||||||
|
{
|
||||||
|
public class MTInfo
|
||||||
|
{
|
||||||
|
[JsonProperty("architecture-name")]
|
||||||
|
public string ArchitectureName { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("board-name")]
|
||||||
|
public string BoardName { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("build-time")]
|
||||||
|
public string BuildTime { get; set; }
|
||||||
|
public string CPU { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("cpu-count")]
|
||||||
|
public string CPUCount { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("cpu-frequency")]
|
||||||
|
public string CPUFrequency { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("cpu-load")]
|
||||||
|
public string CPULoad { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("free-hdd-space")]
|
||||||
|
public string FreeHDDSpace { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("free-memory")]
|
||||||
|
public string FreeMemory { get; set; }
|
||||||
|
public string Platform { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("total-hdd-space")]
|
||||||
|
public string TotalHDDSpace { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("total-memory")]
|
||||||
|
public string TotalMemory { get; set; }
|
||||||
|
public string Uptime { get; set; }
|
||||||
|
public string Version { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("write-sect-since-reboot")]
|
||||||
|
public string WriteSectSinceReboot { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("write-sect-total")]
|
||||||
|
public string WriteSectTotal { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MTInfoViewModel
|
||||||
|
{
|
||||||
|
public string Architecture { get; set; }
|
||||||
|
public string BoardName { get; set; }
|
||||||
|
public string Platform { get; set; }
|
||||||
|
public string CPU { get; set; }
|
||||||
|
public byte CPUCount { get; set; }
|
||||||
|
public short CPUFrequency { get; set; }
|
||||||
|
public byte CPULoad { get; set; }
|
||||||
|
public string TotalHDD { get; set; }
|
||||||
|
public string UsedHDD { get; set; }
|
||||||
|
public string FreeHDD { get; set; }
|
||||||
|
public long TotalHDDBytes { get; set; }
|
||||||
|
public long UsedHDDBytes { get; set; }
|
||||||
|
public long FreeHDDBytes { get; set; }
|
||||||
|
public byte FreeHDDPercentage { get; set; }
|
||||||
|
public string TotalRAM { get; set; }
|
||||||
|
public string UsedRAM { get; set; }
|
||||||
|
public string FreeRAM { get; set; }
|
||||||
|
public long TotalRAMBytes { get; set; }
|
||||||
|
public long UsedRAMBytes { get; set; }
|
||||||
|
public long FreeRAMBytes { get; set; }
|
||||||
|
public byte FreeRAMPercentage { get; set; }
|
||||||
|
public string UPTime { get; set; }
|
||||||
|
public string Version { get; set; }
|
||||||
|
}
|
||||||
|
}
|
24
Models/Mikrotik/ServerTraffic.cs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Models.Mikrotik
|
||||||
|
{
|
||||||
|
public class ServerTraffic
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
[JsonProperty("tx-byte")]
|
||||||
|
public string TX { get; set; }
|
||||||
|
[JsonProperty("rx-byte")]
|
||||||
|
public string RX { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ServerTrafficViewModel
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
public string Upload { get; set; }
|
||||||
|
public string Download { get; set; }
|
||||||
|
public long UploadBytes { get; set; }
|
||||||
|
public long DownloadBytes { get; set; }
|
||||||
|
}
|
||||||
|
}
|
136
Models/Mikrotik/WGPeer.cs
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Models.Mikrotik
|
||||||
|
{
|
||||||
|
public class WGPeer
|
||||||
|
{
|
||||||
|
[JsonProperty(".id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[JsonProperty("allowed-address")]
|
||||||
|
public string AllowedAddress { get; set; }
|
||||||
|
[JsonProperty("current-endpoint-address")]
|
||||||
|
public string CurrentEndpointAddress { get; set; }
|
||||||
|
[JsonProperty("current-endpoint-port")]
|
||||||
|
public string CurrentEndpointPort { get; set; }
|
||||||
|
[JsonProperty("disabled")]
|
||||||
|
public bool Disabled { get; set; }
|
||||||
|
[JsonProperty("interface")]
|
||||||
|
public string Interface { get; set; }
|
||||||
|
[JsonProperty("endpoint-address")]
|
||||||
|
public string EndpointAddress { get; set; }
|
||||||
|
[JsonProperty("endpoint-port")]
|
||||||
|
public string EndpointPort { get; set; }
|
||||||
|
[JsonProperty("public-key")]
|
||||||
|
public string PublicKey { get; set; }
|
||||||
|
[JsonProperty("rx")]
|
||||||
|
public string RX { get; set; }
|
||||||
|
[JsonProperty("tx")]
|
||||||
|
public string TX { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[PrimaryKey("Id")]
|
||||||
|
[Index("PrivateKey", IsUnique = true)]
|
||||||
|
[Index("PublicKey", IsUnique = true)]
|
||||||
|
public class WGPeerDBModel
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string PrivateKey { get; set; }
|
||||||
|
public string PublicKey { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WGPeerViewModel
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Address { get; set; }
|
||||||
|
public string CurrentAddress { get; set; }
|
||||||
|
public bool IsEnabled { get; set; }
|
||||||
|
public string Interface { get; set; }
|
||||||
|
public string PrivateKey { get; set; }
|
||||||
|
public string PublicKey { get; set; }
|
||||||
|
public string Download { get; set; }
|
||||||
|
public string Upload { get; set; }
|
||||||
|
public long DownloadBytes { get; set; }
|
||||||
|
public long UploadBytes { get; set; }
|
||||||
|
public bool IsDifferent { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WGPeerCreateModel
|
||||||
|
{
|
||||||
|
[JsonProperty("allowed-address")]
|
||||||
|
public string AllowedAddress { get; set; }
|
||||||
|
[JsonProperty("disabled")]
|
||||||
|
public bool Disabled { get; set; }
|
||||||
|
[JsonProperty("interface")]
|
||||||
|
public string Interface { get; set; }
|
||||||
|
[JsonProperty("endpoint-address"), DefaultValue("")]
|
||||||
|
public string EndpointAddress { get; set; }
|
||||||
|
[JsonProperty("endpoint-port"), DefaultValue("")]
|
||||||
|
public string EndpointPort { get; set; }
|
||||||
|
[JsonProperty("public-key")]
|
||||||
|
public string PublicKey { get; set; }
|
||||||
|
[JsonProperty("preshared-key"), DefaultValue("")]
|
||||||
|
public string PresharedKey { get; set; }
|
||||||
|
[JsonProperty("persistent-keepalive"), DefaultValue("")]
|
||||||
|
public string PersistentKeepalive { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WGPeerUpdateModel
|
||||||
|
{
|
||||||
|
[JsonProperty(".id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[JsonProperty("allowed-address"), DefaultValue("")]
|
||||||
|
public string AllowedAddress { get; set; }
|
||||||
|
[JsonProperty("interface")]
|
||||||
|
public string Interface { get; set; }
|
||||||
|
[JsonProperty("endpoint-address"), DefaultValue("")]
|
||||||
|
public string EndpointAddress { get; set; }
|
||||||
|
[JsonProperty("endpoint-port"), DefaultValue(0)]
|
||||||
|
public ushort EndpointPort { get; set; }
|
||||||
|
[JsonProperty("public-key"), DefaultValue("")]
|
||||||
|
public string PublicKey { get; set; }
|
||||||
|
[JsonProperty("preshared-key"), DefaultValue("")]
|
||||||
|
public string PresharedKey { get; set; }
|
||||||
|
[JsonProperty("persistent-keepalive"), DefaultValue(0)]
|
||||||
|
public int PersistentKeepalive { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserCreateModel
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string PrivateKey { get; set; }
|
||||||
|
public string AllowedAddress { get; set; }
|
||||||
|
public bool Disabled { get; set; }
|
||||||
|
public string Interface { get; set; }
|
||||||
|
public string EndpointAddress { get; set; }
|
||||||
|
public string EndpointPort { get; set; }
|
||||||
|
public string PublicKey { get; set; }
|
||||||
|
public string PresharedKey { get; set; }
|
||||||
|
public string PersistentKeepalive { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserSyncModel
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string PrivateKey { get; set; }
|
||||||
|
public string PublicKey { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserUpdateModel
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string AllowedAddress { get; set; }
|
||||||
|
public string Interface { get; set; }
|
||||||
|
public string EndpointAddress { get; set; }
|
||||||
|
public ushort EndpointPort { get; set; }
|
||||||
|
public string PublicKey { get; set; }
|
||||||
|
public string PrivateKey { get; set; }
|
||||||
|
public string PresharedKey { get; set; }
|
||||||
|
public int PersistentKeepalive { get; set; }
|
||||||
|
}
|
||||||
|
}
|
83
Models/Mikrotik/WGServer.cs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Models.Mikrotik
|
||||||
|
{
|
||||||
|
public class WGServer
|
||||||
|
{
|
||||||
|
[JsonProperty(".id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[JsonProperty("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
[JsonProperty("disabled")]
|
||||||
|
public bool Disabled { get; set; }
|
||||||
|
[JsonProperty("listen-port")]
|
||||||
|
public string ListenPort { get; set; }
|
||||||
|
[JsonProperty("mtu")]
|
||||||
|
public string MTU { get; set; }
|
||||||
|
[JsonProperty("private-key")]
|
||||||
|
public string PrivateKey { get; set; }
|
||||||
|
[JsonProperty("public-key")]
|
||||||
|
public string PublicKey { get; set; }
|
||||||
|
[JsonProperty("running")]
|
||||||
|
public bool Running { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WGServerViewModel
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public bool IsEnabled { get; set; }
|
||||||
|
public ushort ListenPort { get; set; }
|
||||||
|
public ushort MTU { get; set; }
|
||||||
|
public string PrivateKey { get; set; }
|
||||||
|
public string PublicKey { get; set; }
|
||||||
|
public bool Running { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ServerCreateModel
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
public string ListenPort { get; set; }
|
||||||
|
public string MTU { get; set; }
|
||||||
|
public string PrivateKey { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ServerUpdateModel
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public ushort ListenPort { get; set; }
|
||||||
|
public ushort MTU { get; set; }
|
||||||
|
public string PrivateKey { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WGServerCreateModel
|
||||||
|
{
|
||||||
|
[JsonProperty("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
[JsonProperty("disabled"), DefaultValue(false)]
|
||||||
|
public bool Disabled { get; set; }
|
||||||
|
[JsonProperty("listen-port")]
|
||||||
|
public ushort ListenPort { get; set; }
|
||||||
|
[JsonProperty("mtu")]
|
||||||
|
public ushort MTU { get; set; }
|
||||||
|
[JsonProperty("private-key")]
|
||||||
|
public string PrivateKey { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WGServerUpdateModel
|
||||||
|
{
|
||||||
|
[JsonProperty(".id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[JsonProperty("name"), DefaultValue("")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
[JsonProperty("listen-port"), DefaultValue(0)]
|
||||||
|
public ushort ListenPort { get; set; }
|
||||||
|
[JsonProperty("mtu"), DefaultValue(0)]
|
||||||
|
public ushort MTU { get; set; }
|
||||||
|
[JsonProperty("private-key"), DefaultValue("")]
|
||||||
|
public string PrivateKey { get; set; }
|
||||||
|
}
|
||||||
|
}
|
13
Models/Requests/ChangeStateRequest.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Build.Framework;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Models.Requests
|
||||||
|
{
|
||||||
|
public class ChangeStateRequest
|
||||||
|
{
|
||||||
|
[FromQuery(Name = "ID"), Required]
|
||||||
|
public int Id { get; set; }
|
||||||
|
[FromQuery(Name = "IsEnabled"), Required]
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
}
|
||||||
|
}
|
18
Models/Requests/CreateClientRequest.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Models.Requests
|
||||||
|
{
|
||||||
|
public class CreateClientRequest
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Endpoint { get; set; }
|
||||||
|
public ushort EndpointPort { get; set; }
|
||||||
|
public string AllowedAddress { get; set; }
|
||||||
|
public string PresharedKey { get; set; }
|
||||||
|
public string PrivateKey { get; set; }
|
||||||
|
public string PublicKey { get; set; }
|
||||||
|
public string Interface { get; set; }
|
||||||
|
public int KeepAlive { get; set; }
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
}
|
||||||
|
}
|
17
Models/Requests/CreateServerRequest.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Models.Requests
|
||||||
|
{
|
||||||
|
public class CreateServerRequest
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public ushort Port { get; set; } = 13231;
|
||||||
|
|
||||||
|
public ushort MTU { get; set; } = 1420;
|
||||||
|
|
||||||
|
public string PrivateKey { get; set; }
|
||||||
|
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
}
|
||||||
|
}
|
7
Models/Requests/DeleteRequest.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace MTWireGuard.Models.Requests
|
||||||
|
{
|
||||||
|
public class DeleteRequest
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
}
|
||||||
|
}
|
10
Models/Requests/SyncUserRequest.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace MTWireGuard.Models.Requests
|
||||||
|
{
|
||||||
|
public class SyncUserRequest
|
||||||
|
{
|
||||||
|
public int ID { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string PrivateKey { get; set; }
|
||||||
|
public string PublicKey { get; set; }
|
||||||
|
}
|
||||||
|
}
|
16
Models/Requests/UpdateClientRequest.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
namespace MTWireGuard.Models.Requests
|
||||||
|
{
|
||||||
|
public class UpdateClientRequest
|
||||||
|
{
|
||||||
|
public int ID { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Endpoint { get; set; }
|
||||||
|
public ushort EndpointPort { get; set; }
|
||||||
|
public string AllowedAddress { get; set; }
|
||||||
|
public string PresharedKey { get; set; }
|
||||||
|
public string PrivateKey { get; set; }
|
||||||
|
public string PublicKey { get; set; }
|
||||||
|
public string Interface { get; set; }
|
||||||
|
public int KeepAlive { get; set; }
|
||||||
|
}
|
||||||
|
}
|
15
Models/Requests/UpdateServerRequest.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
namespace MTWireGuard.Models.Requests
|
||||||
|
{
|
||||||
|
public class UpdateServerRequest
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public ushort Port { get; set; }
|
||||||
|
|
||||||
|
public ushort MTU { get; set; }
|
||||||
|
|
||||||
|
public string PrivateKey { get; set; }
|
||||||
|
}
|
||||||
|
}
|
34
Models/Responses/ToastResult.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Models.Responses
|
||||||
|
{
|
||||||
|
public class ToastResult : IActionResult
|
||||||
|
{
|
||||||
|
private class Toast
|
||||||
|
{
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Body { get; set; }
|
||||||
|
public string Background { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Toast _result;
|
||||||
|
|
||||||
|
public ToastResult(string title, string body, string background)
|
||||||
|
{
|
||||||
|
_result = new()
|
||||||
|
{
|
||||||
|
Title = title,
|
||||||
|
Body = body,
|
||||||
|
Background = background
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteResultAsync(ActionContext context)
|
||||||
|
{
|
||||||
|
var objectResult = new ObjectResult(_result);
|
||||||
|
|
||||||
|
await objectResult.ExecuteResultAsync(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
Models/WGEnability.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Models
|
||||||
|
{
|
||||||
|
public class WGEnability
|
||||||
|
{
|
||||||
|
[JsonProperty(".id")]
|
||||||
|
public string ID { get; set; }
|
||||||
|
[JsonProperty("disabled")]
|
||||||
|
public bool Disabled { get; set; }
|
||||||
|
}
|
||||||
|
}
|
111
Pages/Clients.cshtml
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
@page "{handler?}"
|
||||||
|
@using MTWireGuard.Models.Mikrotik
|
||||||
|
@model MTWireGuard.Pages.ClientsModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Clients";
|
||||||
|
ViewData["Breadcrumb"] = "Users";
|
||||||
|
var servers = (List<WGServerViewModel>)ViewData["servers"];
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table border mb-0">
|
||||||
|
<thead class="table-light fw-semibold">
|
||||||
|
<tr class="align-middle">
|
||||||
|
<th class="text-center">
|
||||||
|
<svg class="icon">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-people"></use>
|
||||||
|
</svg>
|
||||||
|
</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Interface</th>
|
||||||
|
<th>Address</th>
|
||||||
|
<th>Public-Key</th>
|
||||||
|
<th>Traffic</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="col-12">
|
||||||
|
@await Component.InvokeAsync("CreateClientForm", new {
|
||||||
|
Servers = servers
|
||||||
|
})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- QR Modal -->
|
||||||
|
<component type="typeof(Modals.QRModal)" render-mode="Static" />
|
||||||
|
<!-- Delete Modal -->
|
||||||
|
@await Component.InvokeAsync("DeleteModal", new {
|
||||||
|
IsServer = false
|
||||||
|
})
|
||||||
|
<!-- Edit Modal -->
|
||||||
|
@await Component.InvokeAsync("UpdateClientModal", new {
|
||||||
|
Servers = servers
|
||||||
|
})
|
||||||
|
<!-- Sync Modal -->
|
||||||
|
@await Component.InvokeAsync("SyncUserModal")
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script src="js/wireguard.js"></script>
|
||||||
|
<script src="js/wgelements.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function (event) {
|
||||||
|
fetch("/Clients/getAll")
|
||||||
|
.then((response) => response.text())
|
||||||
|
.then((data) => {
|
||||||
|
document.querySelector('table tbody').innerHTML = data;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll(".sync-btn").forEach(form => {
|
||||||
|
form.addEventListener('click', function (event) {
|
||||||
|
let Id = event.target.closest('tr').getAttribute('data-id');
|
||||||
|
document.querySelector('#SyncModal input[name="ID"]').value = Id;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll(".delete-btn").forEach(form => {
|
||||||
|
form.addEventListener('click', function (event) {
|
||||||
|
let Id = event.target.closest('tr').getAttribute('data-id');
|
||||||
|
document.querySelector('#DeleteModal input[name="Id"]').value = Id;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll(".update-btn").forEach(form => {
|
||||||
|
form.addEventListener('click', function (event) {
|
||||||
|
document.querySelector('#EditModal form').reset();
|
||||||
|
let row = event.target.closest('tr');
|
||||||
|
let Id = row.getAttribute('data-id');
|
||||||
|
let name = row.querySelector('td:nth-child(2)').innerText;
|
||||||
|
let interface = row.querySelector('td:nth-child(3)').innerText;
|
||||||
|
let address = row.querySelector('td:nth-child(4)').innerText;
|
||||||
|
let publicKey = row.querySelector('td:nth-child(5)').innerText;
|
||||||
|
document.querySelector('#EditModal input[name="ID"]').value = Id;
|
||||||
|
document.querySelector('#EditModal input[name="Name"]').placeholder = name;
|
||||||
|
document.querySelector('#EditModal input[name="AllowedAddress"]').placeholder = address;
|
||||||
|
document.querySelector('#EditModal input[name="PublicKey"]').placeholder = publicKey;
|
||||||
|
let ifOption = document.querySelector('#EditModal option[value="' + interface + '"]');
|
||||||
|
if (ifOption)
|
||||||
|
ifOption.setAttribute("selected", true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("QRModal").addEventListener('shown.coreui.modal', (event) => {
|
||||||
|
let Id = event.relatedTarget.closest('tr').getAttribute('data-id');
|
||||||
|
const xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.onload = function () {
|
||||||
|
document.querySelector('#QRModal img.img-thumbnail').setAttribute("src", "data:image/png;base64, " + this.responseText);
|
||||||
|
}
|
||||||
|
xhttp.open("GET", "/Clients/QR?id=" + Id, true);
|
||||||
|
xhttp.send();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
}
|
105
Pages/Clients.cshtml.cs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
using AutoMapper;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using MTWireGuard.Models;
|
||||||
|
using MTWireGuard.Models.Mikrotik;
|
||||||
|
using MTWireGuard.Models.Requests;
|
||||||
|
using MTWireGuard.Models.Responses;
|
||||||
|
using MTWireGuard.Repositories;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using QRCoder;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Pages
|
||||||
|
{
|
||||||
|
public class ClientsModel : PageModel
|
||||||
|
{
|
||||||
|
private readonly IMikrotikRepository API;
|
||||||
|
private readonly IMapper mapper;
|
||||||
|
|
||||||
|
public ClientsModel(IMikrotikRepository mikrotik, IMapper mapper)
|
||||||
|
{
|
||||||
|
API = mikrotik;
|
||||||
|
this.mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnGetAsync()
|
||||||
|
{
|
||||||
|
var servers = await API.GetServersAsync();
|
||||||
|
ViewData["servers"] = servers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PartialViewResult> OnGetGetAll()
|
||||||
|
{
|
||||||
|
var users = await API.GetUsersAsync();
|
||||||
|
return Partial("_ClientsTable", users);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnGetQRAsync(int id)
|
||||||
|
{
|
||||||
|
string config = await API.GetQRCodeBase64(id);
|
||||||
|
return Content(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FileResult> OnGetDownloadTunnelAsync(int id)
|
||||||
|
{
|
||||||
|
string config = await API.GetUserTunnelConfig(id);
|
||||||
|
|
||||||
|
byte[] bytesInStream = System.Text.Encoding.UTF8.GetBytes(config);
|
||||||
|
|
||||||
|
var user = await API.GetUser(id);
|
||||||
|
string filename = string.IsNullOrWhiteSpace(user.Name) ? user.Interface : user.Name;
|
||||||
|
Response.Headers.Add("content-disposition", $"attachment; filename={filename}.conf");
|
||||||
|
|
||||||
|
return File(bytesInStream, "text/plain");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnPostCreateAsync(CreateClientRequest request)
|
||||||
|
{
|
||||||
|
var model = mapper.Map<UserCreateModel>(request);
|
||||||
|
var make = await API.CreateUser(model);
|
||||||
|
string status = make.Code == "200" ? "success" : "danger";
|
||||||
|
string title = make.Code == "200" ? make.Title : $"[{make.Code}] {make.Title}";
|
||||||
|
return new ToastResult(title, make.Description, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnPostDelete(DeleteRequest request)
|
||||||
|
{
|
||||||
|
var delete = await API.DeleteUser(request.Id);
|
||||||
|
string status = delete.Code == "200" ? "success" : "danger";
|
||||||
|
string title = delete.Code == "200" ? delete.Title : $"[{delete.Code}] {delete.Title}";
|
||||||
|
return new ToastResult(title, delete.Description, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnPostUpdate(UpdateClientRequest request)
|
||||||
|
{
|
||||||
|
var model = mapper.Map<UserUpdateModel>(request);
|
||||||
|
var update = await API.UpdateUser(model);
|
||||||
|
string status = update.Code == "200" ? "success" : "danger";
|
||||||
|
string title = update.Code == "200" ? update.Title : $"[{update.Code}] {update.Title}";
|
||||||
|
return new ToastResult(title, update.Description, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnPostSyncAsync(SyncUserRequest request)
|
||||||
|
{
|
||||||
|
var model = mapper.Map<UserSyncModel>(request);
|
||||||
|
var update = await API.SyncUser(model);
|
||||||
|
string status = update.Code == "200" ? "success" : "danger";
|
||||||
|
string title = update.Code == "200" ? update.Title : $"[{update.Code}] {update.Title}";
|
||||||
|
return new ToastResult(title, update.Description, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnGetEnableAsync(ChangeStateRequest request)
|
||||||
|
{
|
||||||
|
CreationResult result;
|
||||||
|
if (!request.Enabled)
|
||||||
|
result = await API.EnableUser(request.Id);
|
||||||
|
else
|
||||||
|
result = await API.DisableUser(request.Id);
|
||||||
|
string status = result.Code == "200" ? "success" : "danger";
|
||||||
|
string title = result.Code == "200" ? result.Title : $"[{result.Code}] {result.Title}";
|
||||||
|
return new ToastResult(title, result.Description, status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
89
Pages/Components/CreateClientForm/CreateClientForm.cshtml
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
@using MTWireGuard.Models.Requests
|
||||||
|
@model CreateClientRequest
|
||||||
|
@{
|
||||||
|
List<Models.Mikrotik.WGServerViewModel> Servers = ViewData["Servers"] as List<Models.Mikrotik.WGServerViewModel>;
|
||||||
|
}
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">Quick add peer</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form asp-page="Clients" asp-page-handler="Create" method="POST">
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPName" class="col-sm-3 col-form-label user-select-none">Name</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="WGPName" name="Name">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPInterface" class="col-sm-3 col-form-label user-select-none">Interface</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select class="form-select" name="Interface" aria-label="Default select example">
|
||||||
|
@foreach (var server in Servers)
|
||||||
|
{
|
||||||
|
<option value="@server.Name">@server.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPPrivKey" class="col-sm-3 col-form-label user-select-none">Private Key</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" id="WGPPrivKey" name="PrivateKey" aria-label="Generate" aria-describedby="PKeygenBTN" autocomplete="off">
|
||||||
|
<button type="button" class="input-group-text" id="PKeygenBTN" style="cursor: pointer;" onclick="generateKeys(this)">
|
||||||
|
<svg class="icon icon-sm text-dark my-1">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-reload"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPPubKey" class="col-sm-3 col-form-label user-select-none">Public Key</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="WGPPubKey" name="PublicKey" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPPSKey" class="col-sm-3 col-form-label user-select-none">Preshared Key</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="WGPPSKey" name="PresharedKey" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPAddress" class="col-sm-3 col-form-label user-select-none">Allowed Address</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input class="form-control" id="WGPAddress" asp-for="AllowedAddress">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPEndpoint" class="col-sm-3 col-form-label user-select-none">Endpoint</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="WGPEndpoint" name="Endpoint">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPPort" class="col-sm-3 col-form-label user-select-none">Endpoint Port</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="number" class="form-control" id="WGPPort" name="EndpointPort">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPKeepAlive" class="col-sm-3 col-form-label user-select-none">Persistent Keepalive</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="WGPKeepAlive" name="KeepAlive" placeholder="--:--:--">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-5 row">
|
||||||
|
<label for="WGSEnabled" class="col-sm-3 form-check-label user-select-none">Enabled</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="checkbox" class="form-check-input" id="WGSEnabled" asp-for="Enabled" checked>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<button class="btn btn-primary" type="submit">Create</button>
|
||||||
|
<button class="btn btn-danger" type="reset">Clear</button>
|
||||||
|
</div>
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
12
Pages/Components/CreateClientForm/CreateClientForm.cshtml.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Pages.Components.CreateClientForm
|
||||||
|
{
|
||||||
|
public class CreateClientFormModel : PageModel
|
||||||
|
{
|
||||||
|
public void OnGet()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
64
Pages/Components/CreateServerForm/CreateServerForm.cshtml
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
@using MTWireGuard.Models.Requests
|
||||||
|
@model CreateServerRequest
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">Quick add server</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form asp-page="Servers" asp-page-handler="Create" method="POST">
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGSName" class="col-sm-3 col-form-label user-select-none">Name</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="WGSName" name="Name" placeholder="wireguard1" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGSPort" class="col-sm-3 col-form-label user-select-none">Listening Port</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="number" class="form-control" id="WGSPort" name="Port" placeholder="13231" min="1" max="65535">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGSMTU" class="col-sm-3 col-form-label user-select-none">MTU</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="number" class="form-control" id="WGSMTU" name="MTU" placeholder="1420" min="64" max="65535">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGSPrivKey" class="col-sm-3 col-form-label user-select-none">Private Key</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" id="WGSPrivKey" name="PrivateKey" aria-label="Generate" aria-describedby="SKeygenBTN" autocomplete="off" required>
|
||||||
|
<button type="button" class="input-group-text" id="SKeygenBTN" style="cursor: pointer;" onclick="generateKeys(this)">
|
||||||
|
<svg class="icon icon-sm text-dark my-1">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-reload"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGSPubKey" class="col-sm-3 col-form-label user-select-none">Public Key</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" id="WGSPubKey" aria-label="Generate" aria-describedby="SUnlockBTN" autocomplete="off" disabled>
|
||||||
|
<button type="button" class="input-group-text" id="SUnlockBTN" style="cursor: pointer;" onclick="changeIcon(this)">
|
||||||
|
<svg class="icon icon-sm text-dark my-1">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-lock-unlocked"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-5 row">
|
||||||
|
<label for="WGSEnabled" class="col-sm-3 form-check-label user-select-none">Enabled</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="checkbox" class="form-check-input" id="WGSEnabled" asp-for="Enabled" checked>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<button class="btn btn-primary" type="submit">Create</button>
|
||||||
|
<button class="btn btn-danger" type="reset">Clear</button>
|
||||||
|
</div>
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
12
Pages/Components/CreateServerForm/CreateServerForm.cshtml.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Pages.Components.CreateServerForm
|
||||||
|
{
|
||||||
|
public class CreateServerFormModel : PageModel
|
||||||
|
{
|
||||||
|
public void OnGet()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
Pages/Components/DeleteModal/DeleteModal.cshtml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
@using MTWireGuard.Models.Requests
|
||||||
|
@model DeleteRequest
|
||||||
|
@{
|
||||||
|
bool IsServer = (bool)ViewData["IsServer"];
|
||||||
|
}
|
||||||
|
<div class="modal fade" id="DeleteModal" tabindex="-1" aria-labelledby="DeleteModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
@{
|
||||||
|
string FormAction = IsServer ? "Servers/Delete" : "Clients/Delete";
|
||||||
|
string Type = IsServer ? "server interface" : "client account";
|
||||||
|
}
|
||||||
|
<form class="modal-content" action="@FormAction" method="POST">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="DeleteModalLabel">Are you sure ?</h5>
|
||||||
|
<button type="button" class="btn-close" data-coreui-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
Are you sure to delete this @Type ?<br />
|
||||||
|
This can't be undone!
|
||||||
|
</p>
|
||||||
|
<input type="hidden" asp-for="Id" />
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="reset" class="btn btn-secondary" data-coreui-dismiss="modal">Close</button>
|
||||||
|
<button type="submit" class="btn btn-danger">Delete</button>
|
||||||
|
</div>
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
12
Pages/Components/DeleteModal/DeleteModal.cshtml.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Pages.Components.DeleteModal
|
||||||
|
{
|
||||||
|
public class DeleteModalModel : PageModel
|
||||||
|
{
|
||||||
|
public void OnGet()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
Pages/Components/LogsView.razor
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
@{
|
||||||
|
int LogCount = Logs.Count <= 20 ? Logs.Count : 20;
|
||||||
|
var LastLogs = Logs.TakeLast(LogCount).ToList();
|
||||||
|
}
|
||||||
|
<div class="dropdown-header bg-light dark:bg-white dark:bg-opacity-10">
|
||||||
|
<strong>You have @Logs.Count logs</strong>
|
||||||
|
</div>
|
||||||
|
@for (int i = 0; i < LogCount; i++)
|
||||||
|
{
|
||||||
|
var log = LastLogs[i];
|
||||||
|
<a class="dropdown-item" href="#">
|
||||||
|
<div class="message">
|
||||||
|
<div>
|
||||||
|
<small class="text-medium-emphasis">#@log.Id</small>
|
||||||
|
<small class="text-medium-emphasis float-end mt-1">@log.Time</small>
|
||||||
|
</div>
|
||||||
|
<div class="font-weight-bold text-medium-emphasis text-truncate">@log.Message</div>
|
||||||
|
<div class="text-truncate small">
|
||||||
|
@foreach (var topic in log.Topics)
|
||||||
|
{
|
||||||
|
switch (topic.ToLower())
|
||||||
|
{
|
||||||
|
case "system":
|
||||||
|
<span class="badge text-bg-dark mx-1">@topic</span>
|
||||||
|
break;
|
||||||
|
case "info":
|
||||||
|
<span class="badge text-bg-info mx-1">@topic</span>
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
case "critical":
|
||||||
|
<span class="badge text-bg-danger mx-1">@topic</span>
|
||||||
|
break;
|
||||||
|
case "account":
|
||||||
|
<span class="badge text-bg-secondary mx-1">@topic</span>
|
||||||
|
break;
|
||||||
|
case "dhcp":
|
||||||
|
case "ppp":
|
||||||
|
case "l2tp":
|
||||||
|
case "pptp":
|
||||||
|
case "sstp":
|
||||||
|
<span class="badge text-bg-light border border-secondary mx-1">@topic</span>
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
<span class="badge text-bg-light border border-secondary mx-1">@topic</span>
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public List<Models.Mikrotik.LogViewModel> Logs { get; set; }
|
||||||
|
}
|
50
Pages/Components/SyncUserModal/SyncUserModal.cshtml
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
@using MTWireGuard.Models.Requests
|
||||||
|
@model SyncUserRequest
|
||||||
|
<div class="modal fade" id="SyncModal" tabindex="-1" aria-labelledby="SyncModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form class="modal-content" asp-page="Clients" asp-page-handler="Sync" method="POST">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="SyncModalLabel">Are you sure ?</h5>
|
||||||
|
<button type="button" class="btn-close" data-coreui-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
By syncing, the values of unsynced properties will be changed to the value stored in the database.<br />
|
||||||
|
Peer's Private Key is required to create client tunnel file or QR code.<br />
|
||||||
|
<strong>All keys (such as private key and public key) are only stored in your docker machine and no any information is sent to outside your network.</strong>
|
||||||
|
</p>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPName" class="col-sm-3 col-form-label user-select-none">Name</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="WGPName" name="Name">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPPrivKey" class="col-sm-3 col-form-label user-select-none">Private Key</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" id="WGUPrivKey" name="PrivateKey" aria-label="Generate" aria-describedby="PKeygenBTN">
|
||||||
|
<button type="button" class="input-group-text" id="PKeygenBTN" style="cursor: pointer;" onclick="generateKeys(this)">
|
||||||
|
<svg class="icon icon-sm text-dark my-1">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-reload"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPPubKey" class="col-sm-3 col-form-label user-select-none">Public Key</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="WGUPubKey" name="PublicKey">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" asp-for="ID" />
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="reset" class="btn btn-danger" data-coreui-dismiss="modal">Close</button>
|
||||||
|
<button type="submit" class="btn btn-success">Sync</button>
|
||||||
|
</div>
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
12
Pages/Components/SyncUserModal/SyncUserModal.cshtml.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Pages.Components.SyncUserModal
|
||||||
|
{
|
||||||
|
public class SyncUserModalModel : PageModel
|
||||||
|
{
|
||||||
|
public void OnGet()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
Pages/Components/ToastContainer.razor
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<div class="toast-container position-fixed bottom-0 end-0 p-3" id="toastContainer">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
}
|
97
Pages/Components/UpdateClientModal/UpdateClientModal.cshtml
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
@using MTWireGuard.Models.Requests
|
||||||
|
@model UpdateClientRequest
|
||||||
|
@{
|
||||||
|
List<Models.Mikrotik.WGServerViewModel> Servers = ViewData["Servers"] as List<Models.Mikrotik.WGServerViewModel>;
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="modal fade" id="EditModal" tabindex="-1" aria-labelledby="EditModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form class="modal-content" action="Clients/Update" method="POST">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="EditModalLabel">Edit user details</h5>
|
||||||
|
<button type="button" class="btn-close" data-coreui-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPUName" class="col-sm-3 col-form-label user-select-none">Name</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="WGPUName" name="Name">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPUInterface" class="col-sm-3 col-form-label user-select-none">Interface</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select class="form-select" id="WGPUInterface" name="Interface" aria-label="Default select example">
|
||||||
|
@foreach (var server in Servers)
|
||||||
|
{
|
||||||
|
@if (server.IsEnabled)
|
||||||
|
{
|
||||||
|
<option value="@server.Name">@server.Name</option>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<option class="fst-italic" value="@server.Name">@server.Name</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPUPrivKey" class="col-sm-3 col-form-label user-select-none">Private Key</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" id="WGPUPrivKey" name="PrivateKey" aria-label="Generate" aria-describedby="PKeygenBTN" autocomplete="off">
|
||||||
|
<button type="button" class="input-group-text" id="PKeygenBTN" style="cursor: pointer;" onclick="generateKeys(this)">
|
||||||
|
<svg class="icon icon-sm text-dark my-1">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-reload"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPUPubKey" class="col-sm-3 col-form-label user-select-none">Public Key</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="WGPUPubKey" name="PublicKey" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPUPSKey" class="col-sm-3 col-form-label user-select-none">Preshared Key</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="WGPUPSKey" name="PresharedKey" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPUAddress" class="col-sm-3 col-form-label user-select-none">Allowed Address</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="WGPUAddress" name="AllowedAddress">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPUEndpoint" class="col-sm-3 col-form-label user-select-none">Endpoint</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="WGPUEndpoint" name="Endpoint">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPUPort" class="col-sm-3 col-form-label user-select-none">Endpoint Port</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="number" class="form-control" id="WGPUPort" name="EndpointPort">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGPUKeepAlive" class="col-sm-3 col-form-label user-select-none">Persistent Keepalive</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="WGPUKeepAlive" name="KeepAlive" placeholder="Seconds">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="ID" />
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="reset" class="btn btn-secondary" data-coreui-dismiss="modal">Close</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Update</button>
|
||||||
|
</div>
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,12 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Pages.Components.UpdateClientModal
|
||||||
|
{
|
||||||
|
public class UpdateClientModalModel : PageModel
|
||||||
|
{
|
||||||
|
public void OnGet()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
Pages/Components/UpdateServerModal/UpdateServerModal.cshtml
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
@using MTWireGuard.Models.Requests
|
||||||
|
@model UpdateServerRequest
|
||||||
|
|
||||||
|
<div class="modal fade" id="EditModal" tabindex="-1" aria-labelledby="EditModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form class="modal-content" action="Servers/Update" method="POST">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="EditModalLabel">Edit server details</h5>
|
||||||
|
<button type="button" class="btn-close" data-coreui-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGSName" class="col-sm-3 col-form-label user-select-none">Name</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" id="WGSName" name="Name" placeholder="wireguard1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGSPort" class="col-sm-3 col-form-label user-select-none">Listening Port</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="number" class="form-control" id="WGSPort" name="Port" placeholder="13231" min="1" max="65535">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGSMTU" class="col-sm-3 col-form-label user-select-none">MTU</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="number" class="form-control" id="WGSMTU" name="MTU" placeholder="1420" min="64" max="65535">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGSPrivKey" class="col-sm-3 col-form-label user-select-none">Private Key</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" id="WGSPrivKey" name="PrivateKey" aria-label="Generate" aria-describedby="SKeygenBTN" autocomplete="off">
|
||||||
|
<button type="button" class="input-group-text" id="SKeygenBTN" style="cursor: pointer;" onclick="generateKeys(this)">
|
||||||
|
<svg class="icon icon-sm text-dark my-1">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-reload"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 row">
|
||||||
|
<label for="WGSPubKey" class="col-sm-3 col-form-label user-select-none">Public Key</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" id="WGSPubKey" aria-label="Generate" aria-describedby="SUnlockBTN" autocomplete="off">
|
||||||
|
<button type="button" class="input-group-text" id="SUnlockBTN" style="cursor: pointer;" onclick="changeIcon(this);">
|
||||||
|
<svg class="icon icon-sm text-dark my-1">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-lock-unlocked"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="ID" />
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="reset" class="btn btn-secondary" data-coreui-dismiss="modal">Close</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Update</button>
|
||||||
|
</div>
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,12 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Pages.Components.UpdateServerModal
|
||||||
|
{
|
||||||
|
public class UpdateServerModalModel : PageModel
|
||||||
|
{
|
||||||
|
public void OnGet()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
58
Pages/Error.cshtml
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
@page
|
||||||
|
@model ErrorModel
|
||||||
|
@{
|
||||||
|
Layout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<base href="./">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<meta name="description" content="CoreUI - Open Source Bootstrap Admin Template">
|
||||||
|
<meta name="author" content="Łukasz Holeczek">
|
||||||
|
<meta name="keyword" content="Bootstrap,Admin,Template,Open,Source,jQuery,CSS,HTML,RWD,Dashboard">
|
||||||
|
<title>Error</title>
|
||||||
|
<link rel="apple-touch-icon" sizes="57x57" href="assets/favicon/apple-icon-57x57.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="60x60" href="assets/favicon/apple-icon-60x60.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="72x72" href="assets/favicon/apple-icon-72x72.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="76x76" href="assets/favicon/apple-icon-76x76.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="114x114" href="assets/favicon/apple-icon-114x114.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="120x120" href="assets/favicon/apple-icon-120x120.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="144x144" href="assets/favicon/apple-icon-144x144.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="152x152" href="assets/favicon/apple-icon-152x152.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="assets/favicon/apple-icon-180x180.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="assets/favicon/android-icon-192x192.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="96x96" href="assets/favicon/favicon-96x96.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="assets/favicon/favicon-16x16.png">
|
||||||
|
<link rel="manifest" href="assets/favicon/manifest.json">
|
||||||
|
<meta name="msapplication-TileColor" content="#ffffff">
|
||||||
|
<meta name="msapplication-TileImage" content="assets/favicon/ms-icon-144x144.png">
|
||||||
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
<!-- Vendors styles-->
|
||||||
|
<link rel="stylesheet" href="vendors/simplebar/css/simplebar.css">
|
||||||
|
<link rel="stylesheet" href="css/vendors/simplebar.css">
|
||||||
|
<!-- Main styles for this application-->
|
||||||
|
<link href="css/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="bg-light min-vh-100 d-flex flex-row align-items-center">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="clearfix">
|
||||||
|
<h1 class="float-start display-3 me-4">500</h1>
|
||||||
|
<h4 class="pt-3">@ViewBag.Title</h4>
|
||||||
|
<p class="text-medium-emphasis">@Html.Raw(ViewBag.Message)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="vendors/coreui/coreui/js/coreui.bundle.min.js"></script>
|
||||||
|
<script src="vendors/simplebar/js/simplebar.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
32
Pages/Error.cshtml.cs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
using Microsoft.AspNetCore.Diagnostics;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Pages
|
||||||
|
{
|
||||||
|
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||||
|
[IgnoreAntiforgeryToken]
|
||||||
|
public class ErrorModel : PageModel
|
||||||
|
{
|
||||||
|
public string? RequestId { get; set; }
|
||||||
|
public string? ExceptionMessage { get; set; }
|
||||||
|
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||||
|
|
||||||
|
public ErrorModel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnGet()
|
||||||
|
{
|
||||||
|
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
|
||||||
|
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
|
||||||
|
|
||||||
|
var exceptionHandlerPathFeature =
|
||||||
|
HttpContext.Features.Get<IExceptionHandlerPathFeature>();
|
||||||
|
|
||||||
|
ViewData["Title"] = exceptionHandlerPathFeature.Error.Message;
|
||||||
|
ViewData["Message"] = exceptionHandlerPathFeature.Error.Source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
Pages/Index.cshtml
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
@page
|
||||||
|
@using MTWireGuard.Models.Mikrotik
|
||||||
|
@model IndexModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Dashboard";
|
||||||
|
ViewData["Breadcrumb"] = "Home";
|
||||||
|
var users = (List<WGPeerViewModel>)ViewData["users"];
|
||||||
|
var servers = (List<WGServerViewModel>)ViewData["servers"];
|
||||||
|
var uptr = ViewData["uptr"];
|
||||||
|
var downtr = ViewData["downtr"];
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6 col-lg-4">
|
||||||
|
<div class="card mb-4" style="--cui-card-cap-bg: #88171a">
|
||||||
|
<div class="card-header position-relative d-flex justify-content-center align-items-center">
|
||||||
|
<svg class="icon icon-3xl text-white my-4">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/brand.svg#cib-wireguard"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="chart-wrapper position-absolute top-0 start-0 w-100 h-100">
|
||||||
|
<canvas id="social-box-chart-1" height="90"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body row text-center">
|
||||||
|
<div class="col">
|
||||||
|
<div class="fs-5 fw-semibold">@servers.Count</div>
|
||||||
|
<div class="text-uppercase text-medium-emphasis small">Servers</div>
|
||||||
|
</div>
|
||||||
|
<div class="vr"></div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="fs-5 fw-semibold">@servers.Where(x => x.Running).Count()</div>
|
||||||
|
<div class="text-uppercase text-medium-emphasis small">Active</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 col-lg-4">
|
||||||
|
<div class="card mb-4" style="--cui-card-cap-bg: #0d6efd">
|
||||||
|
<div class="card-header position-relative d-flex justify-content-center align-items-center">
|
||||||
|
<svg class="icon icon-3xl text-white my-4">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-lan"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="chart-wrapper position-absolute top-0 start-0 w-100 h-100">
|
||||||
|
<canvas id="social-box-chart-1" height="90"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body row text-center">
|
||||||
|
<div class="col">
|
||||||
|
<div class="fs-5 fw-semibold">@users.Count</div>
|
||||||
|
<div class="text-uppercase text-medium-emphasis small">Users</div>
|
||||||
|
</div>
|
||||||
|
<div class="vr"></div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="fs-5 fw-semibold">@users.Where(x => !x.CurrentAddress.EndsWith(":0")).Count()</div>
|
||||||
|
<div class="text-uppercase text-medium-emphasis small">Online</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 col-lg-4">
|
||||||
|
<div class="card mb-4" style="--cui-card-cap-bg: #0dcaf0">
|
||||||
|
<div class="card-header position-relative d-flex justify-content-center align-items-center">
|
||||||
|
<svg class="icon icon-3xl text-white my-4">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-graph"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="chart-wrapper position-absolute top-0 start-0 w-100 h-100">
|
||||||
|
<canvas id="social-box-chart-1" height="90"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body row text-center">
|
||||||
|
<div class="col">
|
||||||
|
<div class="fs-5 fw-semibold">@uptr</div>
|
||||||
|
<div class="text-uppercase text-medium-emphasis small">Upload</div>
|
||||||
|
</div>
|
||||||
|
<div class="vr"></div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="fs-5 fw-semibold">@downtr</div>
|
||||||
|
<div class="text-uppercase text-medium-emphasis small">Download</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 col-lg-6">
|
||||||
|
@await Component.InvokeAsync("CreateServerForm")
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12 col-lg-6">
|
||||||
|
@await Component.InvokeAsync("CreateClientForm", new {
|
||||||
|
Servers = servers
|
||||||
|
})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script src="vendors/coreui/utils/js/coreui-utils.js"></script>
|
||||||
|
<script src="js/wireguard.js"></script>
|
||||||
|
<script src="js/wgelements.js"></script>
|
||||||
|
}
|
42
Pages/Index.cshtml.cs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using MTWireGuard.Repositories;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Pages
|
||||||
|
{
|
||||||
|
[Authorize]
|
||||||
|
public class IndexModel : PageModel
|
||||||
|
{
|
||||||
|
private readonly ILogger<IndexModel> _logger;
|
||||||
|
private readonly IMikrotikRepository API;
|
||||||
|
|
||||||
|
public IndexModel(ILogger<IndexModel> logger, IMikrotikRepository mikrotik)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
API = mikrotik;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnGetAsync()
|
||||||
|
{
|
||||||
|
var users = await API.GetUsersAsync();
|
||||||
|
var servers = await API.GetServersAsync();
|
||||||
|
var traffics = await API.GetServersTraffic();
|
||||||
|
var wgTraffics = traffics.Where(s => s.Type == "wg");
|
||||||
|
long up = 0;
|
||||||
|
long down = 0;
|
||||||
|
foreach (var item in wgTraffics)
|
||||||
|
{
|
||||||
|
up += item.UploadBytes;
|
||||||
|
down += item.DownloadBytes;
|
||||||
|
}
|
||||||
|
ViewData["users"] = users;
|
||||||
|
ViewData["servers"] = servers;
|
||||||
|
ViewData["uptr"] = Helper.ConvertByteSize(up, 2);
|
||||||
|
ViewData["downtr"] = Helper.ConvertByteSize(down, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
88
Pages/Login.cshtml
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
@page
|
||||||
|
@model MTWireGuard.Pages.LoginModel
|
||||||
|
@{
|
||||||
|
Layout = null;
|
||||||
|
bool invalid = false;
|
||||||
|
if (ViewData["Invalid"] != null) invalid = (bool)ViewData["Invalid"];
|
||||||
|
string invalidClass = invalid ? "is-invalid" : "", username = "";
|
||||||
|
if (ViewData["Username"] != null) username = ViewData["Username"].ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<base href="./">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<meta name="description" content="CoreUI - Open Source Bootstrap Admin Template">
|
||||||
|
<meta name="author" content="Łukasz Holeczek">
|
||||||
|
<meta name="keyword" content="Bootstrap,Admin,Template,Open,Source,jQuery,CSS,HTML,RWD,Dashboard">
|
||||||
|
<title>Login</title>
|
||||||
|
<link rel="apple-touch-icon" sizes="57x57" href="assets/favicon/apple-icon-57x57.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="60x60" href="assets/favicon/apple-icon-60x60.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="72x72" href="assets/favicon/apple-icon-72x72.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="76x76" href="assets/favicon/apple-icon-76x76.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="114x114" href="assets/favicon/apple-icon-114x114.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="120x120" href="assets/favicon/apple-icon-120x120.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="144x144" href="assets/favicon/apple-icon-144x144.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="152x152" href="assets/favicon/apple-icon-152x152.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="assets/favicon/apple-icon-180x180.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="assets/favicon/android-icon-192x192.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="96x96" href="assets/favicon/favicon-96x96.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="assets/favicon/favicon-16x16.png">
|
||||||
|
<link rel="manifest" href="assets/favicon/manifest.json">
|
||||||
|
<meta name="msapplication-TileColor" content="#ffffff">
|
||||||
|
<meta name="msapplication-TileImage" content="assets/favicon/ms-icon-144x144.png">
|
||||||
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
<!-- Vendors styles-->
|
||||||
|
<link rel="stylesheet" href="vendors/simplebar/css/simplebar.css">
|
||||||
|
<link rel="stylesheet" href="css/vendors/simplebar.css">
|
||||||
|
<!-- Main styles for this application-->
|
||||||
|
<link href="css/style.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="bg-light min-vh-100 d-flex flex-row align-items-center">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="card-group d-block d-md-flex row">
|
||||||
|
<div class="card p-4 mb-0">
|
||||||
|
<form class="card-body" method="POST">
|
||||||
|
<h1>Login</h1>
|
||||||
|
<p class="text-medium-emphasis mb-4">Sign In to your router</p>
|
||||||
|
<div class="input-group mb-3"><span class="input-group-text">
|
||||||
|
<svg class="icon">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-user"></use>
|
||||||
|
</svg></span>
|
||||||
|
<input type="text" name="username" class="form-control @invalidClass" placeholder="Username" value="@username">
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-4"><span class="input-group-text">
|
||||||
|
<svg class="icon">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-lock-locked"></use>
|
||||||
|
</svg></span>
|
||||||
|
<input type="password" name="password" class="form-control @invalidClass" placeholder="Password">
|
||||||
|
@if (invalid == true) {
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
Invalid username or password!
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6">
|
||||||
|
<button class="btn btn-primary px-4" type="submit">Login</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- CoreUI and necessary plugins-->
|
||||||
|
<script src="vendors/coreui/coreui/js/coreui.bundle.min.js"></script>
|
||||||
|
<script src="vendors/simplebar/js/simplebar.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
82
Pages/Login.cshtml.cs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Pages
|
||||||
|
{
|
||||||
|
[IgnoreAntiforgeryToken(Order = 1001)]
|
||||||
|
public class LoginModel : PageModel
|
||||||
|
{
|
||||||
|
public IActionResult OnGet()
|
||||||
|
{
|
||||||
|
if (HttpContext.User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
return RedirectToPage("Index");
|
||||||
|
}
|
||||||
|
return Page();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnPostAsync(string username, string password)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string MT_IP = Environment.GetEnvironmentVariable("MT_IP");
|
||||||
|
string MT_USER = Environment.GetEnvironmentVariable("MT_USER");
|
||||||
|
string MT_PASS = Environment.GetEnvironmentVariable("MT_PASS");
|
||||||
|
|
||||||
|
if (username == MT_USER && password == MT_PASS)
|
||||||
|
{
|
||||||
|
HttpClientHandler handler = new()
|
||||||
|
{
|
||||||
|
ServerCertificateCustomValidationCallback = delegate { return true; }
|
||||||
|
};
|
||||||
|
using HttpClient httpClient = new(handler);
|
||||||
|
using var request = new HttpRequestMessage(new HttpMethod("GET"), $"https://{MT_IP}/rest/");
|
||||||
|
string base64authorization = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{MT_USER}:{MT_PASS}"));
|
||||||
|
request.Headers.TryAddWithoutValidation("Authorization", $"Basic {base64authorization}");
|
||||||
|
|
||||||
|
HttpResponseMessage response = await httpClient.SendAsync(request);
|
||||||
|
var resp = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
var claims = new List<Claim>
|
||||||
|
{
|
||||||
|
new Claim(ClaimTypes.Role, "Administrator"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var claimsIdentity = new ClaimsIdentity(
|
||||||
|
claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
|
||||||
|
var authProperties = new AuthenticationProperties
|
||||||
|
{
|
||||||
|
AllowRefresh = true,
|
||||||
|
IsPersistent = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
|
||||||
|
await HttpContext.SignInAsync(
|
||||||
|
CookieAuthenticationDefaults.AuthenticationScheme,
|
||||||
|
claimsPrincipal,
|
||||||
|
authProperties);
|
||||||
|
HttpContext.User = claimsPrincipal;
|
||||||
|
|
||||||
|
return RedirectToPage("Index");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ViewData["Username"] = username;
|
||||||
|
ViewData["Invalid"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ViewData["body"] = ex.Message;
|
||||||
|
Console.WriteLine(ex.Message);
|
||||||
|
}
|
||||||
|
return Page();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
Pages/Logout.cshtml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
@page
|
||||||
|
@model MTWireGuard.Pages.LogoutModel
|
||||||
|
@{
|
||||||
|
}
|
30
Pages/Logout.cshtml.cs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using MTWireGuard.Repositories;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Pages
|
||||||
|
{
|
||||||
|
public class LogoutModel : PageModel
|
||||||
|
{
|
||||||
|
private readonly IMikrotikRepository API;
|
||||||
|
|
||||||
|
public LogoutModel(IMikrotikRepository mikrotik)
|
||||||
|
{
|
||||||
|
API = mikrotik;
|
||||||
|
}
|
||||||
|
public async Task<IActionResult> OnGetAsync(string returnUrl = "Login")
|
||||||
|
{
|
||||||
|
// Clear the existing external cookie
|
||||||
|
await HttpContext.SignOutAsync(
|
||||||
|
CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
/*
|
||||||
|
var sessionId = await MTAPIHandler.GetCurrentSessionID();
|
||||||
|
var remove = await MTAPIHandler.KillJob(sessionId);*/
|
||||||
|
var sessionId = await API.GetCurrentSessionID();
|
||||||
|
var kill = await API.KillJob(sessionId);
|
||||||
|
return RedirectToPage(returnUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
Pages/Logs.cshtml
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
@page
|
||||||
|
@using MTWireGuard.Models.Mikrotik
|
||||||
|
@model MTWireGuard.Pages.LogsModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Event Logs";
|
||||||
|
ViewData["Breadcrumb"] = "Logs";
|
||||||
|
var Logs = (List<LogViewModel>)ViewData["Logs"];
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="">
|
||||||
|
<table class="table table-bordered border-secondary logs-table">
|
||||||
|
<thead class="table-dark fw-semibold">
|
||||||
|
<tr class="align-middle">
|
||||||
|
<th class="text-center">
|
||||||
|
<svg class="icon">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-people"></use>
|
||||||
|
</svg>
|
||||||
|
</th>
|
||||||
|
<th>Message</th>
|
||||||
|
<th>Topics</th>
|
||||||
|
<th>Time</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var log in Logs)
|
||||||
|
{
|
||||||
|
<tr class="align-middle">
|
||||||
|
<td class="text-center">
|
||||||
|
<span class="text-medium-emphasis">#@log.Id</span>
|
||||||
|
</td>
|
||||||
|
<td>@log.Message</td>
|
||||||
|
<td>
|
||||||
|
@foreach (var topic in log.Topics)
|
||||||
|
{
|
||||||
|
switch (topic.ToLower())
|
||||||
|
{
|
||||||
|
case "system":
|
||||||
|
<span class="badge text-bg-dark mx-1">@topic</span>
|
||||||
|
break;
|
||||||
|
case "info":
|
||||||
|
<span class="badge text-bg-info mx-1">@topic</span>
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
case "critical":
|
||||||
|
<span class="badge text-bg-danger mx-1">@topic</span>
|
||||||
|
break;
|
||||||
|
case "account":
|
||||||
|
<span class="badge text-bg-secondary mx-1">@topic</span>
|
||||||
|
break;
|
||||||
|
case "dhcp":
|
||||||
|
case "ppp":
|
||||||
|
case "l2tp":
|
||||||
|
case "pptp":
|
||||||
|
case "sstp":
|
||||||
|
<span class="badge text-bg-light border border-secondary mx-1">@topic</span>
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
<span class="badge text-bg-light border border-secondary mx-1">@topic</span>
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>@log.Time</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script>
|
||||||
|
</script>
|
||||||
|
}
|
21
Pages/Logs.cshtml.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using MTWireGuard.Repositories;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Pages
|
||||||
|
{
|
||||||
|
public class LogsModel : PageModel
|
||||||
|
{
|
||||||
|
private readonly IMikrotikRepository API;
|
||||||
|
|
||||||
|
public LogsModel(IMikrotikRepository mikrotik)
|
||||||
|
{
|
||||||
|
API = mikrotik;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnGetAsync()
|
||||||
|
{
|
||||||
|
ViewData["Logs"] = await API.GetLogsAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
Pages/Modals/QRModal.razor
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<div class="modal fade" id="QRModal" tabindex="-1" aria-labelledby="QRModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="QRModalLabel">Scan This</h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<img id="QRImage" src="" class="img-thumbnail" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
}
|
8
Pages/Privacy.cshtml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
@page
|
||||||
|
@model PrivacyModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Privacy Policy";
|
||||||
|
}
|
||||||
|
<h1>@ViewData["Title"]</h1>
|
||||||
|
|
||||||
|
<p>Use this page to detail your site's privacy policy.</p>
|
19
Pages/Privacy.cshtml.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Pages
|
||||||
|
{
|
||||||
|
public class PrivacyModel : PageModel
|
||||||
|
{
|
||||||
|
private readonly ILogger<PrivacyModel> _logger;
|
||||||
|
|
||||||
|
public PrivacyModel(ILogger<PrivacyModel> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnGet()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
83
Pages/Servers.cshtml
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
@page "{handler?}"
|
||||||
|
@using MTWireGuard.Models.Mikrotik
|
||||||
|
@model MTWireGuard.Pages.ServersModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Servers";
|
||||||
|
ViewData["Breadcrumb"] = "Interfaces";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table border mb-0">
|
||||||
|
<thead class="table-light fw-semibold">
|
||||||
|
<tr class="align-middle">
|
||||||
|
<th class="text-center">
|
||||||
|
<svg class="icon">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-people"></use>
|
||||||
|
</svg>
|
||||||
|
</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Listen Port</th>
|
||||||
|
<th>MTU</th>
|
||||||
|
<th>Public-Key</th>
|
||||||
|
<th>Traffic</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-2">
|
||||||
|
<div class="col-12">
|
||||||
|
@await Component.InvokeAsync("CreateServerForm")
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete Modal -->
|
||||||
|
@await Component.InvokeAsync("DeleteModal", new {
|
||||||
|
IsServer = true
|
||||||
|
})
|
||||||
|
|
||||||
|
<!-- Edit Modal -->
|
||||||
|
@await Component.InvokeAsync("UpdateServerModal")
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script src="js/wireguard.js"></script>
|
||||||
|
<script src="js/wgelements.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function (event) {
|
||||||
|
fetch("/Servers/getAll")
|
||||||
|
.then((response) => response.text())
|
||||||
|
.then((data) => {
|
||||||
|
document.querySelector('table tbody').innerHTML = data;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll(".delete-btn").forEach(form => {
|
||||||
|
form.addEventListener('click', function (event) {
|
||||||
|
let row = event.target.closest('tr');
|
||||||
|
let Id = row.getAttribute('data-id');
|
||||||
|
document.querySelector('#DeleteModal input[name="Id"]').value = Id;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll(".update-btn").forEach(form => {
|
||||||
|
form.addEventListener('click', function (event) {
|
||||||
|
document.querySelector('#EditModal form').reset();
|
||||||
|
let row = event.target.closest('tr');
|
||||||
|
let Id = row.getAttribute('data-id');
|
||||||
|
let name = row.querySelector('td:nth-child(2)').innerText;
|
||||||
|
let port = row.querySelector('td:nth-child(3)').innerText;
|
||||||
|
let mtu = row.querySelector('td:nth-child(4)').innerText;
|
||||||
|
let publicKey = row.querySelector('td:nth-child(5)').innerText;
|
||||||
|
document.querySelector('#EditModal input[name="ID"]').value = Id;
|
||||||
|
document.querySelector('#EditModal input[name="Name"]').placeholder = name;
|
||||||
|
document.querySelector('#EditModal input[name="Port"]').placeholder = port;
|
||||||
|
document.querySelector('#EditModal input[name="MTU"]').placeholder = mtu;
|
||||||
|
document.querySelector('#EditModal input[id$="PubKey"]').placeholder = publicKey;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
}
|
73
Pages/Servers.cshtml.cs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
using AutoMapper;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using MTWireGuard.Models;
|
||||||
|
using MTWireGuard.Models.Mikrotik;
|
||||||
|
using MTWireGuard.Models.Requests;
|
||||||
|
using MTWireGuard.Models.Responses;
|
||||||
|
using MTWireGuard.Repositories;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Pages
|
||||||
|
{
|
||||||
|
public class ServersModel : PageModel
|
||||||
|
{
|
||||||
|
private readonly IMikrotikRepository API;
|
||||||
|
private readonly IMapper mapper;
|
||||||
|
|
||||||
|
public ServersModel(IMikrotikRepository mikrotik, IMapper mapper)
|
||||||
|
{
|
||||||
|
API = mikrotik;
|
||||||
|
this.mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnGetAsync()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PartialViewResult> OnGetGetAll()
|
||||||
|
{
|
||||||
|
var servers = await API.GetServersAsync();
|
||||||
|
var traffics = await API.GetServersTraffic();
|
||||||
|
return Partial("_ServersTable", (servers, traffics));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnPostCreateAsync(CreateServerRequest request)
|
||||||
|
{
|
||||||
|
var model = mapper.Map<ServerCreateModel>(request);
|
||||||
|
var make = await API.CreateServer(model);
|
||||||
|
string status = make.Code == "200" ? "success" : "danger";
|
||||||
|
string title = make.Code == "200" ? make.Title : $"[{make.Code}] {make.Title}";
|
||||||
|
return new ToastResult(title, make.Description, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnPostDelete(DeleteRequest request)
|
||||||
|
{
|
||||||
|
var delete = await API.DeleteServer(request.Id);
|
||||||
|
string status = delete.Code == "200" ? "success" : "danger";
|
||||||
|
string title = delete.Code == "200" ? delete.Title : $"[{delete.Code}] {delete.Title}";
|
||||||
|
return new ToastResult(title, delete.Description, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnPostUpdate(UpdateServerRequest request)
|
||||||
|
{
|
||||||
|
var model = mapper.Map<ServerUpdateModel>(request);
|
||||||
|
var update = await API.UpdateServer(model);
|
||||||
|
string status = update.Code == "200" ? "success" : "danger";
|
||||||
|
string title = update.Code == "200" ? update.Title : $"[{update.Code}] {update.Title}";
|
||||||
|
return new ToastResult(title, update.Description, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnGetEnableAsync(ChangeStateRequest request)
|
||||||
|
{
|
||||||
|
CreationResult result;
|
||||||
|
if (!request.Enabled)
|
||||||
|
result = await API.EnableServer(request.Id);
|
||||||
|
else
|
||||||
|
result = await API.DisableServer(request.Id);
|
||||||
|
string status = result.Code == "200" ? "success" : "danger";
|
||||||
|
string title = result.Code == "200" ? result.Title : $"[{result.Code}] {result.Title}";
|
||||||
|
return new ToastResult(title, result.Description, status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
145
Pages/Settings.cshtml
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
@page
|
||||||
|
@using MTWireGuard.Models.Mikrotik
|
||||||
|
@model SettingsModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "System Settings";
|
||||||
|
ViewData["Breadcrumb"] = "Settings";
|
||||||
|
var servers = (List<WGServerViewModel>)ViewData["servers"];
|
||||||
|
var info = (MTInfoViewModel)ViewData["info"];
|
||||||
|
var HDDPercent = (info.UsedHDDBytes * 100) / info.TotalHDDBytes;
|
||||||
|
var RAMPercent = (info.UsedRAMBytes * 100) / info.TotalRAMBytes;
|
||||||
|
var name = ViewData["name"].ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-6 col-sm-4 col-lg-4">
|
||||||
|
<div class="card text-white bg-info">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="fs-4 fw-semibold">@info.FreeHDD</div>
|
||||||
|
<div>Free Disk Space</div>
|
||||||
|
<div class="progress progress-white progress-thin my-2">
|
||||||
|
<div class="progress-bar" role="progressbar" style="width: @HDDPercent%" aria-valuenow="@HDDPercent" aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
|
</div><small class="text-medium-emphasis-inverse">Used @info.UsedHDD of @info.TotalHDD</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 col-sm-4 col-lg-4">
|
||||||
|
<div class="card text-white bg-warning">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="fs-4 fw-semibold">@info.FreeRAM</div>
|
||||||
|
<div>Free RAM Space</div>
|
||||||
|
<div class="progress progress-white progress-thin my-2">
|
||||||
|
<div class="progress-bar" role="progressbar" style="width: @RAMPercent%" aria-valuenow="@RAMPercent" aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
|
</div><small class="text-medium-emphasis-inverse">Used @info.UsedRAM of @info.TotalRAM</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 col-sm-4 col-lg-4">
|
||||||
|
<div class="card text-white bg-danger">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="text-medium-emphasis-inverse text-end mb-2">
|
||||||
|
<svg class="icon icon-xxl">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-speedometer"></use>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="fs-4 fw-semibold">@info.UPTime</div>
|
||||||
|
<small class="text-medium-emphasis-inverse text-uppercase fw-semibold">UPTime</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row my-2">
|
||||||
|
<div class="col-sm-6 col-lg-4">
|
||||||
|
<div class="card mb-4" style="--cui-card-cap-bg: #7D50B9">
|
||||||
|
<div class="card-header position-relative d-flex justify-content-center align-items-center">
|
||||||
|
<svg class="icon icon-3xl text-white my-4">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-router"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="chart-wrapper position-absolute top-0 start-0 w-100 h-100">
|
||||||
|
<canvas id="social-box-chart-1" height="90"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body row text-center">
|
||||||
|
<div class="col">
|
||||||
|
<div class="fs-5 fw-semibold">@info.Architecture</div>
|
||||||
|
<div class="text-uppercase text-medium-emphasis small">Arch</div>
|
||||||
|
</div>
|
||||||
|
<div class="vr"></div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="fs-5 fw-semibold">@info.Platform</div>
|
||||||
|
<div class="text-uppercase text-medium-emphasis small">Platform</div>
|
||||||
|
</div>
|
||||||
|
<div class="vr"></div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="fs-5 fw-semibold">@info.BoardName</div>
|
||||||
|
<div class="text-uppercase text-medium-emphasis small">Board</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer row bg-light px-3 py-2 m-0">
|
||||||
|
<a class="btn-block text-medium-emphasis d-flex justify-content-center align-items-center text-decoration-none col" href="#">
|
||||||
|
<span class="small fw-semibold">Identity: @name</span>
|
||||||
|
</a>
|
||||||
|
<div class="vr"></div>
|
||||||
|
<a class="btn-block text-medium-emphasis d-flex justify-content-center align-items-center text-decoration-none col" href="#">
|
||||||
|
<span class="small fw-semibold">Version: @info.Version</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 col-lg-4">
|
||||||
|
<div class="card mb-4" style="--cui-card-cap-bg: #4FA3A5">
|
||||||
|
<div class="card-header position-relative d-flex justify-content-center align-items-center">
|
||||||
|
<svg class="icon icon-3xl text-white my-4">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-aperture"></use>
|
||||||
|
</svg>
|
||||||
|
<div class="chart-wrapper position-absolute top-0 start-0 w-100 h-100">
|
||||||
|
<canvas id="social-box-chart-1" height="90"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body row text-center">
|
||||||
|
<div class="col">
|
||||||
|
<div class="fs-5 fw-semibold">@info.CPU</div>
|
||||||
|
<div class="text-uppercase text-medium-emphasis small">CPU</div>
|
||||||
|
</div>
|
||||||
|
<div class="vr"></div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="fs-5 fw-semibold">@info.CPUCount</div>
|
||||||
|
<div class="text-uppercase text-medium-emphasis small">Cores</div>
|
||||||
|
</div>
|
||||||
|
<div class="vr"></div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="fs-5 fw-semibold">@info.CPUFrequency</div>
|
||||||
|
<div class="text-uppercase text-medium-emphasis small">Frequency</div>
|
||||||
|
</div>
|
||||||
|
<div class="vr"></div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="fs-5 fw-semibold">@info.CPULoad %</div>
|
||||||
|
<div class="text-uppercase text-medium-emphasis small">Usage</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--<div class="col-sm-6 col-lg-4">
|
||||||
|
<div class="card text-bg-dark">
|
||||||
|
<div class="card-header">
|
||||||
|
Fix MSS
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Fix problems loading apps like Instagram</h5>
|
||||||
|
<p class="card-text">This will fix problems with loading some applications such as Instagram by changing TCP MSS.</p>
|
||||||
|
<select class="form-select" aria-label="Default select example">
|
||||||
|
@foreach (var server in servers)
|
||||||
|
{
|
||||||
|
<option value="@server.Id">@server.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
<button type="button" class="btn btn-info mt-2">
|
||||||
|
<span class="cil-contrast btn-icon mr-2"></span> Fix
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>-->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
}
|
31
Pages/Settings.cshtml.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using MTWireGuard.Repositories;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Pages
|
||||||
|
{
|
||||||
|
public class SettingsModel : PageModel
|
||||||
|
{
|
||||||
|
private readonly IMikrotikRepository API;
|
||||||
|
|
||||||
|
public SettingsModel(IMikrotikRepository mikrotik)
|
||||||
|
{
|
||||||
|
API = mikrotik;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnGetAsync()
|
||||||
|
{
|
||||||
|
ViewData["servers"] = await API.GetServersAsync();
|
||||||
|
ViewData["info"] = await API.GetInfo();
|
||||||
|
var identity = await API.GetName();
|
||||||
|
ViewData["name"] = identity.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnGetGetInfo()
|
||||||
|
{
|
||||||
|
var info = await API.GetInfo();
|
||||||
|
return new JsonResult(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
Pages/Shared/_ClientsTable.cshtml
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
@using MTWireGuard.Models.Mikrotik
|
||||||
|
@model List<WGPeerViewModel>
|
||||||
|
|
||||||
|
@foreach (var user in Model)
|
||||||
|
{
|
||||||
|
<tr class="align-middle" data-id="@user.Id">
|
||||||
|
<td class="text-center">
|
||||||
|
<span class="text-medium-emphasis">#@user.Id</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>@user.Name</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if (user.Interface.StartsWith('*'))
|
||||||
|
{
|
||||||
|
<div class="text-danger fst-italic">@user.Interface</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div>@user.Interface</div>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>@user.Address</div>
|
||||||
|
@if (!user.CurrentAddress.EndsWith(":0"))
|
||||||
|
{
|
||||||
|
<div class="small text-medium-emphasis">@user.CurrentAddress</div>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="text-truncate">@user.PublicKey</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>
|
||||||
|
<svg class="icon">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-data-transfer-up"></use>
|
||||||
|
</svg>
|
||||||
|
@user.Upload / @user.Download
|
||||||
|
<svg class="icon">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-data-transfer-down"></use>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="row">
|
||||||
|
@if (!user.IsEnabled)
|
||||||
|
{
|
||||||
|
<span class="col badge rounded-pill text-bg-danger">Disabled</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="col badge rounded-pill text-bg-success">Enabled</span>
|
||||||
|
}
|
||||||
|
@if (user.IsDifferent)
|
||||||
|
{
|
||||||
|
<span class="col badge rounded-pill text-bg-danger mx-md-1">Not Synced</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="col badge rounded-pill text-bg-success mx-md-1">Synced</span>
|
||||||
|
}
|
||||||
|
@if (user.CurrentAddress.EndsWith(":0"))
|
||||||
|
{
|
||||||
|
<span class="col badge rounded-pill text-bg-danger">Offline</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="col badge rounded-pill text-bg-success">Online</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="dropdown position-static">
|
||||||
|
<button class="btn btn-transparent p-0" type="button" data-coreui-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<svg class="icon">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-options"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-end">
|
||||||
|
@if (user.IsDifferent)
|
||||||
|
{
|
||||||
|
<button class="dropdown-item sync-btn" data-coreui-toggle="modal" data-coreui-target="#SyncModal">Sync</button>
|
||||||
|
}
|
||||||
|
<button class="dropdown-item" data-coreui-toggle="modal" data-coreui-target="#QRModal">QR</button>
|
||||||
|
<a class="dropdown-item" asp-page="Clients" asp-page-handler="DownloadTunnel" asp-route-id="@user.Id">Download</a>
|
||||||
|
<button class="dropdown-item update-btn" data-coreui-toggle="modal" data-coreui-target="#EditModal">Edit</button>
|
||||||
|
@{
|
||||||
|
var enabled = user.IsEnabled;
|
||||||
|
string enableORdisable = enabled ? "Disable" : "Enable";
|
||||||
|
}
|
||||||
|
<a class="dropdown-item" asp-page="Clients" asp-page-handler="Enable" asp-route-ID="@user.Id" asp-route-IsEnabled="@enabled" data-target="_self" onclick="ddBtnClick(event);">@enableORdisable</a>
|
||||||
|
<button class="dropdown-item delete-btn text-danger" data-coreui-toggle="modal" data-coreui-target="#DeleteModal">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
215
Pages/Shared/_Layout.cshtml
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
@using MTWireGuard.Repositories
|
||||||
|
@inject IMikrotikRepository API;
|
||||||
|
@{
|
||||||
|
var logs = await API.GetLogsAsync();
|
||||||
|
var info = await API.GetInfo();
|
||||||
|
var ramUsed = 100 - info.FreeRAMPercentage;
|
||||||
|
var hddUsed = 100 - info.FreeHDDPercentage;
|
||||||
|
string cpuColor, ramColor, hddColor;
|
||||||
|
|
||||||
|
if (info.CPULoad <= 25) cpuColor = "bg-info-gradient";
|
||||||
|
else if (info.CPULoad <= 75) cpuColor = "bg-warning-gradient";
|
||||||
|
else cpuColor = "bg-danger-gradient";
|
||||||
|
|
||||||
|
if (hddUsed <= 25) hddColor = "bg-info-gradient";
|
||||||
|
else if (hddUsed <= 75) hddColor = "bg-warning-gradient";
|
||||||
|
else hddColor = "bg-danger-gradient";
|
||||||
|
|
||||||
|
if (ramUsed <= 25) ramColor = "bg-info-gradient";
|
||||||
|
else if (ramUsed<= 75) ramColor = "bg-warning-gradient";
|
||||||
|
else ramColor = "bg-danger-gradient";
|
||||||
|
}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<base href="./">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<meta name="description" content="Mikrotik Wireguard management panel">
|
||||||
|
<meta name="author" content="Kazem Ma79">
|
||||||
|
<title>@ViewData["Title"]</title>
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="assets/favicon/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="assets/favicon/android-icon-36x36.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="assets/favicon/android-icon-48x48.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="assets/favicon/android-icon-72x72.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="assets/favicon/android-icon-96x96.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="assets/favicon/android-icon-144x144.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="assets/favicon/android-icon-192x192.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="assets/favicon/android-icon-256x256.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="assets/favicon/android-icon-384x384.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="assets/favicon/android-icon-512x512.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="assets/favicon/favicon-16x16.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="96x96" href="assets/favicon/favicon-96x96.png">
|
||||||
|
<link rel="manifest" href="assets/favicon/manifest.json">
|
||||||
|
<meta name="msapplication-TileColor" content="#ffffff">
|
||||||
|
<meta name="msapplication-TileImage" content="assets/favicon/mstile-150x150.png">
|
||||||
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
<!-- Simplebar styles-->
|
||||||
|
<link rel="stylesheet" href="vendors/simplebar/css/simplebar.css">
|
||||||
|
<link rel="stylesheet" href="css/vendors/simplebar.css">
|
||||||
|
<!-- Main styles-->
|
||||||
|
<link href="css/style.css" rel="stylesheet">
|
||||||
|
<link href="css/gradients.css" rel="stylesheet">
|
||||||
|
<link href="css/custom.css" rel="stylesheet">
|
||||||
|
<!-- Page Styles (if any)-->
|
||||||
|
@await RenderSectionAsync("Styles", required: false)
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="sidebar sidebar-dark sidebar-fixed" id="sidebar">
|
||||||
|
<div class="sidebar-brand d-none d-md-flex">
|
||||||
|
<svg class="sidebar-brand-full" width="118" height="46" alt="Wireguard Logo">
|
||||||
|
<image xlink:href="assets/brand/wireguard.svg" width="118" height="46" />
|
||||||
|
</svg>
|
||||||
|
<svg class="sidebar-brand-narrow" width="46" height="46" alt="Wireguard Logo">
|
||||||
|
<image xlink:href="assets/brand/wireguard-mini.svg" width="46" height="46"></image>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<ul class="sidebar-nav" data-coreui="navigation" data-simplebar="">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" asp-page="/">
|
||||||
|
<svg class="nav-icon">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-speedometer"></use>
|
||||||
|
</svg> Dashboard
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-title">Wireguard</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" asp-page="/Servers">
|
||||||
|
<svg class="nav-icon">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-3d"></use>
|
||||||
|
</svg> Servers
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" asp-page="/Clients">
|
||||||
|
<svg class="nav-icon">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-devices"></use>
|
||||||
|
</svg> Clients
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-title">System</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" asp-page="/Settings">
|
||||||
|
<svg class="nav-icon">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-apps"></use>
|
||||||
|
</svg> Settings
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-title mt-auto">System Utilization</li>
|
||||||
|
<li class="nav-item px-3 d-narrow-none">
|
||||||
|
<div class="text-uppercase mb-1"><small><b>CPU Usage</b></small></div>
|
||||||
|
<div class="progress progress-thin">
|
||||||
|
<div class="progress-bar @cpuColor" role="progressbar" style="width: @info.CPULoad%" aria-valuenow="@info.CPULoad" aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
|
</div><small class="text-medium-emphasis-inverse">@info.CPULoad% is using.</small>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item px-3 d-narrow-none">
|
||||||
|
<div class="text-uppercase mb-1"><small><b>Memory Usage</b></small></div>
|
||||||
|
<div class="progress progress-thin">
|
||||||
|
<div class="progress-bar @ramColor" role="progressbar" style="width: @ramUsed%" aria-valuenow="@ramUsed" aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
|
</div><small class="text-medium-emphasis-inverse">@info.UsedRAM/@info.TotalRAM</small>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item px-3 mb-3 d-narrow-none">
|
||||||
|
<div class="text-uppercase mb-1"><small><b>Disk Usage</b></small></div>
|
||||||
|
<div class="progress progress-thin">
|
||||||
|
<div class="progress-bar @hddColor" role="progressbar" style="width: @hddUsed%" aria-valuenow="@hddUsed" aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
|
</div><small class="text-medium-emphasis-inverse">@info.UsedHDD/@info.TotalHDD</small>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<button class="sidebar-toggler" type="button" data-coreui-toggle="unfoldable"></button>
|
||||||
|
</div>
|
||||||
|
<div class="wrapper d-flex flex-column min-vh-100 bg-light">
|
||||||
|
<header class="header header-sticky mb-4">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<button class="header-toggler px-md-0 me-md-3" type="button" onclick="coreui.Sidebar.getInstance(document.querySelector('#sidebar')).toggle()">
|
||||||
|
<svg class="icon icon-lg">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-menu"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<a class="header-brand d-md-none" href="#">
|
||||||
|
<svg width="118" height="46" alt="Wireguard Logo">
|
||||||
|
<use xlink:href="assets/brand/wireguard.svg"></use>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<ul class="header-nav d-none d-md-flex">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" asp-page="Index">Dashboard</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" asp-page="Clients">Users</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" asp-page="Settings">Settings</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="header-nav ms-auto">
|
||||||
|
<li class="nav-item dropdown d-md-down-none">
|
||||||
|
<a class="nav-link" data-coreui-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<span class="d-inline-block my-1 mx-2 position-relative">
|
||||||
|
<svg class="icon icon-lg">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-list-rich"></use>
|
||||||
|
</svg>
|
||||||
|
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
|
||||||
|
<span class="visually-hidden">New logs</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-end dropdown-menu-lg py-0" style="max-width: 30vw; max-height: 70vh; overflow-y: auto;">
|
||||||
|
<component type="typeof(Components.LogsView)" render-mode="Static" param-logs="@logs" />
|
||||||
|
<a class="dropdown-item text-center border-top" asp-page="Logs"><strong>View all messages</strong></a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="header-nav ms-3">
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link py-0" data-coreui-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<div class="avatar avatar-md"><img class="avatar-img" src="assets/img/avatars/admin.png" alt="kazem_ma79@yahoo.com"></div>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-end py-0 mb-0">
|
||||||
|
<div class="dropdown-header bg-light py-2">
|
||||||
|
<div class="fw-semibold">Account</div>
|
||||||
|
</div>
|
||||||
|
<a class="dropdown-item" asp-page="Logout">
|
||||||
|
<svg class="icon">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-account-logout"></use>
|
||||||
|
</svg> Logout
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="header-divider"></div>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb my-0 ms-2">
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<span>Dashboard</span>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item active">
|
||||||
|
<span>@ViewData["Breadcrumb"]</span>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="body flex-grow-1 px-3">
|
||||||
|
<div class="container-lg">
|
||||||
|
@RenderBody()
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer class="footer">
|
||||||
|
<div><a href="https://mtwg.techgarage.ir/">Mikrotik WireGuard Dashboard</a> © 2023 Techgarage.</div>
|
||||||
|
<div class="ms-md-auto">Powered by <a href="https://coreui.io/">CoreUI</a></div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
|
<component type="typeof(Components.ToastContainer)" render-mode="Static" />
|
||||||
|
<!-- CoreUI and necessary plugins-->
|
||||||
|
<script src="vendors/coreui/coreui/js/coreui.bundle.min.js"></script>
|
||||||
|
<script src="vendors/simplebar/js/simplebar.min.js"></script>
|
||||||
|
<script src="js/script.js"></script>
|
||||||
|
|
||||||
|
@await RenderSectionAsync("Scripts", required: false)
|
||||||
|
</body>
|
||||||
|
</html>
|
48
Pages/Shared/_Layout.cshtml.css
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
|
||||||
|
for details on configuring this project to bundle and minify static web assets. */
|
||||||
|
|
||||||
|
a.navbar-brand {
|
||||||
|
white-space: normal;
|
||||||
|
text-align: center;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #0077cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #1b6ec2;
|
||||||
|
border-color: #1861ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #1b6ec2;
|
||||||
|
border-color: #1861ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-top {
|
||||||
|
border-top: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
.border-bottom {
|
||||||
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-shadow {
|
||||||
|
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.accept-policy {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 60px;
|
||||||
|
}
|
79
Pages/Shared/_ServersTable.cshtml
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
@using MTWireGuard.Models.Mikrotik
|
||||||
|
@model (List<WGServerViewModel>, List<ServerTrafficViewModel>)
|
||||||
|
@{
|
||||||
|
var servers = Model.Item1;
|
||||||
|
var traffic = Model.Item2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@foreach (var server in servers)
|
||||||
|
{
|
||||||
|
<tr class="align-middle" data-id="@server.Id">
|
||||||
|
<td class="text-center">
|
||||||
|
<span class="text-medium-emphasis">#@server.Id</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>@server.Name</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>@server.ListenPort</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>@server.MTU</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>@server.PublicKey</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@{
|
||||||
|
var enabled = server.IsEnabled;
|
||||||
|
string enableORdisable = enabled ? "Disable" : "Enable";
|
||||||
|
var currentServerTraffic = traffic.Find(s => s.Name == server.Name);
|
||||||
|
var up = currentServerTraffic.Upload;
|
||||||
|
var down = currentServerTraffic.Download;
|
||||||
|
}
|
||||||
|
<div>
|
||||||
|
<svg class="icon">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-data-transfer-up"></use>
|
||||||
|
</svg>
|
||||||
|
@up / @down
|
||||||
|
<svg class="icon">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-data-transfer-down"></use>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@switch (enabled)
|
||||||
|
{
|
||||||
|
case true:
|
||||||
|
<span class="badge rounded-pill text-bg-success">Enabled</span>
|
||||||
|
break;
|
||||||
|
case false:
|
||||||
|
<span class="badge rounded-pill text-bg-danger">Disabled</span>
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
@switch (server.Running)
|
||||||
|
{
|
||||||
|
case true:
|
||||||
|
<span class="badge rounded-pill text-bg-success">Running</span>
|
||||||
|
break;
|
||||||
|
case false:
|
||||||
|
<span class="badge rounded-pill text-bg-secondary">Not Running</span>
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="dropdown position-static">
|
||||||
|
<button class="btn btn-transparent p-0" type="button" data-coreui-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<svg class="icon">
|
||||||
|
<use xlink:href="vendors/coreui/icons/svg/free.svg#cil-options"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-end">
|
||||||
|
<a class="dropdown-item" asp-page="Servers" asp-page-handler="Enable" asp-route-ID="@server.Id" asp-route-IsEnabled="@enabled" data-target="_self" onclick="ddBtnClick(event);">@enableORdisable</a>
|
||||||
|
<button class="dropdown-item update-btn" data-coreui-toggle="modal" data-coreui-target="#EditModal">Edit</button>
|
||||||
|
<button class="dropdown-item delete-btn text-danger" data-coreui-toggle="modal" data-coreui-target="#DeleteModal" data-id="@server.Id">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
3
Pages/_ViewImports.cshtml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
@using MTWireGuard
|
||||||
|
@namespace MTWireGuard.Pages
|
||||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
3
Pages/_ViewStart.cshtml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
@{
|
||||||
|
Layout = "_Layout";
|
||||||
|
}
|
118
Program.cs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
using AutoMapper;
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using MTWireGuard;
|
||||||
|
using MTWireGuard.Mapper;
|
||||||
|
using MTWireGuard.Middlewares;
|
||||||
|
using MTWireGuard.Repositories;
|
||||||
|
using MTWireGuard.Services;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
#region Error Handling
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledExceptionHandler);
|
||||||
|
|
||||||
|
static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
|
||||||
|
{
|
||||||
|
Exception ex = e.ExceptionObject as Exception;
|
||||||
|
if (e.IsTerminating)
|
||||||
|
Debug.WriteLine($"[+] {ex.Message}");
|
||||||
|
else
|
||||||
|
Debug.WriteLine($"[-] {ex.Message}");
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
using DBContext context = new();
|
||||||
|
|
||||||
|
// Add services to the container.
|
||||||
|
builder.Services.AddRazorPages().AddRazorPagesOptions(o =>
|
||||||
|
{
|
||||||
|
//o.Conventions.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute());
|
||||||
|
o.Conventions.AuthorizeFolder("/");
|
||||||
|
o.Conventions.AllowAnonymousToPage("/Login");
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddAntiforgery(o =>
|
||||||
|
{
|
||||||
|
o.HeaderName = "XSRF-TOKEN";
|
||||||
|
o.FormFieldName = "XSRF-Validation-Token";
|
||||||
|
o.Cookie.Name = "XSRF-Validation";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto Mapper Configurations
|
||||||
|
var mapperConfig = new MapperConfiguration(mc =>
|
||||||
|
{
|
||||||
|
mc.AddProfile(new MappingProfile());
|
||||||
|
mc.AddProfile(new PeerMapping(context));
|
||||||
|
mc.AddProfile(new ServerMapping());
|
||||||
|
});
|
||||||
|
|
||||||
|
IMapper mapper = mapperConfig.CreateMapper();
|
||||||
|
builder.Services.AddSingleton(mapper);
|
||||||
|
|
||||||
|
builder.Services.AddSingleton(context);
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<IMikrotikRepository, MTAPI>();
|
||||||
|
|
||||||
|
//builder.Services.AddScoped<AntiForgeryMiddleware>();
|
||||||
|
|
||||||
|
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
|
||||||
|
{
|
||||||
|
options.ExpireTimeSpan = TimeSpan.FromMinutes(15);
|
||||||
|
options.SlidingExpiration = true;
|
||||||
|
options.LoginPath = "/Login";
|
||||||
|
options.AccessDeniedPath = "/Forbidden";
|
||||||
|
options.Cookie.Name = "Authentication";
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddAuthorization(configure =>
|
||||||
|
{
|
||||||
|
configure.AddPolicy("Administrator", authBuilder =>
|
||||||
|
{
|
||||||
|
authBuilder.RequireRole("Administrator");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.ConfigureApplicationCookie(configure =>
|
||||||
|
{
|
||||||
|
configure.Cookie.Name = "MTWireguard";
|
||||||
|
});
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
|
if (!app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseExceptionHandler("/Error");
|
||||||
|
app.UseHsts();
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Database.EnsureCreated();
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
if (!app.Environment.IsDevelopment())
|
||||||
|
app.UseStaticFiles();
|
||||||
|
else
|
||||||
|
app.UseStaticFiles(new StaticFileOptions()
|
||||||
|
{
|
||||||
|
OnPrepareResponse = context =>
|
||||||
|
{
|
||||||
|
context.Context.Response.Headers.Add("Cache-Control", "no-cache, no-store");
|
||||||
|
context.Context.Response.Headers.Add("Expires", "-1");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.UseDependencyCheck();
|
||||||
|
//app.UseAntiForgery();
|
||||||
|
|
||||||
|
app.UseRouting();
|
||||||
|
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.MapRazorPages();
|
||||||
|
|
||||||
|
app.Run();
|
46
Properties/launchSettings.json
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"MTWireGuard": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"hotReloadEnabled": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
|
"MT_IP": "192.168.0.96",
|
||||||
|
"MT_USER": "admin",
|
||||||
|
"MT_PASS": "",
|
||||||
|
"MT_PUBLIC_IP": "192.168.0.96"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "https://localhost:7220;http://localhost:5220"
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Docker": {
|
||||||
|
"commandName": "Docker",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
|
||||||
|
"publishAllPorts": true,
|
||||||
|
"useSSL": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"MT_IP": "192.168.0.96",
|
||||||
|
"MT_USER": "panel",
|
||||||
|
"MT_PASS": "",
|
||||||
|
"MT_PUBLIC_IP": "192.168.0.96"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:48442",
|
||||||
|
"sslPort": 44372
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
README.md
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# MTWireGuard
|
||||||
|
Documentation available at [HERE](https://mtwireguard.techgarage.ir/Documentation)
|
35
Repositories/IMikrotikRepository.cs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
using MTWireGuard.Models;
|
||||||
|
using MTWireGuard.Models.Mikrotik;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Repositories
|
||||||
|
{
|
||||||
|
public interface IMikrotikRepository : IDisposable
|
||||||
|
{
|
||||||
|
Task<List<LogViewModel>> GetLogsAsync();
|
||||||
|
Task<List<WGServerViewModel>> GetServersAsync();
|
||||||
|
Task<WGServerViewModel> GetServer(string Name);
|
||||||
|
Task<List<ServerTrafficViewModel>> GetServersTraffic();
|
||||||
|
Task<List<WGPeerViewModel>> GetUsersAsync();
|
||||||
|
Task<WGPeerViewModel> GetUser(int id);
|
||||||
|
Task<string> GetUserTunnelConfig(int id);
|
||||||
|
Task<string> GetQRCodeBase64(int id);
|
||||||
|
Task<MTInfoViewModel> GetInfo();
|
||||||
|
Task<MTIdentityViewModel> GetName();
|
||||||
|
Task<bool> TryConnectAsync();
|
||||||
|
Task<List<ActiveUserViewModel>> GetActiveSessions();
|
||||||
|
Task<List<JobViewModel>> GetJobs();
|
||||||
|
Task<string> GetCurrentSessionID();
|
||||||
|
Task<string> KillJob(string JobID);
|
||||||
|
Task<CreationResult> CreateServer(ServerCreateModel server);
|
||||||
|
Task<CreationResult> CreateUser(UserCreateModel peer);
|
||||||
|
Task<CreationResult> SyncUser(UserSyncModel user);
|
||||||
|
Task<CreationResult> UpdateUser(UserUpdateModel user);
|
||||||
|
Task<CreationResult> UpdateServer(ServerUpdateModel server);
|
||||||
|
Task<CreationResult> EnableServer(int id);
|
||||||
|
Task<CreationResult> DisableServer(int id);
|
||||||
|
Task<CreationResult> EnableUser(int id);
|
||||||
|
Task<CreationResult> DisableUser(int id);
|
||||||
|
Task<CreationResult> DeleteServer(int id);
|
||||||
|
Task<CreationResult> DeleteUser(int id);
|
||||||
|
}
|
||||||
|
}
|
313
Services/MTAPI.cs
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
using AutoMapper;
|
||||||
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using MTWireGuard.Models;
|
||||||
|
using MTWireGuard.Models.Mikrotik;
|
||||||
|
using MTWireGuard.Repositories;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using QRCoder;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Services
|
||||||
|
{
|
||||||
|
public class MTAPI : IMikrotikRepository
|
||||||
|
{
|
||||||
|
private readonly IMapper mapper;
|
||||||
|
private readonly DBContext dbContext;
|
||||||
|
private bool disposed = false;
|
||||||
|
public MTAPI(IMapper mapper, DBContext dbContext)
|
||||||
|
{
|
||||||
|
this.mapper = mapper;
|
||||||
|
this.dbContext = dbContext;
|
||||||
|
}
|
||||||
|
public async Task<List<LogViewModel>> GetLogsAsync()
|
||||||
|
{
|
||||||
|
var model = await APIHandler.GetLogsAsync();
|
||||||
|
var result = mapper.Map<List<LogViewModel>>(model);
|
||||||
|
return result.OrderBy(list => list.Id).ToList();
|
||||||
|
}
|
||||||
|
public async Task<List<WGServerViewModel>> GetServersAsync()
|
||||||
|
{
|
||||||
|
var model = await APIHandler.GetServersAsync();
|
||||||
|
var result = mapper.Map<List<WGServerViewModel>>(model);
|
||||||
|
return result.OrderBy(list => list.Id).ToList();
|
||||||
|
}
|
||||||
|
public async Task<WGServerViewModel> GetServer(string Name)
|
||||||
|
{
|
||||||
|
var model = await APIHandler.GetServer(Name);
|
||||||
|
return mapper.Map<WGServerViewModel>(model);
|
||||||
|
}
|
||||||
|
public async Task<List<ServerTrafficViewModel>> GetServersTraffic() {
|
||||||
|
var model = await APIHandler.GetServersTraffic();
|
||||||
|
return mapper.Map<List<ServerTrafficViewModel>>(model);
|
||||||
|
}
|
||||||
|
public async Task<List<WGPeerViewModel>> GetUsersAsync()
|
||||||
|
{
|
||||||
|
var model = await APIHandler.GetUsersAsync();
|
||||||
|
var result = mapper.Map<List<WGPeerViewModel>>(model);
|
||||||
|
return result.OrderBy(list => list.Id).ToList();
|
||||||
|
}
|
||||||
|
public async Task<WGPeerViewModel> GetUser(int id)
|
||||||
|
{
|
||||||
|
var model = await APIHandler.GetUser($"*{id:X}");
|
||||||
|
return mapper.Map<WGPeerViewModel>(model);
|
||||||
|
}
|
||||||
|
public async Task<string> GetUserTunnelConfig(int id)
|
||||||
|
{
|
||||||
|
WGPeerViewModel User = await GetUser(id);
|
||||||
|
WGServerViewModel Server = await GetServer(User.Interface);
|
||||||
|
string IP = Environment.GetEnvironmentVariable("MT_PUBLIC_IP"),
|
||||||
|
Endpoint = Server != null ? $"{IP}:{Server.ListenPort}" : "";
|
||||||
|
return $"[Interface]{Environment.NewLine}" +
|
||||||
|
$"Address = {User.Address ?? "0.0.0.0/0"}{Environment.NewLine}" +
|
||||||
|
$"PrivateKey = {User.PrivateKey}{Environment.NewLine}" +
|
||||||
|
$"{Environment.NewLine}" +
|
||||||
|
$"[Peer]{Environment.NewLine}" +
|
||||||
|
$"AllowedIPs = 0.0.0.0/0{Environment.NewLine}" +
|
||||||
|
$"Endpoint = {Endpoint}{Environment.NewLine}" +
|
||||||
|
$"PublicKey = {Server?.PublicKey ?? ""}";
|
||||||
|
}
|
||||||
|
public async Task<string> GetQRCodeBase64(int id)
|
||||||
|
{
|
||||||
|
string config = await GetUserTunnelConfig(id);
|
||||||
|
|
||||||
|
using QRCodeGenerator qrGenerator = new();
|
||||||
|
using QRCodeData qrCodeData = qrGenerator.CreateQrCode(config, QRCodeGenerator.ECCLevel.Q);
|
||||||
|
using PngByteQRCode qrCode = new(qrCodeData);
|
||||||
|
var QR = qrCode.GetGraphic(20);
|
||||||
|
return Convert.ToBase64String(QR);
|
||||||
|
}
|
||||||
|
public async Task<MTInfoViewModel> GetInfo()
|
||||||
|
{
|
||||||
|
var model = await APIHandler.GetInfo();
|
||||||
|
return mapper.Map<MTInfoViewModel>(model);
|
||||||
|
}
|
||||||
|
public async Task<MTIdentityViewModel> GetName()
|
||||||
|
{
|
||||||
|
var model = await APIHandler.GetName();
|
||||||
|
return mapper.Map<MTIdentityViewModel>(model);
|
||||||
|
}
|
||||||
|
public async Task<bool> TryConnectAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var model = await APIHandler.TryConnectAsync();
|
||||||
|
if ((model.Error == 400 && model.Message == "Bad Request") || (model.Error == 401 && model.Message == "Unauthorized"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw new($"[{model.Error}] Login failed, {model.Message}.<br>Enter router username/password in environment variables (MT_USER/MT_PASS).");
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
throw new($"Login failed, {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public async Task<List<ActiveUserViewModel>> GetActiveSessions()
|
||||||
|
{
|
||||||
|
var model = await APIHandler.GetActiveSessions();
|
||||||
|
return mapper.Map<List<ActiveUserViewModel>>(model);
|
||||||
|
}
|
||||||
|
public async Task<List<JobViewModel>> GetJobs()
|
||||||
|
{
|
||||||
|
var model = await APIHandler.GetJobs();
|
||||||
|
return mapper.Map<List<JobViewModel>>(model);
|
||||||
|
}
|
||||||
|
public async Task<string> GetCurrentSessionID()
|
||||||
|
{
|
||||||
|
var activeSessions = await APIHandler.GetActiveSessions();
|
||||||
|
var apiSession = activeSessions.Find(x => x.Via == "api");
|
||||||
|
var jobs = await APIHandler.GetJobs();
|
||||||
|
var currentJob = jobs.Find(x => x.Started == apiSession.When);
|
||||||
|
return currentJob.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> KillJob(string JobID)
|
||||||
|
{
|
||||||
|
return await APIHandler.KillJob(JobID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CreationResult> CreateServer(ServerCreateModel server)
|
||||||
|
{
|
||||||
|
var srv = mapper.Map<WGServerCreateModel>(server);
|
||||||
|
var model = await APIHandler.CreateServer(srv);
|
||||||
|
return mapper.Map<CreationResult>(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CreationResult> CreateUser(UserCreateModel peer)
|
||||||
|
{
|
||||||
|
var user = mapper.Map<WGPeerCreateModel>(peer);
|
||||||
|
var model = await APIHandler.CreateUser(user);
|
||||||
|
if (model.Success)
|
||||||
|
{
|
||||||
|
var item = model.Item as WGPeer;
|
||||||
|
await dbContext.Users.AddAsync(new()
|
||||||
|
{
|
||||||
|
Id = Convert.ToInt32(item.Id[1..], 16),
|
||||||
|
Name = peer.Name,
|
||||||
|
PrivateKey = peer.PrivateKey,
|
||||||
|
PublicKey = peer.PublicKey
|
||||||
|
});
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
return mapper.Map<CreationResult>(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CreationResult> SyncUser(UserSyncModel user)
|
||||||
|
{
|
||||||
|
CreationResult result = new();
|
||||||
|
var userID = user.Id;
|
||||||
|
var dbUser = await dbContext.Users.FindAsync(userID);
|
||||||
|
var mtUser = await GetUser(userID);
|
||||||
|
if (dbUser == null)
|
||||||
|
{
|
||||||
|
await dbContext.Users.AddAsync(new()
|
||||||
|
{
|
||||||
|
Id = userID,
|
||||||
|
Name = user.Name,
|
||||||
|
PublicKey = user.PublicKey,
|
||||||
|
PrivateKey = user.PrivateKey
|
||||||
|
});
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
result = new()
|
||||||
|
{
|
||||||
|
Code = "200",
|
||||||
|
Title = "Success",
|
||||||
|
Description = "Database updated successfully."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (dbUser.PublicKey != user.PublicKey)
|
||||||
|
{
|
||||||
|
var fxUser = dbUser;
|
||||||
|
fxUser.Name = user.Name;
|
||||||
|
fxUser.PrivateKey = user.PrivateKey;
|
||||||
|
fxUser.PublicKey = user.PublicKey;
|
||||||
|
dbContext.Users.Update(fxUser);
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
result = new()
|
||||||
|
{
|
||||||
|
Code = "200",
|
||||||
|
Title = "Success",
|
||||||
|
Description = "Database updated successfully."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (mtUser.PublicKey != user.PublicKey)
|
||||||
|
{
|
||||||
|
var fxUser = mapper.Map<WGPeerUpdateModel>(user);
|
||||||
|
var update = await APIHandler.UpdateUser(fxUser);
|
||||||
|
result = mapper.Map<CreationResult>(update);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CreationResult> UpdateUser(UserUpdateModel user)
|
||||||
|
{
|
||||||
|
var mtPeer = mapper.Map<WGPeerUpdateModel>(user);
|
||||||
|
var mtUpdate = await APIHandler.UpdateUser(mtPeer);
|
||||||
|
if (mtUpdate.Success)
|
||||||
|
{
|
||||||
|
var exists = await dbContext.Users.FindAsync(user.Id);
|
||||||
|
dbContext.ChangeTracker.Clear();
|
||||||
|
if (exists != null)
|
||||||
|
{
|
||||||
|
dbContext.Users.Update(new()
|
||||||
|
{
|
||||||
|
Id = user.Id,
|
||||||
|
Name = user.Name ?? exists.Name,
|
||||||
|
PrivateKey = user.PrivateKey ?? exists.PrivateKey,
|
||||||
|
PublicKey = user.PublicKey ?? exists.PublicKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
await dbContext.Users.AddAsync(new()
|
||||||
|
{
|
||||||
|
Id = user.Id,
|
||||||
|
Name = user.Name,
|
||||||
|
PublicKey = user.PublicKey,
|
||||||
|
PrivateKey = user.PrivateKey
|
||||||
|
});
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
return mapper.Map<CreationResult>(mtUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CreationResult> UpdateServer(ServerUpdateModel server)
|
||||||
|
{
|
||||||
|
var srv = mapper.Map<WGServerUpdateModel>(server);
|
||||||
|
var mtUpdate = await APIHandler.UpdateServer(srv);
|
||||||
|
return mapper.Map<CreationResult>(mtUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CreationResult> EnableServer(int id)
|
||||||
|
{
|
||||||
|
var enable = await APIHandler.SetServerEnabled(new()
|
||||||
|
{
|
||||||
|
ID = $"*{id:X}",
|
||||||
|
Disabled = false
|
||||||
|
});
|
||||||
|
return mapper.Map<CreationResult>(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CreationResult> DisableServer(int id)
|
||||||
|
{
|
||||||
|
var enable = await APIHandler.SetServerEnabled(new()
|
||||||
|
{
|
||||||
|
ID = $"*{id:X}",
|
||||||
|
Disabled = true
|
||||||
|
});
|
||||||
|
return mapper.Map<CreationResult>(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CreationResult> EnableUser(int id)
|
||||||
|
{
|
||||||
|
var enable = await APIHandler.SetUserEnabled(new()
|
||||||
|
{
|
||||||
|
ID = $"*{id:X}",
|
||||||
|
Disabled = false
|
||||||
|
});
|
||||||
|
return mapper.Map<CreationResult>(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CreationResult> DisableUser(int id)
|
||||||
|
{
|
||||||
|
var enable = await APIHandler.SetUserEnabled(new()
|
||||||
|
{
|
||||||
|
ID = $"*{id:X}",
|
||||||
|
Disabled = true
|
||||||
|
});
|
||||||
|
return mapper.Map<CreationResult>(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<CreationResult> DeleteServer(int id)
|
||||||
|
{
|
||||||
|
var delete = await APIHandler.DeleteServer($"*{id:X}");
|
||||||
|
return mapper.Map<CreationResult>(delete);
|
||||||
|
}
|
||||||
|
public async Task<CreationResult> DeleteUser(int id)
|
||||||
|
{
|
||||||
|
var delete = await APIHandler.DeleteUser($"*{id:X}");
|
||||||
|
return mapper.Map<CreationResult>(delete);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
// Free any other managed objects here.
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free any unmanaged objects here.
|
||||||
|
//
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
ViewComponents/CreateClientFormViewComponent.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MTWireGuard.Models.Requests;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace MTWireGuard.ViewComponents
|
||||||
|
{
|
||||||
|
[ViewComponent(Name = "CreateClientForm")]
|
||||||
|
public class CreateClientFormViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public async Task<IViewComponentResult> InvokeAsync()
|
||||||
|
{
|
||||||
|
return View("CreateClientForm", new CreateClientRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
ViewComponents/CreateServerFormViewComponent.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MTWireGuard.Models.Requests;
|
||||||
|
|
||||||
|
namespace MTWireGuard.ViewComponents
|
||||||
|
{
|
||||||
|
[ViewComponent(Name = "CreateServerForm")]
|
||||||
|
public class CreateServerFormViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public async Task<IViewComponentResult> InvokeAsync()
|
||||||
|
{
|
||||||
|
return View("CreateServerForm", new CreateServerRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
ViewComponents/DeleteModalViewComponent.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MTWireGuard.Models.Requests;
|
||||||
|
|
||||||
|
namespace MTWireGuard.ViewComponents
|
||||||
|
{
|
||||||
|
[ViewComponent(Name = "DeleteModal")]
|
||||||
|
public class DeleteModalViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public async Task<IViewComponentResult> InvokeAsync(bool IsServer)
|
||||||
|
{
|
||||||
|
ViewData["IsServer"] = IsServer;
|
||||||
|
return View("DeleteModal", new DeleteRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
ViewComponents/SyncUserModalViewComponent.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MTWireGuard.Models.Requests;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace MTWireGuard.ViewComponents
|
||||||
|
{
|
||||||
|
[ViewComponent(Name = "SyncUserModal")]
|
||||||
|
public class SyncUserModalViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public async Task<IViewComponentResult> InvokeAsync()
|
||||||
|
{
|
||||||
|
return View("SyncUserModal", new SyncUserRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
ViewComponents/UpdateClientModalViewComponent.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MTWireGuard.Models.Requests;
|
||||||
|
|
||||||
|
namespace MTWireGuard.ViewComponents
|
||||||
|
{
|
||||||
|
[ViewComponent(Name = "UpdateClientModal")]
|
||||||
|
public class UpdateClientModalViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public async Task<IViewComponentResult> InvokeAsync(List<Models.Mikrotik.WGServerViewModel> Servers)
|
||||||
|
{
|
||||||
|
ViewData["Servers"] = Servers;
|
||||||
|
return View("UpdateClientModal", new UpdateClientRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
ViewComponents/UpdateServerModalViewComponent.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MTWireGuard.Models.Requests;
|
||||||
|
|
||||||
|
namespace MTWireGuard.ViewComponents
|
||||||
|
{
|
||||||
|
[ViewComponent(Name = "UpdateServerModal")]
|
||||||
|
public class UpdateServerModalViewComponent : ViewComponent
|
||||||
|
{
|
||||||
|
public async Task<IViewComponentResult> InvokeAsync()
|
||||||
|
{
|
||||||
|
return View("UpdateServerModal", new UpdateServerRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
appsettings.Development.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"DetailedErrors": true,
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
appsettings.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
11
build.bat
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
@echo off
|
||||||
|
echo Publish project...
|
||||||
|
dotnet publish -c Release -o published
|
||||||
|
echo Publish done!
|
||||||
|
echo.
|
||||||
|
echo Build docker image...
|
||||||
|
docker buildx build --no-cache --platform linux/amd64 -t mtwg .
|
||||||
|
echo Docker image built!
|
||||||
|
echo Save output file!
|
||||||
|
docker save mtwg > mtwg.tar
|
||||||
|
echo Finish
|
9
wwwroot/assets/brand/wireguard-mini.svg
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<svg width="300" height="300" viewport="0 0 46 46" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||||
|
|
||||||
|
<title>wireguard</title>
|
||||||
|
<g>
|
||||||
|
<title>Layer 1</title>
|
||||||
|
<path id="svg_37" fill="#88171a" d="m299.7406,145.56s6.9396,-145.56 -153.04,-145.56c-141.48,0 -145.9,139.63 -145.9,139.63s-20.811,160.37 149.16,160.37c163.02,0 149.78,-154.44 149.78,-154.44zm-197.8,-50.863c30.017,-18.364 68.366,-7.1401 82.735,20.476c2.7233,5.2338 3.0694,13.291 1.3447,18.782c-5.9546,18.956 -20.014,29.587 -39.312,34.103c5.6892,-4.8707 10.218,-10.394 11.659,-18.025a26.402,26.402 0 0 0 -4.5425,-20.956a26.76,26.76 0 0 0 -30.811,-9.3892c-11.881,4.5111 -18.389,15.354 -17.216,28.683c1.0898,12.381 10.484,20.405 28.061,23.453c-2.627,1.3904 -4.6503,2.4144 -6.6299,3.5172a63.918,63.918 0 0 0 -20.544,17.868c-1.7839,2.4084 -3.0104,2.6024 -5.727,0.94116c-35.338,-21.61 -37.609,-75.844 0.98226,-99.453l0.00044,-0.00016zm-26.449,133.53c-5.6769,1.441 -11.178,3.5742 -16.981,5.4775c2.8385,-19.151 25.265,-36.788 44.23,-34.776a48.881,48.881 0 0 0 -9.242,25.893c-6.302,1.1606 -12.241,1.9414 -18.007,3.405l0,0.0005zm120.79,-186.98c5.6099,0.20612 11.23,0.12091 16.844,0.25378a29.052,29.052 0 0 1 4.1674,0.58069a40.607,40.607 0 0 1 -4.2357,5.4332c-2.007,1.8701 -4.2745,3.6986 -7.1661,0.856c-0.6955,-0.68372 -2.3386,-0.52679 -3.5487,-0.54272c-5.5823,-0.07336 -11.172,-0.25177 -16.746,-0.04132a104.04,104.04 0 0 0 -14.425,1.473c-0.89368,0.16046 -2.2299,3.1315 -1.8191,4.227c0.9693,2.5853 2.3833,5.4363 4.4779,7.0898c7.7403,6.11 15.972,11.596 23.748,17.664c7.556,5.8966 14.589,12.358 18.875,21.253c5.5843,11.59 5.747,23.743 3.3388,35.95c-4.0203,20.378 -14.333,37.261 -31.032,49.524c-6.7288,4.941 -15.06,7.7451 -22.767,11.295c-6.778,3.1225 -13.755,5.8115 -20.549,8.9008c-12.249,5.5695 -19.133,18.865 -17.108,32.688c1.8585,12.685 12.987,23.271 25.735,25.456c15.292,2.6216 31.071,-7.3163 34.812,-22.86c4.2067,-17.478 -5.2898,-33.083 -23.065,-37.813c-0.78271,-0.20831 -1.5684,-0.40552 -3.2012,-0.8269c4.7549,-2.1245 8.8614,-3.6381 12.653,-5.7244q9.9213,-5.4594 19.481,-11.562c1.8742,-1.199 2.8868,-1.1996 4.4852,0.18225c12.225,10.57 19.518,23.718 21.563,39.839c3.3845,26.684 -9.2471,51.198 -33.072,63.762c-36.86,19.439 -81.965,-2.6864 -90.106,-43.552c-6.9738,-35.003 17.73,-66.754 47.462,-72.884c12.787,-2.6364 24.48,-7.9596 33.57,-17.807c5.8652,-6.3541 8.7084,-11.806 9.6772,-14.266a39.565,39.565 0 0 0 2.7211,-14.469a33.867,33.867 0 0 0 -2.9654,-12.398c-3.104,-7.075 -14.995,-18.33 -17.939,-20.704l-28,-21.921c-0.98761,-0.81256 -2.0994,-0.75366 -4.5079,-0.59045c-2.8611,0.19391 -10.175,0.59888 -13.331,-0.22815c2.553,-1.9321 9.5132,-4.7451 12.502,-7.007c-9.0734,-6.1297 -19.43,-3.9158 -28.941,-5.7461c2.1992,-4.0959 13.081,-10.39 19.27,-11.091a91.533,91.533 0 0 0 -1.6876,-10.281c-0.37781,-1.3917 -1.9312,-2.7408 -3.2864,-3.5355c-3.286,-1.9267 -6.7694,-3.5167 -10.549,-5.4327a21.936,21.936 0 0 1 11.332,-3.5055a42.316,42.316 0 0 1 11.348,1.1056c6.7422,1.5405 12.124,0.53491 17.488,-4.048c-4.222,-1.7002 -8.4435,-3.2535 -12.538,-5.0907a123.04,123.04 0 0 1 -11.779,-6.1583c10.622,1.4755 20.896,5.4585 31.757,4.0034q0.1387,-0.74048 0.27728,-1.4809c-8.1194,-1.8899 -16.239,-3.7798 -25.229,-5.8724c15.04,-1.3769 29.042,-1.604 42.301,4.8541c3.731,1.8173 7.6348,3.3215 11.211,5.3972c1.7443,1.0124 2.9186,3.0078 4.3496,4.5594c1.1366,1.2325 2.0495,2.8837 3.446,3.6264c5.3,2.8184 11.134,2.9291 17.078,2.7879c0.04443,-0.67694 0.08606,-1.3114 0.1308,-1.9933c5.9821,1.8693 12.715,8.7679 12.704,13.806c-9.6911,0 -19.374,-0.037 -29.056,0.05389c-1.0348,0.0097 -2.0626,0.76563 -3.0936,1.1754c0.97986,0.57067 1.9428,1.5994 2.9423,1.6362l-0.00388,-0.00067z" class="a"/>
|
||||||
|
<path id="svg_38" fill="#88171a" d="m183.7806,26.906a1.4806,1.4806 0 0 0 -0.18927,2.3686a2.2326,2.2326 0 0 0 3.0724,0.8219c0.9328,-0.47052 1.8478,-0.97137 2.975,-1.5665c-0.9079,-0.775 -1.6362,-1.4148 -2.3857,-2.0324c-1.318,-1.086 -2.411,-0.40386 -3.4724,0.40833l-0.00003,0.00007z" class="a"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.8 KiB |
2
wwwroot/assets/brand/wireguard.svg
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
wwwroot/assets/favicon/android-icon-144x144.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
wwwroot/assets/favicon/android-icon-192x192.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
wwwroot/assets/favicon/android-icon-256x256.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
wwwroot/assets/favicon/android-icon-36x36.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
wwwroot/assets/favicon/android-icon-384x384.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
wwwroot/assets/favicon/android-icon-48x48.png
Normal file
After Width: | Height: | Size: 2.6 KiB |