Initial version

main
EonaCat 2 months ago
parent fa73b671c0
commit f43866e96c

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

@ -0,0 +1,20 @@
{
"runtimeOptions": {
"tfm": "net7.0",
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "7.0.0"
},
{
"name": "Microsoft.AspNetCore.App",
"version": "7.0.0"
}
],
"configProperties": {
"System.GC.Server": true,
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

@ -0,0 +1,28 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>EonaCat.Blocky</name>
</assembly>
<members>
<member name="M:EonaCat.Blocky.Helpers.SlackClient.#ctor(System.String)">
<summary>
SlackClient
</summary>
<param name="urlWithAccessToken"></param>
</member>
<member name="M:EonaCat.Blocky.Helpers.SlackClient.PostMessage(System.String,System.String,System.String)">
<summary>
Post a message using simple strings
</summary>
<param name="text"></param>
<param name="username"></param>
<param name="channel"></param>
</member>
<member name="M:EonaCat.Blocky.Helpers.SlackClient.PostMessage(EonaCat.Blocky.Helpers.Payload)">
<summary>
Post a message using a Payload object
</summary>
<param name="payload"></param>
</member>
</members>
</doc>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>EonaCat.Dns</name>
</assembly>
<members>
</members>
</doc>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -1,6 +1,54 @@
# Blocky_public-CSharp
# Blocky
Blocky
Blocking domains the way you want it.
Copyright EonaCat (Jeroen Saey) 2017-2023
https://blocky.eonacat.com
https://blocky.eonacat.com
#### Windows notice:
It could be that you need to run the following command in CMD:
net stop http
#### Linux notice:
If you want to install Blocky on a Linux environment be sure that port 53 is free.
(If there is no command output there is no other application using port 53)
You can check if the port is free using the following command:
```bash
sudo lsof -i :53
```
1. Open the resolved.conf using the command:
```bash
sudo nano /etc/systemd/resolved.conf
```
2. Uncomment the following lines and set their values:
```bash
[Resolve]
DNS=127.0.0.1
#FallbackDNS=
#Domains=
#LLMNR=no
#MulticastDNS=no
#DNSSEC=no
#DNSOverTLS=no
#Cache=no
DNSStubListener=no
#ReadEtcHosts=yes
```
3. Save the file and quit.
4. Create a symbolic link for /run/systemd/resolve/resolv.conf with /etc/resolv.conf as the destination:
```bash
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
```
5. Reboot your system.
6. Port 53 is now free for Blocky to use.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,142 @@
๏ปฟ<div id="mainContainer">
<div class="row">
<div class="col-md-12">
<table id="blockListTable" class="table table-bordered table-hover" style="width: 100%">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Url</th>
<th>Enabled</th>
<th>Last result</th>
<th>Last updated</th>
<th>Last updated startTime</th>
<th>Creation date</th>
<th>Total entries</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>
<button type="button" class="addBlockList btn btn-primary btn-sm fa fa-plus" style="float: right;" data-bs-toggle="modal" data-bs-target="#EonaCatModal"></button>
</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
<script>
$(function ()
{
var datatablesOptions = {
"serverSide": true,
"ajaxSource": '/BlockList/GetList',
"serverMethod": "GET",
"bautoWidth": false,
destroy: true,
columns: [
{ 'data': 'Id' },
{ 'data': 'Name' },
{ 'data': 'Url' },
{ 'data': 'IsEnabled' },
{ 'data': 'LastResult' },
{ 'data': 'LastUpdated' },
{ 'data': 'LastUpdateStartTime' },
{ 'data': 'CreationDate' },
{ 'data': 'TotalEntries' },
{
mRender: function (data, type, row)
{
var linkUpdate = '<div class="form-group">\
<form action="/BlockList/UpdateList" method="post">\
<input type="hidden" id="id" name="id" value="' + row.Id + '">\
<button type="submit" class="btn btn-warning btn-sm" onclick="return confirm(\'Are you sure you want to update this blockList?\')">\
<i class="fas fa-undo"></i> Update\
</button>\
</form>\
</div>';
if (row.IsUpdating)
{
linkUpdate = '<div class="form-group">\
<i class="fas fa-undo rotatingleft"></i> Updating\
<div id="progress' + row.Id + '"></div></div>'
fetchProgress(row.Id, row.Url);
}
return linkUpdate;
},
'sWidth': '50px'
},
{
mRender: function (data, type, row)
{
var linkEdit = '<div class="form-group">\
<input type="hidden" id="id" name="id" value="' + row.Id + '">\
<button type="button" class="editBlockList btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#EonaCatModal" data-id="' + row.Id + '">\
<i class="fas fa-edit"></i> Edit\
</button>\
</div>';
return linkEdit;
},
'sWidth': '50px'
},
{
mRender: function (data, type, row)
{
var linkDelete = '<div class="form-group">\
<form action="/BlockList/Delete" method="post">\
<input type="hidden" id="id" name="id" value="' + row.Id + '">\
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm(\'Are you sure you want to delete this blockList?\')">\
<i class="fas fa-trash-alt"></i> Delete\
</button>\
</form>\
</div>';
return linkDelete;
},
'sWidth': '50px'
}
]
};
$('#blockListTable').dataTable(datatablesOptions);
$(document).on('click', '.editBlockList', function () {
var id = $(this).data('id');
var route = '@Url.Action("GetById", "BlockList")?id=' + id;
$('#partial').load(route);
});
$('.addBlockList').on("click", function () {
var route = '@Url.Action("GetById", "BlockList")?id=0';
$('#partial').load(route);
});
function fetchProgress(id, url) {
fetch(`/api/blocklistProgress?token=${token}&url=${url}`)
.then(response => response.json())
.then(data =>
{
if (data == null)
{
$('#progress' + id).html("Error");
return;
}
if (data.total > 0 && data.progress < 100)
{
$('#progress' + id).html("<small>(" + data.current + "/" + data.total + ") (" + data.progress + "%)</small>");
}
if (typeof(data.total) == undefined || data.total == 0 || data.progress < 100) {
setTimeout(fetchProgress(id, url), 5000);
}
else
{
$('#progress' + id).html(data.progress);
}
});
}
});
</script>

@ -0,0 +1,52 @@
๏ปฟ@model BlockListViewModel;
<script>
$(function ()
{
$(".btn-group > .btn").click(function ()
{
$(".btn-group > .btn").removeClass("active");
$(this).addClass("active");
});
});
</script>
@using (Html.BeginForm("Update", "BlockList"))
{
<div class="form-group">
<label for="ID">ID</label>
@Html.TextBoxFor(x => x.Id, new { @class = "form-control", disabled = "disabled" })
@Html.Hidden("Id")
</div>
<div class="form-group">
<label for="Name">Name</label>
@Html.TextBoxFor(x => x.Name, new { id = "Name", @class = "form-control", placeholder = "Name" })
</div>
<div class="form-group">
<label for="Url">Url</label>
@Html.TextBoxFor(x => x.Url, new { id = "Url", @class = "form-control", placeholder = "Url" })
</div>
<div class="form-group">
Enabled&nbsp;
<div class="btn-group" data-bs-toggle="buttons">
@{
var isEnabled = Model.IsEnabled ? "active" : "";
var isDisabled = !Model.IsEnabled ? "active" : "";
}
<label class="btn btn-primary @isEnabled">
@Html.RadioButtonFor(e => e.IsEnabled, true, new { id = "isEnabled-true" })
@Html.Label("isEnabled-true", "Yes")
</label>
<label class="btn btn-primary @isDisabled">
@Html.RadioButtonFor(e => e.IsEnabled, false, new { id = "isDisabled-false" })
@Html.Label("isDisabled-false", "No")
</label>
</div>
</div>
<button type="submit" class="btn btn-primary">Save changes</button>
}

@ -0,0 +1,76 @@
๏ปฟ<div id="mainContainer">
<div class="row">
<div class="col-md-12">
<table id="blockyLogsTable" class="table table-bordered table-hover dataTables" style="width: 100%">
<thead>
<tr>
<th>Id</th>
<th>Date</th>
<th>ClientIp</th>
<th>Request</th>
<th>Result</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
<style>
.green { background-color: lightgreen; }
.red { background-color: lightcoral; }
</style>
<script>
$(function () {
var datatablesOptions = {
"serverSide": true,
"ajaxSource": '/Logs/GetList',
"serverMethod": "GET",
"bautoWidth": false,
order: [[2, 'desc']],
destroy: true,
columns: [
{ 'data': 'Id' },
{ 'data': 'Date' },
{ 'data': 'ClientIp' },
{ 'data': 'Request' },
{ 'data': 'Result' }
],
createdRow: function (row, data, index) {
if (data[4] == "SUCCESS") {
$('td', row).eq(4).addClass('green');
} else if (data[4] == "BLOCKED") {
$('td', row).eq(4).addClass('red');
}
},
}
$('#blockyLogsTable').dataTable(datatablesOptions);
$('#blockyLogsTable tbody').on('click', 'tr', function ()
{
var table = $('#blockyLogsTable').DataTable();
var data = table.row(this).data();
GetLogDetails(data.Id);
});
async function GetLogDetails(id)
{
await HTTPRequest({
url: `/api/logdetails?token=${token}&id=${id}`,
async: true,
success: function (responseJSON) {
alert(responseJSON.response.details);
},
error: function ()
{
},
invalidToken: function () {
loadDashboard();
}
});
return false;
}
});
</script>

@ -0,0 +1,75 @@
๏ปฟ<div id="mainContainer">
<div class="row">
<div class="col-md-12">
<table id="categoryTable" class="table table-bordered table-hover dataTables" style="width: 100%">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>&nbsp;</th>
<th>
<button type="button" class="addCategory btn btn-primary btn-sm fa fa-plus" style="float: right;" data-bs-toggle="modal" data-bs-target="#EonaCatModal"></button>
</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
<script>
$(function () {
var datatablesOptions = {
"serverSide": true,
"ajaxSource": '/Category/GetList',
"serverMethod": "GET",
"bautoWidth": false,
destroy: true,
columns: [
{ 'data': 'Id' },
{ 'data': 'Name' },
{
mRender: function (data, type, row)
{
var linkEdit = '<div class="form-group">\
<input type="hidden" id="id" name="id" value="' + row.Id + '">\
<button type="button" class="editCategory btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#EonaCatModal" data-id="' + row.Id + '">\
<i class="fas fa-edit"></i> Edit\
</button>\
</div>';
return linkEdit;
},
'sWidth': '50px'
},
{
mRender: function (data, type, row) {
var linkDelete = '<div class="form-group">\
<form action="/Category/Delete" method="post">\
<input type="hidden" id="id" name="id" value="' + row.Id + '">\
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm(\'Are you sure you want to delete this category?\')">\
<i class="fas fa-trash-alt"></i> Delete\
</button>\
</form>\
</div>';
return linkDelete;
},
'sWidth': '50px'
}
]
};
$('#categoryTable').dataTable(datatablesOptions);
$(document).on('click', '.editCategory', function () {
var id = $(this).data('id');
var route = '@Url.Action("GetById", "Category")?id=' + id;
$('#partial').load(route);
});
$('.addCategory').on("click", function () {
var route = '@Url.Action("GetById", "Category")?id=0';
$('#partial').load(route);
});
});
</script>

@ -0,0 +1,27 @@
๏ปฟ@model CategoryViewModel;
<script>
$(function ()
{
$(".btn-group > .btn").click(function ()
{
$(".btn-group > .btn").removeClass("active");
$(this).addClass("active");
});
});
</script>
@using (Html.BeginForm("Update", "Category"))
{
<div class="form-group">
<label for="ID">ID</label>
@Html.TextBoxFor(x => x.Id, new { @class = "form-control", disabled = "disabled" })
@Html.Hidden("Id")
</div>
<div class="form-group">
<label for="Url">Name</label>
@Html.TextBoxFor(x => x.Name, new { id = "Name", @class = "form-control", placeholder = "Name" })
</div>
<button type="submit" class="btn btn-primary">Save changes</button>
}

@ -0,0 +1,70 @@
๏ปฟ<table id="clientTable" class="table table-bordered table-hover dataTables" style="width: 100%">
<thead>
<tr>
<th>Ip</th>
<th>Name</th>
<th>IsBlocked</th>
<th>&nbsp;</th>
<th>
<button type="button" class="addClient btn btn-primary btn-sm fa fa-plus" style="float: right;" data-bs-toggle="modal" data-bs-target="#EonaCatModal"></button>
</th>
</tr>
</thead>
</table>
<script>
$(function () {
var datatablesOptions = {
"serverSide": true,
"ajaxSource": '/Client/GetList',
"serverMethod": "GET",
"bautoWidth": false,
destroy: true,
columns: [
{ 'data': 'Ip' },
{ 'data': 'Name'},
{ 'data': 'IsBlocked' },
{
mRender: function (data, type, row) {
var linkEdit = '<div class="form-group">\
<input type="hidden" id="ip" name="ip" value="' + row.Ip + '">\
<button type="button" class="editClient btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#EonaCatModal" data-ip="' + row.Ip + '">\
<i class="fas fa-edit"></i> Edit\
</button>\
</div>';
return linkEdit;
},
'sWidth': '50px'
},
{
mRender: function (data, type, row) {
var linkDelete = '<div class="form-group">\
<form action="/Client/Delete" method="post">\
<input type="hidden" id="ip" name="ip" value="' + row.Ip + '">\
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm(\'Are you sure you want to delete this client?\')">\
<i class="fas fa-trash-alt"></i> Delete\
</button>\
</form>\
</div>';
return linkDelete;
},
'sWidth': '50px'
}
]
};
$('#clientTable').dataTable(datatablesOptions);
$(document).on('click', '.editClient', function () {
var ip = $(this).data('ip');
var route = '@Url.Action("GetByIp", "Client")?ip=' + ip;
$('#partial').load(route);
});
$('.addClient').on("click", function () {
var route = '@Url.Action("GetByIp", "Client")';
$('#partial').load(route);
});
});
</script>

@ -0,0 +1,48 @@
๏ปฟ@model ClientViewModel;
<script>
$(function ()
{
$(".btn-group > .btn").click(function ()
{
$(".btn-group > .btn").removeClass("active");
$(this).addClass("active");
});
});
</script>
@using (Html.BeginForm("Update", "Client"))
{
<div class="form-group">
<label for="Ip">Ip</label>
@Html.TextBoxFor(x => x.Ip, new { @class = "form-control", placeholder = "ipAddress" })
@Html.Hidden("Ip")
</div>
<div class="form-group">
<label for="Name">Name</label>
@Html.TextBoxFor(x => x.Name, new { id = "Name", @class = "form-control", placeholder = "Name" })
</div>
<div class="form-group">
Blocked&nbsp;
<div class="btn-group" data-bs-toggle="buttons">
@{
var isEnabled = Model.IsBlocked ? "active" : "";
var isDisabled = !Model.IsBlocked ? "active" : "";
}
<label class="btn btn-primary @isEnabled">
@Html.RadioButtonFor(e => e.IsBlocked, true, new { id = "isBlocked-true" })
@Html.Label("isBlocked-true", "Yes")
</label>
<label class="btn btn-primary @isDisabled">
@Html.RadioButtonFor(e => e.IsBlocked, false, new { id = "isBlocked-false" })
@Html.Label("isBlocked-false", "No")
</label>
</div>
</div>
<button type="submit" class="btn btn-primary">Save changes</button>
}

@ -0,0 +1,85 @@
๏ปฟ<div id="mainContainer">
<div class="row">
<div class="col-md-12">
<table id="domainTable" class="table table-bordered table-hover" style="width: 100%">
<thead>
<tr>
<th>Id</th>
<th>Url</th>
<th>Forward Ip</th>
<th>List</th>
<th>Category</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>
<button type="button" class="addDomain btn btn-primary btn-sm fa fa-plus" style="float: right;" data-bs-toggle="modal" data-bs-target="#EonaCatModal"></button>
</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
<script>
$(function ()
{
var datatablesOptions = {
"serverSide": true,
"ajaxSource": '/Domain/GetList',
"serverMethod": "GET",
"bautoWidth": false,
destroy: true,
columns: [
{ 'data': 'Id' },
{ 'data': 'Url' },
{ 'data': 'ForwardIp' },
{ 'data': 'FromBlockList' },
{ 'data': 'ListType' },
{ 'data': 'Category' },
{
mRender: function (data, type, row)
{
var linkEdit = '<div class="form-group">\
<input type="hidden" id="id" name="id" value="' + row.Id + '">\
<button type="button" class="editDomain btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#EonaCatModal" data-id="' + row.Id + '">\
<i class="fas fa-edit"></i> Edit\
</button>\
</div>';
return linkEdit;
},
'sWidth': '50px'
},
{
mRender: function (data, type, row)
{
var linkDelete = '<div class="form-group">\
<form action="/Domain/Delete" method="post">\
<input type="hidden" id="id" name="id" value="' + row.Id + '">\
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm(\'Are you sure you want to delete this domain?\')">\
<i class="fas fa-trash-alt"></i> Delete\
</button>\
</form>\
</div>';
return linkDelete;
},
'sWidth': '50px'
}
]
};
$('#domainTable').dataTable(datatablesOptions);
$(document).on('click', '.editDomain', function () {
var id = $(this).data('id');
var route = '@Url.Action("GetById", "Domain")?id=' + id;
$('#partial').load(route);
});
$('.addDomain').on("click", function () {
var route = '@Url.Action("GetById", "Domain")?id=0';
$('#partial').load(route);
});
});
</script>

@ -0,0 +1,73 @@
๏ปฟ@model DomainViewModel;
<script>
$(function ()
{
$(".btn-group > .btn").click(function ()
{
$(".btn-group > .btn").removeClass("active");
$(this).addClass("active");
});
});
</script>
@using (Html.BeginForm("Update", "Domain"))
{
<div class="form-group">
<label for="ID">ID</label>
@Html.TextBoxFor(x => x.Id, new { @class = "form-control", disabled = "disabled" })
@Html.Hidden("Id")
</div>
<div class="form-group">
<label for="Url">Url</label>
@Html.TextBoxFor(x => x.Url, new { id = "Url", @class = "form-control", placeholder = "Url" })
</div>
<div class="form-group">
<label for="ForwardIp">Forward Ip</label>
@Html.TextBoxFor(x => x.ForwardIp, new { id = "ForwardIp", @class = "form-control", placeholder = "ForwardIp" })
</div>
<div class="form-group">
List type&nbsp;
<div class="btn-group" data-bs-toggle="buttons">
@{
var isDefault = Model.ListType.ToLower() == "default" || Model.ListType.ToLower() == "" ? "active" : "";
var isBlocked = Model.ListType.ToLower() == "blocked" ? "active" : "";
var isAllowed = Model.ListType.ToLower() == "allowed" ? "active" : "";
}
<label class="btn btn-primary @isDefault">
@Html.RadioButtonFor(x => x.ListType, "default", new { id = "isDefault-true" })
@Html.Label("isDefault-true", "Default")
</label>
<label class="btn btn-primary @isBlocked">
@Html.RadioButtonFor(x => x.ListType, "Blocked", new { id = "isBlocked-true" })
@Html.Label("isBlocked-true", "Blocked")
</label>
<label class="btn btn-primary @isAllowed">
@Html.RadioButtonFor(x => x.ListType, "allowed", new { id = "isAllowed-true" })
@Html.Label("isAllowed-true", "Allowed")
</label>
</div>
</div>
if (Model.Categories != null && Model.Categories.Any())
{
<div class="form-group">
<label for="Category">Category</label>
@Html.DropDownList("Category", Model.Categories.Select(x => new SelectListItem { Text = x.Key, Value = x.Value.ToString() }), "Select Category", new { @class = "form-control" })
</div>
}
if (!string.IsNullOrEmpty(Model.FromBlockList))
{
<div class="form-group">
<label for="BlockList">BlockList</label>
@Html.TextBoxFor(x => x.FromBlockList, new { @class = "form-control", disabled = "disabled" })
</div>
}
<button type="submit" class="btn btn-primary">Save changes</button>
}

@ -0,0 +1,46 @@
๏ปฟ@model IndexViewModel;
@{
ViewBag.Title ??= "EonaCatDns";
}
@await Html.PartialAsync("../Shared/header.cshtml", Model)
<div id="content">
<div class="container-fluid">
<div class="placeholderAlert"></div>
<div id="main" class="page">
<div class="panel panel-default">
<div class="panel-heading" style="height: 38px;">
<div style="float: left;">
<h3 class="panel-title">
<span id="title" title="Powered by @DllInfo.NAME @DllInfo.VERSION">@ViewData["Title"]</span>
</h3>
</div>
@if (!Model.IsLoggedIn)
{
<div style="float: right;">
<button id="login" type="button" class="btn btn-default" data-bs-toggle="modal" data-bs-target="#modalLogin">Login</button>
</div>
}
</div>
<div class="panel-body" style="min-height: 600px;">
@await Html.PartialAsync("../Shared/tabs.cshtml", Model)
</div>
</div>
</div>
</div>
</div>
@await Html.PartialAsync("../Shared/javascript.cshtml")
@if (!Model.IsLoggedIn)
{
@await Html.PartialAsync("../Shared/loginModal.cshtml")
}
else
{
@await Html.PartialAsync("../Shared/changePasswordModal.cshtml", new ChangePasswordViewModel())
}

@ -0,0 +1,39 @@
๏ปฟ<!--
EonaCatDns
Copyright (C) 2017-2023 EonaCat (Jeroen Saey)
Licensed under the apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "aS IS" BaSIS,
WITHOUT WaRIsRecursionavailableNTIES OR CONDITIONS OF aNY KInternetD, either express or implied.
See the License for the specific language governing permissions and
limitations under the License
-->
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">an error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>Development environment should not be enabled in deployed applications</strong>, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the <strong>aSPNEIsTruncatedORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>, and restarting the application.
</p>

@ -0,0 +1,5 @@
๏ปฟ<div id="footer">
<div class="content">
EonaCatDns created by <a href="https://EonaCat.com/" target="_blank">EonaCat (Jeroen Saey)</a>
</div>
</div>

@ -0,0 +1,66 @@
๏ปฟ<!doctype html>
<html lang="en">
<head>
@using EonaCat.Dns.Managers
<!--
EonaCatDns
Copyright (C) 2017-2023 EonaCat (Jeroen Saey)
Licensed under the apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "aS IS" BaSIS,
WITHOUT WaRIsRecursionavailableNTIES OR CONDITIONS OF aNY KInternetD, either express or implied.
See the License for the specific language governing permissions and
limitations under the License
-->
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>EonaCatDns</title>
<script src="/js/jquery.min.js"></script>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/datatables.min.css" rel="stylesheet">
<link href="/css/datatables.css" rel="stylesheet">
<link href="/css/bootstrap_theme.min.css" rel="stylesheet">
<script src="/js/bootstrap.bundle.min.js"></script>
<script src="/js/datatables.min.js"></script>
<script src="/js/Chart.min.js"></script>
<script src="/js/colpick.js"></script>
<link href="/css/font-awesome.css" rel="stylesheet"/>
<link href="/css/modal.css" rel="stylesheet"/>
<link href="/css/main.css" rel="stylesheet"/>
<link href="/css/colpick.css" rel="stylesheet"/>
<script src="/js/common.js"></script>
<style>
.allowed {
background-color: @ApiStatsManager.AllowedBackgroundColor !important;
}
.denied {
background-color: @ApiStatsManager.BlockedBackgroundColor !important;
}
</style>
</head>
<body>
<!-- Wrapper-->
<div id="wrapper">
<!-- Page wrapper -->
<div id="page-wrapper">
<!-- Main view -->
@RenderBody()
<!-- Footer -->
<partial name="_Footer"/>
</div>
<!-- End page wrapper-->
</div>
<!-- End wrapper-->
@await RenderSectionAsync("Scripts", false)
</body>
</html>

@ -0,0 +1,20 @@
๏ปฟ<div class="modal" id="EonaCatModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="EonaCatModalLabel"></h4>
<div class="logo">
<img src="~/images/logo.svg" style="width: 30px" alt="EonaCatLogo" />
</div>
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div id="partial"></div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>

@ -0,0 +1,44 @@
๏ปฟ@model ChangePasswordViewModel
@{
Layout = null;
}
<div id="modalChangePassword" class="modal" tabindex="-1" role="dialog" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Change Password</h4>
</div>
@using (Html.BeginForm("ChangePassword", "index", FormMethod.Post, new { @class = "form-horizontal" }))
{
<div class="modal-body">
<div id="divChangePasswordAlert"></div>
<div class="form-group">
<label for="txtOldPassword" class="col-sm-4 control-label">Old password</label>
<div class="col-sm-7">
@Html.TextBoxFor(x => x.OldPassword, new { id = "txtOldPassword", @class = "form-control", placeholder = "old", type = "password", autocomplete = "on" })
</div>
</div>
<div class="form-group">
<label for="txtChangePasswordNewPassword" class="col-sm-4 control-label">New password</label>
<div class="col-sm-7">
@Html.TextBoxFor(x => x.NewPassword, new { id = "txtChangePasswordNewPassword", @class = "form-control", placeholder = "new", type = "password", autocomplete = "on" })
</div>
</div>
<div class="form-group">
<label for="txtChangePasswordConfirmPassword" class="col-sm-4 control-label">Confirm password</label>
<div class="col-sm-7">
@Html.TextBoxFor(x => x.NewPassword, new { id = "txtChangePasswordConfirmPassword", @class = "form-control", placeholder = "confirmation", type = "password", autocomplete = "on" })
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" data-loading-text="Saving..." onsubmit="checkPassword();">Save</button>
</div>
}
</div>
</div>
</div>

@ -0,0 +1,245 @@
๏ปฟ<!--
EonaCatDns
Copyright (C) 2017-2023 EonaCat (Jeroen Saey)
Licensed under the apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "aS IS" BaSIS,
WITHOUT WaRIsRecursionavailableNTIES OR CONDITIONS OF aNY KInternetD, either express or implied.
See the License for the specific language governing permissions and
limitations under the License
-->
@using EonaCat.Dns.Managers
@model IndexViewModel;
<style>
.statsMenu .total-queries {
background-color: @ApiStatsManager.TotalQueriesBackgroundColor;
color: #ffffff;
}
.statsMenu .no-error {
background-color: @ApiStatsManager.NoErrorBackgroundColor;
color: #ffffff;
}
.statsMenu .server-failure {
background-color: @ApiStatsManager.ServerFailureBackgroundColor;
color: #ffffff;
}
.statsMenu .name-error {
background-color: @ApiStatsManager.NameErrorBackgroundColor;
color: #ffffff;
}
.statsMenu .refused {
background-color: @ApiStatsManager.RefusedBackgroundColor;
color: #ffffff;
}
.statsMenu .blocked {
background-color: @ApiStatsManager.BlockedBackgroundColor;
color: #ffffff;
}
.statsMenu .cached {
background-color: @ApiStatsManager.CachedBackgroundColor;
color: #ffffff;
}
.statsMenu .clients {
background-color: @ApiStatsManager.ClientsBackgroundColor;
color: #ffffff;
}
</style>
<div id="divDashboardSpinner" style="height: 400px; width: 100%; margin-top: 10px;"></div>
@{
if (Model.IsLoggedIn)
{
<ul class="nav nav-tabs">
<li id="board" class="nav-item">
<a href="#switchDashboardButton" class="active" data-bs-toggle="tab" onclick="return showDashboard();">Dashboard</a>
</li>
<li id="board" class="nav-item">
<a href="#switchInfoButton" class="active" data-bs-toggle="tab" onclick="return showInfo();">Info</a>
</li>
</ul>
<div id="divInfo" style="display: none;">
<div class="row">
<div class="col col-md-4 col-sm-4">
<div class="latestQueries panel panel-default col-2" style="height: 400px; overflow: auto">
<div class="panel-heading">Latest Queries</div>
<table class="table table-hover" id="lastQueries">
<thead>
<tr>
<th>Domain</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td>EonaCat.com</td>
<td>100</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="col col-md-4 col-sm-4">
<div class="topClients panel panel-default col-2" style="height: 400px; overflow: auto">
<div class="panel-heading">Top Clients</div>
<table class="table table-hover" id="topClients">
<thead>
<tr>
<th>Client</th>
<th>Queries</th>
</tr>
</thead>
<tbody>
<tr>
<td>127.0.0.1</td>
<td>100</td>
</tr>
<tr>
<td>::1</td>
<td>100</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="col col-md-4 col-sm-4">
<div class="topQuery panel panel-default" style="height: 400px; overflow: auto">
<div class="panel-heading">Top Query Types</div>
<div class="panel-body" style="margin-left: 10%;">
<canvas id="queryDonut"></canvas>
</div>
</div>
</div>
<div class="col col-md-4 col-sm-4">
<div class="topBlockedDomains panel panel-default col-2" style="height: 400px; overflow: auto">
<div class="panel-heading">Top Blocked Domains</div>
<table class="table table-hover" id="topBlocked">
<thead>
<tr>
<th>Domain</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td>Blocked.EonaCat.com</td>
<td>&nbsp;</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="col col-md-4 col-sm-4">
<div class="topDomains panel panel-default col-2" style="height: 400px; overflow: auto">
<div class="panel-heading">Top Domains</div>
<table class="table table-hover" id="topDomains">
<thead>
<tr>
<th>Domain</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td>EonaCat.com</td>
<td>100</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
}
}
<div id="divDashboard" style="display: none;">
<div class="btn-group" data-bs-toggle="buttons">
<label class="btn btn-default">
<input type="radio" name="radioStatsType" value="lastHour" checked> Hour
</label>
<label class="btn btn-default">
<input type="radio" name="radioStatsType" value="lastDay"> Day
</label>
<label class="btn btn-default">
<input type="radio" name="radioStatsType" value="lastWeek"> Week
</label>
@{
if (Model.IsLoggedIn)
{
<label class="btn btn-default">
<input type="radio" name="radioStatsType" value="lastMonth"> Month
</label>
<label class="btn btn-default">
<input type="radio" name="radioStatsType" value="lastYear"> Year
</label>
}
}
</div>
<div class="statsMenu row">
<div class="col-md-3 stats-item total-queries">
<div class="number" id="totalQueriesStats">100</div>
<div class="percentage" id="totalQueriesStatsPercentage">0%</div>
<div class="title">Total Queries</div>
</div>
<div class="col-md-3 stats-item no-error">
<div class="number" id="totalNoErrorStats">100</div>
<div class="percentage" id="totalNoErrorStatsPercentage">0%</div>
<div class="title">No Error</div>
</div>
<div class="col-md-3 stats-item cached">
<div class="number" id="totalCachedStats">100</div>
<div class="percentage" id="totalCachedStatsPercentage">0%</div>
<div class="title">Cached</div>
</div>
<div class="col-md-3 stats-item server-failure">
<div class="number" id="totalServerFailuresStats">100</div>
<div class="percentage" id="totalServerFailuresStatsPercentage">0%</div>
<div class="title">Server Failure</div>
</div>
<div class="col-md-3 stats-item name-error">
<div class="number" id="totalNameErrorsStats">100</div>
<div class="percentage" id="totalNameErrorsStatsPercentage">0%</div>
<div class="title">Name Error</div>
</div>
<div class="col-md-3 stats-item refused">
<div class="number" id="totalRefusedStats">100</div>
<div class="percentage" id="totalRefusedStatsPercentage">0%</div>
<div class="title">Refused</div>
</div>
<div class="col-md-3 stats-item blocked">
<div class="number" id="totalBlockedStats">100</div>
<div class="percentage" id="totalBlockedStatsPercentage">0%</div>
<div class="title">Blocked</div>
</div>
<div class="col-md-3 stats-item clients">
<div class="number" id="totalClientsStats">100</div>
<div class="percentage">&nbsp;</div>
<div class="title">Clients</div>
</div>
</div>
<canvas id="stats" style="height:200px"></canvas>
</div>

@ -0,0 +1,36 @@
๏ปฟ@model IndexViewModel;
<div id="header">
@if (!string.IsNullOrEmpty(ViewBag.AlertType))
{
<script>
$(function () {
showAlert('@ViewBag.AlertType', '@ViewBag.AlertTitle', '@ViewBag.AlertMessage');
});
</script>
}
@if (Model.IsLoggedIn)
{
<div id="userMenu" class="menu dropdown">
<a href="#" data-bs-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
<span class="menu-title">
<span class="fa fa-user" aria-hidden="true"></span>
<span id="userMenuDisplayName">@Model.Name</span>
<span class="caret"></span>
</span>
</a>
<ul class="dropdown-menu">
<li>
<button type="button" class="btn btn-default" data-bs-toggle="modal" data-bs-target="#modalChangePassword">Change password</button>
</li>
<li>
@using (Html.BeginForm("logout", "index", FormMethod.Post, new { @class = "form-horizontal" }))
{
<button type="submit" class="btn btn-default">Logout</button>
}
</li>
</ul>
</div>
}
</div>

@ -0,0 +1,548 @@
๏ปฟ@model IndexViewModel;
<script>
/*
EonaCat Library
Copyright (C) 2017-2023 EonaCat (Jeroen Saey)
Licensed under the apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "aS IS" BaSIS,
WITHOUT WaRIsRecursionavailableNTIES OR CONDITIONS OF aNY KInternetD, either express or implied.
See the License for the specific language governing permissions and
limitations under the License
*/
var token = '@Model.Token';
var username = '@Model.Username';
var statsRefreshInterval = @DnsConstants.Stats.RefreshInterval;
var title = '@ViewData["Title"]';
var refreshTimerHandle;
var isadmin = false;
function showadmin()
{
$(".nav-tabs li").removeClass("active");
$(".tab-pane").removeClass("active");
if (statsRefreshInterval > 0)
{
refreshTimerHandle = setInterval(function ()
{
var type = $("input[name=radioStatsType]:checked").val();
if (type === "lastHour")
{
refreshDashboard();
}
}, statsRefreshInterval * 1000 * 60);
}
loadDashboard();
}
function showInfo()
{
hideAlert();
document.title = title;
$(".nav-tabs li").removeClass("active");
$(".tab-pane").removeClass("active");
$("#tabDashboardButton").addClass("active");
$("#main").show();
$("#divDashboard").hide();
$("#divInfo").addClass("active");
$("#divInfo").show();
}
function loadDashboard()
{
showDashboard();
refreshDashboard();
}
function showDashboard()
{
hideAlert();
document.title = title;
$(".nav-tabs li").removeClass("active");
$(".tab-pane").removeClass("active");
$("#tabDashboardButton").addClass("active");
$("#divInfo").hide();
$("#divInfo").removeClass("active");
$("#main").show();
$("#divDashboard").show();
}
$(function()
{
var headerHtml = $("#header").html();
$("#header").html(`<div class="title">
<a href="/">
<img src="/images/logo.svg" style="width: 30px" alt="EonaCat Logo" />
</a>${headerHtml}
</div>`);
$("[name=radioStatsType]").each(function (i) {
$(this).change(function () {
refreshDashboard();
});
});
isadmin = token !== '';
if (isadmin)
{
$(document).on('click', '.editDomain', function () {
var id = $(this).data('id');
var route = '@Url.Action("GetById", "Domain")?id=' + id;
$('#partial').load(route);
});
showadmin();
} else {
loadDashboard();
}
});
function checkLogin()
{
username = $("#username").val();
password = $("#password").val();
if (username === null || username === "")
{
shakeModal("modalLogin", "Please enter your username", "alert-warning")
return false;
}
if (password === null || password === "")
{
shakeModal("modalLogin", "Please enter your password", "alert-warning")
return false;
}
return true;
}
function checkPassword() {
var divChangePasswordalert = $("#divChangePasswordalert");
var oldPassword = $("#txtOldPassword").val();
var newPassword = $("#txtChangePasswordNewPassword").val();
var confirmPassword = $("#txtChangePasswordConfirmPassword").val();
if (oldPassword === null || oldPassword === "") {
showAlert("warning", "Missing", "Please enter your old password.", divChangePasswordalert);
return false;
}
if (newPassword === null || newPassword === "") {
showAlert("warning", "Missing", "Please enter a new password.", divChangePasswordalert);
return false;
}
if (confirmPassword === null || confirmPassword === "") {
showAlert("warning", "Missing", "Please confirm your password.", divChangePasswordalert);
return false;
}
if (newPassword !== confirmPassword) {
showAlert("warning", "Mismatch", "Passwords do not match. Please try again.", divChangePasswordalert);
return false;
}
return true;
}
const $statIds = ["#totalQueriesStats", "#totalNoErrorStats", "#totalServerFailuresStats", "#totalNameErrorsStats", "#totalRefusedStats", "#totalBlockedStats", "#totalCachedStats", "#totalClientsStats"];
const $percentageIds = ["#totalQueriesStatsPercentage", "#totalNoErrorStatsPercentage", "#totalServerFailuresStatsPercentage", "#totalNameErrorsStatsPercentage", "#totalRefusedStatsPercentage", "#totalBlockedStatsPercentage", "#totalCachedStatsPercentage"];
const divDashboardSpinner = $("#divDashboardSpinner");
const divDashboard = $("#divDashboard");
async function refreshDashboard(hideLoader = null)
{
if (!$("#tabDashboardButton").hasClass("active"))
{
return;
}
if ($("#divInfo").hasClass("active"))
{
return;
}
if (!hideLoader) {
divDashboard.hide();
divDashboardSpinner.show();
}
const type = $("input[name=radioStatsType]:checked").val();
await HTTPRequest({
url: `/api/stats?token=${token}&type=${type}`,
async: true,
loader: divDashboardSpinner,
keepalertVisible: hideLoader,
success: function (responseJSON) {
const result = responseJSON.response;
const stats = result.stats;
const keys = Object.keys(stats);
const totalQueries = stats.totalQueries;
$statIds.forEach((id, i) => {
$(id).text(stats[keys[i]].toLocaleString());
});
if (totalQueries > 0) {
$percentageIds.forEach((id, i) => {
const percentage = (stats[keys[i]] * 100 / totalQueries).toFixed(2);
$(id).text(`${percentage}%`);
});
} else {
$percentageIds.forEach(id => {
$(id).text("0%");
});
}
UpdateCharts(result);
if (!hideLoader) {
divDashboardSpinner.hide();
divDashboard.show();
}
},
error: function () {
if (!hideLoader) {
divDashboardSpinner.hide();
divDashboard.show();
}
},
invalidToken: function () {
loadDashboard();
}
});
return false;
}
function UpdateCharts(result) {
// stats chart
if (window.chartDashboardMain == null)
{
var canvasStats = document.getElementById("stats").getContext("2d");
window.chartDashboardMain = new Chart(canvasStats, {
type: "line",
data: result.statisticsData,
options: {
//responsive: false,
//maintainAspectRatio: false,
elements: {
line: {
tension: 0.1
}
},
}
});
} else {
window.chartDashboardMain.data = result.statisticsData;
window.chartDashboardMain.update();
}
if (!isadmin || result.queryTypeChartData.labels.length == 0) {
$('.topList').hide();
}
else
{
$('.topList').show();
// query type chart
if (window.chartqueryDonut == null) {
var contextqueryDonut = document.getElementById("queryDonut").getContext("2d");
window.chartqueryDonut = new Chart(contextqueryDonut,
{
type: "doughnut",
data: result.queryTypeChartData,
options: {
//responsive: false,
//maintainAspectRatio: true,
}
});
} else {
window.chartqueryDonut.data = result.queryTypeChartData;
window.chartqueryDonut.update();
}
}
setTimeout(function () { createTable("topBlocked", result.topBlocked) }, 100);
setTimeout(function () { createTable("topClients", result.topClients) }, 100);
setTimeout(function () { createTable("topDomains", result.topDomains) }, 100);
setTimeout(function () { createTable("lastQueries", result.lastQueries) }, 100);
}
function allowedButton(id)
{
var button = '<a href="#" title="Add to the allowList" onclick = "return allowDomain(' + id +');"> Allow</a>';
return button;
}
function allowDomain(id) {
HTTPRequest({
url: "/api/allowDomain?token=" + token + "&id=" + id,
async: true,
success: function (responseJSON) {
refreshDashboard();
},
invalidToken: function () {
logout();
loadDashboard();
},
});
return false;
}
function allowClientButton(name) {
var button = '<a href="#" title="Allow the client" onclick = "return allowClient(\'' + name + '\');"> Allow</a>';
return button;
}
function blockClientButton(name) {
var button = '<a href="#" title="Block the client" onclick = "return blockClient(\'' + name + '\');"> Block</a>';
return button;
}
function allowButton(id) {
var button = '<a href="#" title="Add to the allowList" onclick = "return allowDomain(' + id + ');"> Allow</a>';